/src/libreoffice/tools/source/datetime/duration.cxx
Line | Count | Source |
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 | | |
16 | | namespace tools |
17 | | { |
18 | | Duration::Duration(const ::DateTime& rStart, const ::DateTime& rEnd) |
19 | 53.3k | : mnDays(static_cast<const Date&>(rEnd) - static_cast<const Date&>(rStart)) |
20 | 53.3k | { |
21 | 53.3k | SetTimeDiff(rStart, rEnd); |
22 | 53.3k | } |
23 | | |
24 | | Duration::Duration(const Time& rStart, const Time& rEnd) |
25 | 0 | { |
26 | 0 | const sal_uInt16 nStartHour = rStart.GetHour(); |
27 | 0 | const sal_uInt16 nEndHour = rEnd.GetHour(); |
28 | 0 | if (nStartHour >= 24 || nEndHour >= 24) |
29 | 0 | { |
30 | 0 | Time aEnd(rEnd); |
31 | 0 | if (nEndHour >= 24) |
32 | 0 | { |
33 | 0 | mnDays = (nEndHour / 24) * (aEnd.GetTime() < 0 ? -1 : 1); |
34 | 0 | aEnd.SetHour(nEndHour % 24); |
35 | 0 | } |
36 | 0 | Time aStart(rStart); |
37 | 0 | if (nStartHour >= 24) |
38 | 0 | { |
39 | 0 | mnDays -= (nStartHour / 24) * (aStart.GetTime() < 0 ? -1 : 1); |
40 | 0 | aStart.SetHour(nStartHour % 24); |
41 | 0 | } |
42 | 0 | SetTimeDiff(aStart, aEnd); |
43 | 0 | } |
44 | 0 | else |
45 | 0 | { |
46 | 0 | SetTimeDiff(rStart, rEnd); |
47 | 0 | } |
48 | 0 | } |
49 | | |
50 | | Duration::Duration(double fTimeInDays, sal_uInt64 nAccuracyEpsilonNanoseconds) |
51 | 1.24k | { |
52 | 1.24k | assert(nAccuracyEpsilonNanoseconds <= Time::nanoSecPerSec - 1); |
53 | 1.24k | double fInt, fFrac; |
54 | 1.24k | if (fTimeInDays < 0.0) |
55 | 0 | { |
56 | 0 | fInt = ::rtl::math::approxCeil(fTimeInDays); |
57 | 0 | fFrac = fInt <= fTimeInDays ? 0.0 : fTimeInDays - fInt; |
58 | 0 | } |
59 | 1.24k | else |
60 | 1.24k | { |
61 | 1.24k | fInt = ::rtl::math::approxFloor(fTimeInDays); |
62 | 1.24k | fFrac = fInt >= fTimeInDays ? 0.0 : fTimeInDays - fInt; |
63 | 1.24k | } |
64 | 1.24k | mnDays = static_cast<sal_Int32>(fInt); |
65 | 1.24k | if (fFrac) |
66 | 77 | { |
67 | 77 | fFrac *= Time::nanoSecPerDay; |
68 | 77 | fFrac = ::rtl::math::approxFloor(fFrac); |
69 | 77 | sal_Int64 nNS = static_cast<sal_Int64>(fFrac); |
70 | 77 | const sal_Int64 nN = nNS % Time::nanoSecPerSec; |
71 | 77 | if (nN) |
72 | 47 | { |
73 | 47 | const sal_uInt64 nA = std::abs(nN); |
74 | 47 | if (nA <= nAccuracyEpsilonNanoseconds) |
75 | 0 | nNS -= (nNS < 0) ? -nN : nN; |
76 | 47 | else if (nA >= Time::nanoSecPerSec - nAccuracyEpsilonNanoseconds) |
77 | 18 | { |
78 | 18 | const sal_Int64 nD = Time::nanoSecPerSec - nA; |
79 | 18 | nNS += (nNS < 0) ? -nD : nD; |
80 | 18 | if (std::abs(nNS) >= Time::nanoSecPerDay) |
81 | 0 | { |
82 | 0 | mnDays += nNS / Time::nanoSecPerDay; |
83 | 0 | nNS %= Time::nanoSecPerDay; |
84 | 0 | } |
85 | 18 | } |
86 | 47 | } |
87 | 77 | maTime.MakeTimeFromNS(nNS); |
88 | 77 | assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (nNS < 0)); |
89 | 77 | } |
90 | 1.24k | } |
91 | | |
92 | | Duration::Duration(sal_Int32 nDays, const Time& rTime) |
93 | 0 | : mnDays(nDays) |
94 | 0 | { |
95 | 0 | assert(nDays == 0 || rTime.GetTime() == 0 || (nDays < 0) == (rTime.GetTime() < 0)); |
96 | 0 | Normalize(rTime.GetHour(), rTime.GetMin(), rTime.GetSec(), rTime.GetNanoSec(), |
97 | 0 | ((nDays < 0) || (rTime.GetTime() < 0))); |
98 | 0 | } |
99 | | |
100 | | Duration::Duration(sal_Int32 nDays, sal_uInt32 nHours, sal_uInt32 nMinutes, sal_uInt32 nSeconds, |
101 | | sal_uInt64 nNanoseconds) |
102 | 0 | : mnDays(nDays) |
103 | 0 | { |
104 | 0 | Normalize(nHours, nMinutes, nSeconds, nNanoseconds, nDays < 0); |
105 | 0 | } |
106 | | |
107 | | Duration::Duration(sal_Int32 nDays, sal_Int64 nTime) |
108 | 0 | : maTime(Time::fromEncodedTime(nTime)) |
109 | 0 | , mnDays(nDays) |
110 | 0 | { |
111 | 0 | } |
112 | | |
113 | | void Duration::Normalize(sal_uInt64 nHours, sal_uInt64 nMinutes, sal_uInt64 nSeconds, |
114 | | sal_uInt64 nNanoseconds, bool bNegative) |
115 | 0 | { |
116 | 0 | if (nNanoseconds >= Time::nanoSecPerSec) |
117 | 0 | { |
118 | 0 | nSeconds += nNanoseconds / Time::nanoSecPerSec; |
119 | 0 | nNanoseconds %= Time::nanoSecPerSec; |
120 | 0 | } |
121 | 0 | if (nSeconds >= Time::secondPerMinute) |
122 | 0 | { |
123 | 0 | nMinutes += nSeconds / Time::secondPerMinute; |
124 | 0 | nSeconds %= Time::secondPerMinute; |
125 | 0 | } |
126 | 0 | if (nMinutes >= Time::minutePerHour) |
127 | 0 | { |
128 | 0 | nHours += nMinutes / Time::minutePerHour; |
129 | 0 | nMinutes %= Time::minutePerHour; |
130 | 0 | } |
131 | 0 | if (nHours >= Time::hourPerDay) |
132 | 0 | { |
133 | 0 | sal_Int64 nDiff = nHours / Time::hourPerDay; |
134 | 0 | nHours %= Time::hourPerDay; |
135 | 0 | bool bOverflow = false; |
136 | 0 | if (bNegative) |
137 | 0 | { |
138 | 0 | nDiff = -nDiff; |
139 | 0 | bOverflow = (nDiff < SAL_MIN_INT32); |
140 | 0 | bOverflow |= o3tl::checked_add(mnDays, static_cast<sal_Int32>(nDiff), mnDays); |
141 | 0 | if (bOverflow) |
142 | 0 | mnDays = SAL_MIN_INT32; |
143 | 0 | } |
144 | 0 | else |
145 | 0 | { |
146 | 0 | bOverflow = (nDiff > SAL_MAX_INT32); |
147 | 0 | bOverflow |= o3tl::checked_add(mnDays, static_cast<sal_Int32>(nDiff), mnDays); |
148 | 0 | if (bOverflow) |
149 | 0 | mnDays = SAL_MAX_INT32; |
150 | 0 | } |
151 | 0 | assert(!bOverflow); |
152 | 0 | if (bOverflow) |
153 | 0 | { |
154 | 0 | nHours = Time::hourPerDay - 1; |
155 | 0 | nMinutes = Time::minutePerHour - 1; |
156 | 0 | nSeconds = Time::secondPerMinute - 1; |
157 | 0 | nNanoseconds = Time::nanoSecPerSec - 1; |
158 | 0 | } |
159 | 0 | } |
160 | 0 | maTime = Time(nHours, nMinutes, nSeconds, nNanoseconds); |
161 | 0 | if (bNegative) |
162 | 0 | maTime = -maTime; |
163 | 0 | assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (maTime.GetTime() < 0)); |
164 | 0 | } |
165 | | |
166 | | void Duration::ApplyTime(sal_Int64 nNS) |
167 | 53.3k | { |
168 | 53.3k | if (mnDays > 0 && nNS < 0) |
169 | 0 | { |
170 | 0 | --mnDays; |
171 | 0 | nNS = Time::nanoSecPerDay + nNS; |
172 | 0 | } |
173 | 53.3k | else if (mnDays < 0 && nNS > 0) |
174 | 0 | { |
175 | 0 | ++mnDays; |
176 | 0 | nNS = -Time::nanoSecPerDay + nNS; |
177 | 0 | } |
178 | 53.3k | maTime.MakeTimeFromNS(nNS); |
179 | 53.3k | assert(mnDays == 0 || maTime.GetTime() == 0 || (mnDays < 0) == (maTime.GetTime() < 0)); |
180 | 53.3k | } |
181 | | |
182 | | void Duration::SetTimeDiff(const Time& rStart, const Time& rEnd) |
183 | 53.3k | { |
184 | 53.3k | const sal_Int64 nNS = rEnd.GetNSFromTime() - rStart.GetNSFromTime(); |
185 | 53.3k | ApplyTime(nNS); |
186 | 53.3k | } |
187 | | |
188 | | Duration Duration::operator-() const |
189 | 0 | { |
190 | 0 | Duration aD(-mnDays, -maTime.GetTime()); |
191 | 0 | return aD; |
192 | 0 | } |
193 | | |
194 | | Duration& Duration::Add(const Duration& rDuration, bool& rbOverflow) |
195 | 0 | { |
196 | 0 | rbOverflow = o3tl::checked_add(mnDays, rDuration.mnDays, mnDays); |
197 | | // Duration is always normalized, time values >= 24h don't occur. |
198 | 0 | sal_Int64 nNS = maTime.GetNSFromTime() + rDuration.maTime.GetNSFromTime(); |
199 | 0 | if (nNS < -Time::nanoSecPerDay) |
200 | 0 | { |
201 | 0 | rbOverflow |= o3tl::checked_sub(mnDays, sal_Int32(1), mnDays); |
202 | 0 | nNS += Time::nanoSecPerDay; |
203 | 0 | } |
204 | 0 | else if (nNS > Time::nanoSecPerDay) |
205 | 0 | { |
206 | 0 | rbOverflow |= o3tl::checked_add(mnDays, sal_Int32(1), mnDays); |
207 | 0 | nNS -= Time::nanoSecPerDay; |
208 | 0 | } |
209 | 0 | ApplyTime(nNS); |
210 | 0 | return *this; |
211 | 0 | } |
212 | | |
213 | | Duration Duration::Mult(sal_Int32 nMult, bool& rbOverflow) const |
214 | 0 | { |
215 | | // First try a simple calculation in nanoseconds. |
216 | 0 | bool bBadNS = false; |
217 | 0 | sal_Int64 nNS; |
218 | 0 | sal_Int64 nDays; |
219 | 0 | if (o3tl::checked_multiply(static_cast<sal_Int64>(mnDays), static_cast<sal_Int64>(nMult), nDays) |
220 | 0 | || o3tl::checked_multiply(nDays, Time::nanoSecPerDay, nDays) |
221 | 0 | || o3tl::checked_multiply(maTime.GetNSFromTime(), static_cast<sal_Int64>(nMult), nNS) |
222 | 0 | || o3tl::checked_add(nDays, nNS, nNS)) |
223 | 0 | { |
224 | 0 | bBadNS = rbOverflow = true; |
225 | 0 | } |
226 | 0 | else |
227 | 0 | { |
228 | 0 | const sal_Int64 nD = nNS / Time::nanoSecPerDay; |
229 | 0 | if (nD < SAL_MIN_INT32 || SAL_MAX_INT32 < nD) |
230 | 0 | rbOverflow = true; |
231 | 0 | else |
232 | 0 | { |
233 | 0 | rbOverflow = false; |
234 | 0 | nNS -= nD * Time::nanoSecPerDay; |
235 | 0 | Duration aD(static_cast<sal_Int32>(nD), 0); |
236 | 0 | aD.ApplyTime(nNS); |
237 | 0 | return aD; |
238 | 0 | } |
239 | 0 | } |
240 | 0 | if (bBadNS) |
241 | 0 | { |
242 | | // Simple calculation in overall nanoseconds overflowed, try with |
243 | | // individual components. |
244 | 0 | const sal_uInt64 nMult64 = (nMult < 0) ? -nMult : nMult; |
245 | 0 | do |
246 | 0 | { |
247 | 0 | sal_uInt64 nN; |
248 | 0 | if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetNanoSec()), nMult64, nN)) |
249 | 0 | break; |
250 | 0 | sal_uInt64 nS; |
251 | 0 | if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetSec()), nMult64, nS)) |
252 | 0 | break; |
253 | 0 | sal_uInt64 nM; |
254 | 0 | if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetMin()), nMult64, nM)) |
255 | 0 | break; |
256 | 0 | sal_uInt64 nH; |
257 | 0 | if (o3tl::checked_multiply(static_cast<sal_uInt64>(maTime.GetHour()), nMult64, nH)) |
258 | 0 | break; |
259 | 0 | sal_uInt64 nD; |
260 | 0 | if (o3tl::checked_multiply( |
261 | 0 | mnDays < 0 ? static_cast<sal_uInt64>(-static_cast<sal_Int64>(mnDays)) |
262 | 0 | : static_cast<sal_uInt64>(mnDays), |
263 | 0 | nMult64, nD)) |
264 | 0 | break; |
265 | 0 | if (nN > Time::nanoSecPerSec) |
266 | 0 | { |
267 | 0 | const sal_uInt64 nC = nN / Time::nanoSecPerSec; |
268 | 0 | if (o3tl::checked_add(nS, nC, nS)) |
269 | 0 | break; |
270 | 0 | nN -= nC * Time::nanoSecPerSec; |
271 | 0 | } |
272 | 0 | if (nS > Time::secondPerMinute) |
273 | 0 | { |
274 | 0 | const sal_uInt64 nC = nS / Time::secondPerMinute; |
275 | 0 | if (o3tl::checked_add(nM, nC, nM)) |
276 | 0 | break; |
277 | 0 | nS -= nC * Time::secondPerMinute; |
278 | 0 | } |
279 | 0 | if (nM > Time::minutePerHour) |
280 | 0 | { |
281 | 0 | const sal_uInt64 nC = nM / Time::minutePerHour; |
282 | 0 | if (o3tl::checked_add(nH, nC, nH)) |
283 | 0 | break; |
284 | 0 | nM -= nC * Time::minutePerHour; |
285 | 0 | } |
286 | 0 | if (nH > Time::hourPerDay) |
287 | 0 | { |
288 | 0 | const sal_uInt64 nC = nH / Time::hourPerDay; |
289 | 0 | if (o3tl::checked_add(nD, nC, nD)) |
290 | 0 | break; |
291 | 0 | nH -= nC * Time::hourPerDay; |
292 | 0 | } |
293 | 0 | if (IsNegative() ? (static_cast<sal_uInt64>(SAL_MAX_INT32) + 1) < nD |
294 | 0 | || -static_cast<sal_Int64>(nD) < SAL_MIN_INT32 |
295 | 0 | : SAL_MAX_INT32 < nD) |
296 | 0 | break; |
297 | | |
298 | 0 | rbOverflow = false; |
299 | 0 | Time aTime(nH, nM, nS, nN); |
300 | 0 | if (IsNegative() == (nMult < 0)) |
301 | 0 | { |
302 | 0 | Duration aD(nD, aTime.GetTime()); |
303 | 0 | return aD; |
304 | 0 | } |
305 | 0 | else |
306 | 0 | { |
307 | 0 | Duration aD(-static_cast<sal_Int64>(nD), -aTime.GetTime()); |
308 | 0 | return aD; |
309 | 0 | } |
310 | 0 | } while (false); |
311 | 0 | } |
312 | 0 | assert(rbOverflow); |
313 | 0 | if (IsNegative() == (nMult < 0)) |
314 | 0 | { |
315 | 0 | Duration aD(SAL_MAX_INT32, 0); |
316 | 0 | aD.ApplyTime(Time::nanoSecPerDay - 1); |
317 | 0 | return aD; |
318 | 0 | } |
319 | 0 | else |
320 | 0 | { |
321 | 0 | Duration aD(SAL_MIN_INT32, 0); |
322 | 0 | aD.ApplyTime(-(Time::nanoSecPerDay - 1)); |
323 | 0 | return aD; |
324 | 0 | } |
325 | 0 | } |
326 | | }; |
327 | | |
328 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ |