LibreOffice Module tools (master) 1
duration.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
9
10#include <tools/duration.hxx>
11#include <tools/datetime.hxx>
12#include <rtl/math.hxx>
13#include <o3tl/safeint.hxx>
14#include <cmath>
15
16namespace tools
17{
18Duration::Duration(const ::DateTime& rStart, const ::DateTime& rEnd)
19 : mnDays(static_cast<const Date&>(rEnd) - static_cast<const Date&>(rStart))
20{
21 SetTimeDiff(rStart, rEnd);
22}
23
24Duration::Duration(const Time& rStart, const Time& rEnd)
25{
26 const sal_uInt16 nStartHour = rStart.GetHour();
27 const sal_uInt16 nEndHour = rEnd.GetHour();
28 if (nStartHour >= 24 || nEndHour >= 24)
29 {
30 Time aEnd(rEnd);
31 if (nEndHour >= 24)
32 {
33 mnDays = (nEndHour / 24) * (aEnd.GetTime() < 0 ? -1 : 1);
34 aEnd.SetHour(nEndHour % 24);
35 }
36 Time aStart(rStart);
37 if (nStartHour >= 24)
38 {
39 mnDays -= (nStartHour / 24) * (aStart.GetTime() < 0 ? -1 : 1);
40 aStart.SetHour(nStartHour % 24);
41 }
42 SetTimeDiff(aStart, aEnd);
43 }
44 else
45 {
46 SetTimeDiff(rStart, rEnd);
47 }
48}
49
50Duration::Duration(double fTimeInDays)
51{
52 double fInt, fFrac;
53 if (fTimeInDays < 0.0)
54 {
55 fInt = ::rtl::math::approxCeil(fTimeInDays);
56 fFrac = fInt <= fTimeInDays ? 0.0 : fTimeInDays - fInt;
57 }
58 else
59 {
60 fInt = ::rtl::math::approxFloor(fTimeInDays);
61 fFrac = fInt >= fTimeInDays ? 0.0 : fTimeInDays - fInt;
62 }
63 mnDays = static_cast<sal_Int32>(fInt);
64 if (fFrac)
65 {
66 fFrac *= Time::nanoSecPerDay;
67 fFrac = ::rtl::math::approxFloor(fFrac);
68 sal_Int64 nNS = static_cast<sal_Int64>(fFrac);
69 // Round by 1 nanosecond if it's just 1 off to a second, i.e.
70 // 0999999999 or 0000000001. This could be loosened to rounding by 2 or
71 // such if necessary.
72 const sal_Int64 nN = nNS % Time::nanoSecPerSec;
73 if (std::abs(nN) == 1)
74 nNS -= (nNS < 0) ? -1 : 1;
75 else if (std::abs(nN) == Time::nanoSecPerSec - 1)
76 {
77 nNS += (nNS < 0) ? -1 : 1;
78 if (std::abs(nNS) >= Time::nanoSecPerDay)
79 {
82 }
83 }
85 assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (nNS < 0));
86 }
87}
88
89Duration::Duration(sal_Int32 nDays, const Time& rTime)
90 : mnDays(nDays)
91{
92 assert(nDays == 0 || rTime.GetTime() == 0 || (nDays < 0) == (rTime.GetTime() < 0));
93 Normalize(rTime.GetHour(), rTime.GetMin(), rTime.GetSec(), rTime.GetNanoSec(),
94 ((nDays < 0) || (rTime.GetTime() < 0)));
95}
96
97Duration::Duration(sal_Int32 nDays, sal_uInt32 nHours, sal_uInt32 nMinutes, sal_uInt32 nSeconds,
98 sal_uInt64 nNanoseconds)
99 : mnDays(nDays)
100{
101 Normalize(nHours, nMinutes, nSeconds, nNanoseconds, nDays < 0);
102}
103
104Duration::Duration(sal_Int32 nDays, sal_Int64 nTime)
105 : maTime(nTime)
106 , mnDays(nDays)
107{
108}
109
110void Duration::Normalize(sal_uInt64 nHours, sal_uInt64 nMinutes, sal_uInt64 nSeconds,
111 sal_uInt64 nNanoseconds, bool bNegative)
112{
113 if (nNanoseconds >= Time::nanoSecPerSec)
114 {
115 nSeconds += nNanoseconds / Time::nanoSecPerSec;
116 nNanoseconds %= Time::nanoSecPerSec;
117 }
118 if (nSeconds >= Time::secondPerMinute)
119 {
120 nMinutes += nSeconds / Time::secondPerMinute;
121 nSeconds %= Time::secondPerMinute;
122 }
123 if (nMinutes >= Time::minutePerHour)
124 {
125 nHours += nMinutes / Time::minutePerHour;
126 nMinutes %= Time::minutePerHour;
127 }
128 if (nHours >= Time::hourPerDay)
129 {
130 sal_Int64 nDiff = nHours / Time::hourPerDay;
131 nHours %= Time::hourPerDay;
132 bool bOverflow = false;
133 if (bNegative)
134 {
135 nDiff = -nDiff;
136 bOverflow = (nDiff < SAL_MIN_INT32);
137 bOverflow |= o3tl::checked_add(mnDays, static_cast<sal_Int32>(nDiff), mnDays);
138 if (bOverflow)
140 }
141 else
142 {
143 bOverflow = (nDiff > SAL_MAX_INT32);
144 bOverflow |= o3tl::checked_add(mnDays, static_cast<sal_Int32>(nDiff), mnDays);
145 if (bOverflow)
147 }
148 assert(!bOverflow);
149 if (bOverflow)
150 {
151 nHours = Time::hourPerDay - 1;
152 nMinutes = Time::minutePerHour - 1;
153 nSeconds = Time::secondPerMinute - 1;
154 nNanoseconds = Time::nanoSecPerSec - 1;
155 }
156 }
157 maTime = Time(nHours, nMinutes, nSeconds, nNanoseconds);
158 if (bNegative)
159 maTime = -maTime;
160 assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (maTime.GetTime() < 0));
161}
162
163void Duration::ApplyTime(sal_Int64 nNS)
164{
165 if (mnDays > 0 && nNS < 0)
166 {
167 --mnDays;
168 nNS = Time::nanoSecPerDay + nNS;
169 }
170 else if (mnDays < 0 && nNS > 0)
171 {
172 ++mnDays;
173 nNS = -Time::nanoSecPerDay + nNS;
174 }
176 assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (maTime.GetTime() < 0));
177}
178
179void Duration::SetTimeDiff(const Time& rStart, const Time& rEnd)
180{
181 const sal_Int64 nNS = rEnd.GetNSFromTime() - rStart.GetNSFromTime();
182 ApplyTime(nNS);
183}
184
186{
187 Duration aD(-mnDays, -maTime.GetTime());
188 return aD;
189}
190
191Duration& Duration::Add(const Duration& rDuration, bool& rbOverflow)
192{
193 rbOverflow = o3tl::checked_add(mnDays, rDuration.mnDays, mnDays);
194 // Duration is always normalized, time values >= 24h don't occur.
195 sal_Int64 nNS = maTime.GetNSFromTime() + rDuration.maTime.GetNSFromTime();
196 if (nNS < -Time::nanoSecPerDay)
197 {
198 rbOverflow |= o3tl::checked_sub(mnDays, sal_Int32(1), mnDays);
199 nNS += Time::nanoSecPerDay;
200 }
201 else if (nNS > Time::nanoSecPerDay)
202 {
203 rbOverflow |= o3tl::checked_add(mnDays, sal_Int32(1), mnDays);
204 nNS -= Time::nanoSecPerDay;
205 }
206 ApplyTime(nNS);
207 return *this;
208}
209
210Duration Duration::Mult(sal_Int32 nMult, bool& rbOverflow) const
211{
212 // First try a simple calculation in nanoseconds.
213 bool bBadNS = false;
214 sal_Int64 nNS;
215 sal_Int64 nDays;
216 if (o3tl::checked_multiply(static_cast<sal_Int64>(mnDays), static_cast<sal_Int64>(nMult), nDays)
218 || o3tl::checked_multiply(maTime.GetNSFromTime(), static_cast<sal_Int64>(nMult), nNS)
219 || o3tl::checked_add(nDays, nNS, nNS))
220 {
221 bBadNS = rbOverflow = true;
222 }
223 else
224 {
225 const sal_Int64 nD = nNS / Time::nanoSecPerDay;
226 if (nD < SAL_MIN_INT32 || SAL_MAX_INT32 < nD)
227 rbOverflow = true;
228 else
229 {
230 rbOverflow = false;
231 nNS -= nD * Time::nanoSecPerDay;
232 Duration aD(static_cast<sal_Int32>(nD), 0);
233 aD.ApplyTime(nNS);
234 return aD;
235 }
236 }
237 if (bBadNS)
238 {
239 // Simple calculation in overall nanoseconds overflowed, try with
240 // individual components.
241 const sal_uInt64 nMult64 = (nMult < 0) ? -nMult : nMult;
242 do
243 {
244 sal_uInt64 nN;
245 if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetNanoSec()), nMult64, nN))
246 break;
247 sal_uInt64 nS;
248 if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetSec()), nMult64, nS))
249 break;
250 sal_uInt64 nM;
251 if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetMin()), nMult64, nM))
252 break;
253 sal_uInt64 nH;
254 if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetHour()), nMult64, nH))
255 break;
256 sal_uInt64 nD;
258 mnDays < 0 ? static_cast<sal_uInt64>(-static_cast<sal_Int64>(mnDays))
259 : static_cast<sal_uInt64>(mnDays),
260 nMult64, nD))
261 break;
262 if (nN > Time::nanoSecPerSec)
263 {
264 const sal_uInt64 nC = nN / Time::nanoSecPerSec;
265 if (o3tl::checked_add(nS, nC, nS))
266 break;
267 nN -= nC * Time::nanoSecPerSec;
268 }
269 if (nS > Time::secondPerMinute)
270 {
271 const sal_uInt64 nC = nS / Time::secondPerMinute;
272 if (o3tl::checked_add(nM, nC, nM))
273 break;
274 nS -= nC * Time::secondPerMinute;
275 }
276 if (nM > Time::minutePerHour)
277 {
278 const sal_uInt64 nC = nM / Time::minutePerHour;
279 if (o3tl::checked_add(nH, nC, nH))
280 break;
281 nM -= nC * Time::minutePerHour;
282 }
283 if (nH > Time::hourPerDay)
284 {
285 const sal_uInt64 nC = nH / Time::hourPerDay;
286 if (o3tl::checked_add(nD, nC, nD))
287 break;
288 nH -= nC * Time::hourPerDay;
289 }
290 if (IsNegative() ? (static_cast<sal_uInt64>(SAL_MAX_INT32) + 1) < nD
291 || -static_cast<sal_Int64>(nD) < SAL_MIN_INT32
292 : SAL_MAX_INT32 < nD)
293 break;
294
295 rbOverflow = false;
296 Time aTime(nH, nM, nS, nN);
297 if (IsNegative() == (nMult < 0))
298 {
299 Duration aD(nD, aTime.GetTime());
300 return aD;
301 }
302 else
303 {
304 Duration aD(-static_cast<sal_Int64>(nD), -aTime.GetTime());
305 return aD;
306 }
307 } while (false);
308 }
309 assert(rbOverflow);
310 if (IsNegative() == (nMult < 0))
311 {
312 Duration aD(SAL_MAX_INT32, 0);
314 return aD;
315 }
316 else
317 {
318 Duration aD(SAL_MIN_INT32, 0);
320 return aD;
321 }
322}
323};
324
325/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
Represents a date in the proleptic Gregorian calendar.
Definition: date.hxx:55
Duration in days and time.
Definition: duration.hxx:22
sal_Int32 mnDays
Definition: duration.hxx:85
Duration & Add(const Duration &rDuration, bool &rbOverflow)
Add a duration to this instance.
Definition: duration.cxx:191
void ApplyTime(sal_Int64 nNS)
Prerequisite: mnDays is already correctly set and absolute value of nanoseconds less than one day.
Definition: duration.cxx:163
Duration operator-() const
Unary minus.
Definition: duration.cxx:185
bool IsNegative() const
Definition: duration.hxx:50
Duration Mult(sal_Int32 nMult, bool &rbOverflow) const
Get multiple of duration.
Definition: duration.cxx:210
void SetTimeDiff(const Time &rStart, const Time &rEnd)
Prerequisite: mnDays is already correctly set and Time hour values are adjusted.
Definition: duration.cxx:179
void Normalize(sal_uInt64 nHours, sal_uInt64 nMinutes, sal_uInt64 nSeconds, sal_uInt64 nNanoseconds, bool bNegative)
Prerequisite: mnDays is already set.
Definition: duration.cxx:110
static const sal_Int64 minutePerHour
Definition: time.hxx:54
static const sal_Int64 nanoSecPerDay
Definition: time.hxx:59
void MakeTimeFromNS(sal_Int64 nNS)
Definition: ttime.cxx:227
sal_uInt16 GetSec() const
Definition: time.hxx:91
static const sal_Int64 nanoSecPerSec
Definition: time.hxx:56
sal_Int64 GetTime() const
Definition: time.hxx:78
sal_uInt16 GetMin() const
Definition: time.hxx:88
static const sal_Int64 secondPerMinute
Definition: time.hxx:55
static const sal_Int64 hourPerDay
Definition: time.hxx:53
sal_uInt16 GetHour() const
Definition: time.hxx:85
sal_uInt32 GetNanoSec() const
Definition: time.hxx:94
sal_Int64 GetNSFromTime() const
Definition: ttime.cxx:212
void SetHour(sal_uInt16 nNewHour)
Definition: ttime.cxx:147
Degree100 abs(Degree100 x)
Definition: degree.hxx:42
std::enable_if< std::is_signed< T >::value, bool >::type checked_add(T a, T b, T &result)
std::enable_if< std::is_signed< T >::value, bool >::type checked_multiply(T a, T b, T &result)
std::enable_if< std::is_signed< T >::value, bool >::type checked_sub(T a, T b, T &result)
Note: this class is a true marvel of engineering: because the author could not decide whether it's be...
#define SAL_MAX_INT32
#define SAL_MIN_INT32