Coverage Report

Created: 2025-11-16 09:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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: */