Coverage Report

Created: 2026-09-28 10:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/unotools/source/misc/datetime.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <unotools/datetime.hxx>
21
#include <unotools/localedatawrapper.hxx>
22
#include <unotools/syslocale.hxx>
23
#include <tools/date.hxx>
24
#include <tools/time.hxx>
25
#include <tools/datetime.hxx>
26
#include <rtl/ustrbuf.hxx>
27
#include <rtl/math.hxx>
28
#include <osl/diagnose.h>
29
#include <comphelper/string.hxx>
30
#include <o3tl/string_view.hxx>
31
#include <cstddef>
32
#include <sstream>
33
34
namespace
35
{
36
    bool checkAllNumber(std::u16string_view rString)
37
29.3k
    {
38
29.3k
        sal_Int32 nPos = 0;
39
29.3k
        sal_Int32 nLen = rString.size();
40
41
        // skip white space
42
29.3k
        while( nPos < nLen && ' ' == rString[nPos] )
43
3
            nPos++;
44
45
29.3k
        if( nPos < nLen && '-' == rString[nPos] )
46
0
            nPos++;
47
48
        // get number
49
114k
        while( nPos < nLen &&
50
87.0k
               '0' <= rString[nPos] &&
51
87.0k
               '9' >= rString[nPos] )
52
85.2k
        {
53
85.2k
            nPos++;
54
85.2k
        }
55
56
29.3k
        return nPos == nLen;
57
29.3k
    }
58
59
    /** convert string to number with optional min and max values */
60
    bool convertNumber32(sal_Int32& rValue,
61
                         std::u16string_view rString,
62
                         sal_Int32 /*nMin*/ = -1, sal_Int32 /*nMax*/ = -1)
63
26.1k
    {
64
26.1k
        if (!checkAllNumber(rString))
65
1.79k
        {
66
1.79k
            rValue = 0;
67
1.79k
            return false;
68
1.79k
        }
69
70
24.3k
        rValue = o3tl::toInt32(rString);
71
24.3k
        return true;
72
26.1k
    }
73
74
    bool convertNumber64(sal_Int64& rValue,
75
                         std::u16string_view rString,
76
                         sal_Int64 /*nMin*/ = -1, sal_Int64 /*nMax*/ = -1)
77
3.20k
    {
78
3.20k
        if (!checkAllNumber(rString))
79
0
        {
80
0
            rValue = 0;
81
0
            return false;
82
0
        }
83
84
3.20k
        rValue = o3tl::toInt64(rString);
85
3.20k
        return true;
86
3.20k
    }
87
88
    // although the standard calls for fixed-length (zero-padded) tokens
89
    // (in their integer part), we are here liberal and allow shorter tokens
90
    // (when there are separators, else it is ambiguous).
91
    // Note that:
92
    //   the token separator is OPTIONAL
93
    //   empty string is a valid token! (to recognise hh or hhmm or hh:mm formats)
94
    // returns: success / failure
95
    // in case of failure, no reference argument is changed
96
    // arguments:
97
    //   i_str: string to extract token from
98
    //   index: index in i_str where to start tokenizing
99
    //          after return, start of *next* token (if any)
100
    //          if this was the last token, then the value is UNDEFINED
101
    //   o_strInt:    output; integer part of token
102
    //   o_bFraction: output; was there a fractional part?
103
    //   o_strFrac:   output; fractional part of token
104
    bool impl_getISO8601TimeToken(std::u16string_view i_str, std::size_t &nPos, OUString &resInt, bool &bFraction, OUString &resFrac)
105
13.6k
    {
106
13.6k
        bFraction = false;
107
        // all tokens are of length 2
108
13.6k
        const std::size_t nEndPos = nPos + 2;
109
13.6k
        const sal_Unicode c0 = '0';
110
13.6k
        const sal_Unicode c9 = '9';
111
13.6k
        const sal_Unicode sep = ':';
112
38.4k
        for (;nPos < nEndPos && nPos < i_str.size(); ++nPos)
113
27.2k
        {
114
27.2k
            const sal_Unicode c = i_str[nPos];
115
27.2k
            if (c == sep)
116
2.35k
                return true;
117
24.8k
            if (c < c0 || c > c9)
118
163
                return false;
119
24.7k
            resInt += OUStringChar(c);
120
24.7k
        }
121
11.1k
        if (nPos == 0)
122
0
            return false;
123
11.1k
        if (nPos == i_str.size() || i_str[nPos] == sep)
124
6.84k
            return true;
125
4.31k
        if (i_str[nPos] == ',' || i_str[nPos] == '.')
126
3.67k
        {
127
3.67k
            bFraction = true;
128
3.67k
            ++nPos;
129
33.9k
            for (; nPos < i_str.size(); ++nPos)
130
30.7k
            {
131
30.7k
                const sal_Unicode c = i_str[nPos];
132
30.7k
                if (c == 'Z' || c == '+' || c == '-')
133
113
                {
134
113
                    --nPos; // we don't want to skip the tz separator
135
113
                    return true;
136
113
                }
137
30.6k
                if (c == sep)
138
                    // fractional part allowed only in *last* token
139
127
                    return false;
140
30.5k
                if (c < c0 || c > c9)
141
310
                    return false;
142
30.2k
                resFrac += OUStringChar(c);
143
30.2k
            }
144
3.12k
            OSL_ENSURE(nPos == i_str.size(), "impl_getISO8601TimeToken internal error; expected to be at end of string");
145
3.12k
            return true;
146
3.67k
        }
147
639
        if (i_str[nPos] == 'Z' || i_str[nPos] == '+' || i_str[nPos] == '-')
148
57
        {
149
57
            --nPos; // we don't want to skip the tz separator
150
57
            return true;
151
57
        }
152
582
        else
153
582
            return false;
154
639
    }
155
    bool getISO8601TimeToken(std::u16string_view i_str, std::size_t &io_index, OUString &o_strInt, bool &o_bFraction, OUString &o_strFrac)
156
13.6k
    {
157
13.6k
        OUString resInt;
158
13.6k
        OUString resFrac;
159
13.6k
        bool bFraction = false;
160
13.6k
        std::size_t index = io_index;
161
13.6k
        if(!impl_getISO8601TimeToken(i_str, index, resInt, bFraction, resFrac))
162
1.18k
            return false;
163
12.4k
        else
164
12.4k
        {
165
12.4k
            io_index = index+1;
166
12.4k
            o_strInt = resInt;
167
12.4k
            o_strFrac = resFrac;
168
12.4k
            o_bFraction = bFraction;
169
12.4k
            return true;
170
12.4k
        }
171
13.6k
    }
172
    bool getISO8601TimeZoneToken(std::u16string_view i_str, std::size_t &io_index, OUString &o_strInt)
173
158
    {
174
158
        const sal_Unicode c0 = '0';
175
158
        const sal_Unicode c9 = '9';
176
158
        const sal_Unicode sep = ':';
177
158
        if (i_str[io_index] == 'Z') // UTC timezone indicator
178
6
        {
179
6
            ++io_index;
180
6
            o_strInt = "Z";
181
6
            return true;
182
6
        }
183
152
        else if (i_str[io_index] == '+' || i_str[io_index] == '-') // other timezones indicator
184
142
        {
185
142
            ++io_index;
186
142
            o_strInt.clear();
187
392
            for (; io_index < i_str.size(); ++io_index)
188
318
            {
189
318
                const sal_Unicode c = i_str[io_index];
190
318
                if ((c < c0 || c > c9) && c != sep)
191
68
                    return false;
192
250
                o_strInt += OUStringChar(c);
193
250
            }
194
74
            return true;
195
142
        }
196
10
        else
197
10
            return false;
198
158
    }
199
}
200
201
namespace utl
202
{
203
const LocaleDataWrapper& GetLocaleData()
204
0
{
205
0
    static SvtSysLocale ourSysLocale;
206
0
    return ourSysLocale.GetLocaleData();
207
0
}
208
209
0
DateTime GetDateTime(const css::util::DateTime& _rDT) { return DateTime(_rDT); }
210
211
OUString GetDateTimeString(const css::util::DateTime& _rDT)
212
0
{
213
    // String with date and time information (#i20172#)
214
0
    DateTime aDT(GetDateTime(_rDT));
215
0
    const LocaleDataWrapper& rLoDa = GetLocaleData();
216
217
0
    return rLoDa.getDate(aDT) + " " + rLoDa.getTime(aDT);
218
0
}
219
220
OUString GetDateTimeString(sal_Int32 _nDate, sal_Int32 _nTime)
221
0
{
222
0
    const LocaleDataWrapper& rLoDa = GetLocaleData();
223
224
0
    Date aDate(_nDate);
225
0
    tools::Time aTime(tools::Time::fromEncodedTime(_nTime * tools::Time::nanoPerCenti));
226
0
    return rLoDa.getDate(aDate) + ", " + rLoDa.getTime(aTime);
227
0
}
228
229
OUString GetDateString(const css::util::DateTime& _rDT)
230
0
{
231
0
    return GetLocaleData().getDate(GetDateTime(_rDT));
232
0
}
233
234
void typeConvert(const Date& _rDate, css::util::Date& _rOut)
235
0
{
236
0
    _rOut.Day = _rDate.GetDay();
237
0
    _rOut.Month = _rDate.GetMonth();
238
0
    _rOut.Year = _rDate.GetYear();
239
0
}
240
241
void typeConvert(const css::util::Date& _rDate, Date& _rOut)
242
0
{
243
0
    _rOut = Date(_rDate.Day, _rDate.Month, _rDate.Year);
244
0
}
245
246
void typeConvert(const DateTime& _rDateTime, css::util::DateTime& _rOut)
247
0
{
248
0
    _rOut.Year = _rDateTime.GetYear();
249
0
    _rOut.Month = _rDateTime.GetMonth();
250
0
    _rOut.Day = _rDateTime.GetDay();
251
0
    _rOut.Hours = _rDateTime.GetHour();
252
0
    _rOut.Minutes = _rDateTime.GetMin();
253
0
    _rOut.Seconds = _rDateTime.GetSec();
254
0
    _rOut.NanoSeconds = _rDateTime.GetNanoSec();
255
0
}
256
257
void typeConvert(const css::util::DateTime& _rDateTime, DateTime& _rOut)
258
0
{
259
0
    Date aDate(_rDateTime.Day, _rDateTime.Month, _rDateTime.Year);
260
0
    tools::Time aTime(_rDateTime.Hours, _rDateTime.Minutes, _rDateTime.Seconds, _rDateTime.NanoSeconds);
261
0
    _rOut = DateTime(aDate, aTime);
262
0
}
263
264
OUString toISO8601(const css::util::DateTime& rDateTime)
265
5.43k
{
266
5.43k
    OUStringBuffer rBuffer(32);
267
5.43k
    rBuffer.append(OUString::number(static_cast<sal_Int32>(rDateTime.Year)) + "-");
268
5.43k
    if( rDateTime.Month < 10 )
269
5.43k
        rBuffer.append('0');
270
5.43k
    rBuffer.append(OUString::number(static_cast<sal_Int32>(rDateTime.Month)) + "-");
271
5.43k
    if( rDateTime.Day < 10 )
272
0
        rBuffer.append('0');
273
5.43k
    rBuffer.append(static_cast<sal_Int32>(rDateTime.Day));
274
275
5.43k
    if( rDateTime.NanoSeconds != 0 ||
276
0
        rDateTime.Seconds     != 0 ||
277
0
        rDateTime.Minutes     != 0 ||
278
0
        rDateTime.Hours       != 0 )
279
5.43k
    {
280
5.43k
        rBuffer.append('T');
281
5.43k
        if( rDateTime.Hours < 10 )
282
0
            rBuffer.append('0');
283
5.43k
        rBuffer.append(OUString::number(static_cast<sal_Int32>(rDateTime.Hours)) + ":");
284
5.43k
        if( rDateTime.Minutes < 10 )
285
0
            rBuffer.append('0');
286
5.43k
        rBuffer.append(OUString::number(static_cast<sal_Int32>(rDateTime.Minutes)) + ":");
287
5.43k
        if( rDateTime.Seconds < 10 )
288
137
            rBuffer.append('0');
289
5.43k
        rBuffer.append(static_cast<sal_Int32>(rDateTime.Seconds));
290
5.43k
        if ( rDateTime.NanoSeconds > 0)
291
5.43k
        {
292
5.43k
            OSL_ENSURE(rDateTime.NanoSeconds < 1000000000,"NanoSeconds cannot be more than 999 999 999");
293
5.43k
            rBuffer.append(',');
294
5.43k
            std::ostringstream ostr;
295
5.43k
            ostr.fill('0');
296
5.43k
            ostr.width(9);
297
5.43k
            ostr << rDateTime.NanoSeconds;
298
5.43k
            rBuffer.appendAscii(ostr.str().c_str());
299
5.43k
        }
300
5.43k
    }
301
5.43k
    return rBuffer.makeStringAndClear();
302
5.43k
}
303
304
/** convert ISO8601 DateTime String to util::DateTime */
305
bool ISO8601parseDateTime(std::u16string_view rString, css::util::DateTime& rDateTime)
306
7.14k
{
307
7.14k
    bool bSuccess = true;
308
309
7.14k
    std::u16string_view aDateStr, aTimeStr;
310
7.14k
    css::util::Date aDate;
311
7.14k
    css::util::Time aTime;
312
7.14k
    size_t nPos = rString.find( 'T' );
313
7.14k
    if ( nPos != std::u16string_view::npos )
314
5.36k
    {
315
5.36k
        aDateStr = rString.substr( 0, nPos );
316
5.36k
        aTimeStr = rString.substr( nPos + 1 );
317
5.36k
    }
318
1.78k
    else
319
1.78k
        aDateStr = rString;         // no separator: only date part
320
321
7.14k
    bSuccess = ISO8601parseDate(aDateStr, aDate);
322
323
7.14k
    if ( bSuccess && !aTimeStr.empty() )           // time is optional
324
4.96k
    {
325
4.96k
        bSuccess = ISO8601parseTime(aTimeStr, aTime);
326
4.96k
    }
327
328
7.14k
    if (bSuccess)
329
3.75k
    {
330
3.75k
        rDateTime = css::util::DateTime(aTime.NanoSeconds, aTime.Seconds, aTime.Minutes, aTime.Hours,
331
3.75k
               aDate.Day, aDate.Month, aDate.Year, false);
332
3.75k
    }
333
334
7.14k
    return bSuccess;
335
7.14k
}
336
337
/** convert ISO8601 Date String to util::Date */
338
// TODO: supports only calendar dates YYYY-MM-DD
339
// MISSING: calendar dates YYYYMMDD YYYY-MM
340
//          year, week date, ordinal date
341
bool ISO8601parseDate(std::u16string_view aDateStr, css::util::Date& rDate)
342
7.14k
{
343
7.14k
    const sal_Int32 nDateTokens {comphelper::string::getTokenCount(aDateStr, '-')};
344
345
7.14k
    if (nDateTokens<1 || nDateTokens>3)
346
218
        return false;
347
348
6.92k
    sal_Int32 nYear    = 1899;
349
6.92k
    sal_Int32 nMonth   = 12;
350
6.92k
    sal_Int32 nDay     = 30;
351
352
6.92k
    sal_Int32 nIdx {0};
353
6.92k
    auto strCurrentToken = o3tl::getToken(aDateStr, 0, '-', nIdx );
354
6.92k
    if ( !convertNumber32( nYear, strCurrentToken, 0, 9999 ) )
355
1.73k
        return false;
356
5.19k
    if ( nDateTokens >= 2 )
357
5.15k
    {
358
5.15k
        strCurrentToken = o3tl::getToken(aDateStr, 0, '-', nIdx );
359
5.15k
        if (strCurrentToken.size() > 2)
360
94
            return false;
361
5.05k
        if ( !convertNumber32( nMonth, strCurrentToken, 0, 12 ) )
362
13
            return false;
363
5.05k
    }
364
5.08k
    if ( nDateTokens >= 3 )
365
1.74k
    {
366
1.74k
        strCurrentToken = o3tl::getToken(aDateStr, 0, '-', nIdx );
367
1.74k
        if (strCurrentToken.size() > 2)
368
30
            return false;
369
1.71k
        if ( !convertNumber32( nDay, strCurrentToken, 0, 31 ) )
370
47
            return false;
371
1.71k
    }
372
373
5.01k
    rDate.Year = static_cast<sal_uInt16>(nYear);
374
5.01k
    rDate.Month = static_cast<sal_uInt16>(nMonth);
375
5.01k
    rDate.Day = static_cast<sal_uInt16>(nDay);
376
377
5.01k
    return true;
378
5.08k
}
379
380
/** convert ISO8601 Time String to util::Time */
381
bool ISO8601parseTime(std::u16string_view aTimeStr, css::util::Time& rTime)
382
4.96k
{
383
4.96k
    sal_Int32 nHour    = 0;
384
4.96k
    sal_Int32 nMin     = 0;
385
4.96k
    sal_Int32 nSec     = 0;
386
4.96k
    sal_Int32 nNanoSec = 0;
387
388
4.96k
    std::size_t n = 0;
389
4.96k
    OUString tokInt;
390
4.96k
    OUString tokFrac;
391
4.96k
    OUString tokTz;
392
4.96k
    bool bFrac = false;
393
    // hours
394
4.96k
    bool bSuccess = getISO8601TimeToken(aTimeStr, n, tokInt, bFrac, tokFrac);
395
4.96k
    if (!bSuccess)
396
139
        return false;
397
398
4.82k
    if ( bFrac && n < aTimeStr.size())
399
0
    {
400
        // is it junk or the timezone?
401
0
        bSuccess = getISO8601TimeZoneToken(aTimeStr, n, tokTz);
402
0
        if (!bSuccess)
403
0
            return false;
404
0
    }
405
4.82k
    bSuccess = convertNumber32( nHour, tokInt, 0, 23 );
406
4.82k
    if (!bSuccess)
407
0
        return false;
408
409
4.82k
    if (bFrac)
410
6
    {
411
6
        sal_Int64 fracNumerator;
412
6
        bSuccess = convertNumber64(fracNumerator, tokFrac);
413
6
        if ( bSuccess )
414
6
        {
415
6
            double frac = static_cast<double>(fracNumerator) / pow(static_cast<double>(10), static_cast<double>(tokFrac.getLength()));
416
             // minutes
417
6
            OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac hours (of hours) not between 0 and 1");
418
6
            frac *= 60;
419
6
            nMin = floor(frac);
420
6
            frac -=  nMin;
421
            // seconds
422
6
            OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac minutes (of hours) not between 0 and 1");
423
6
            frac *= 60;
424
6
            nSec = floor(frac);
425
6
            frac -=  nSec;
426
            // nanoseconds
427
6
            OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac seconds (of hours) not between 0 and 1");
428
6
            frac *= 1000000000;
429
6
            nNanoSec = ::rtl::math::round(frac);
430
6
        }
431
6
        goto end;
432
6
    }
433
4.82k
    if(n >= aTimeStr.size())
434
374
        goto end;
435
436
    // minutes
437
4.44k
    bSuccess = getISO8601TimeToken(aTimeStr, n, tokInt, bFrac, tokFrac);
438
4.44k
    if (!bSuccess)
439
115
        return false;
440
4.33k
    if ( bFrac && n < aTimeStr.size())
441
0
    {
442
        // is it junk or the timezone?
443
0
        bSuccess = getISO8601TimeZoneToken(aTimeStr, n, tokTz);
444
0
        if (!bSuccess)
445
0
            return false;
446
0
    }
447
4.33k
    bSuccess = convertNumber32( nMin, tokInt, 0, 59 );
448
4.33k
    if (!bSuccess)
449
0
        return false;
450
4.33k
    if (bFrac)
451
3
    {
452
3
        sal_Int64 fracNumerator;
453
3
        bSuccess = convertNumber64(fracNumerator, tokFrac);
454
3
        if ( bSuccess )
455
3
        {
456
3
            double frac = static_cast<double>(fracNumerator) / pow(static_cast<double>(10), static_cast<double>(tokFrac.getLength()));
457
            // seconds
458
3
            OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac minutes (of minutes) not between 0 and 1");
459
3
            frac *= 60;
460
3
            nSec = floor(frac);
461
3
            frac -=  nSec;
462
            // nanoseconds
463
3
            OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac seconds (of minutes) not between 0 and 1");
464
3
            frac *= 1000000000;
465
3
            nNanoSec = ::rtl::math::round(frac);
466
3
        }
467
3
        goto end;
468
3
    }
469
4.32k
    if(n >= aTimeStr.size())
470
68
        goto end;
471
472
    // seconds
473
4.26k
    bSuccess = getISO8601TimeToken(aTimeStr, n, tokInt, bFrac, tokFrac);
474
4.26k
    if (!bSuccess)
475
928
        return false;
476
3.33k
    if (n < aTimeStr.size())
477
158
    {
478
        // is it junk or the timezone?
479
158
        bSuccess = getISO8601TimeZoneToken(aTimeStr, n, tokTz);
480
158
        if (!bSuccess)
481
78
            return false;
482
158
    }
483
    // max 60 for leap seconds
484
3.25k
    bSuccess = convertNumber32( nSec, tokInt, 0, 60 );
485
3.25k
    if (!bSuccess)
486
0
        return false;
487
3.25k
    if (bFrac)
488
3.19k
    {
489
3.19k
        sal_Int64 fracNumerator;
490
3.19k
        bSuccess = convertNumber64(fracNumerator, tokFrac);
491
3.19k
        if ( bSuccess )
492
3.19k
        {
493
3.19k
            double frac = static_cast<double>(fracNumerator) / pow(static_cast<double>(10), static_cast<double>(tokFrac.getLength()));
494
            // nanoseconds
495
3.19k
            OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac seconds (of seconds) not between 0 and 1");
496
3.19k
            frac *= 1000000000;
497
3.19k
            nNanoSec = ::rtl::math::round(frac);
498
3.19k
        }
499
3.19k
        goto end;
500
3.19k
    }
501
502
3.70k
    end:
503
3.70k
    if (bSuccess)
504
3.70k
    {
505
        // normalise time
506
3.70k
        const int secondsOverflow = (nSec == 60) ? 61 : 60;
507
3.70k
        if (nNanoSec == 1000000000)
508
0
        {
509
0
            nNanoSec = 0;
510
0
            ++nSec;
511
0
        }
512
3.70k
        if(nSec == secondsOverflow)
513
0
        {
514
0
            nSec = 0;
515
0
            ++nMin;
516
0
        }
517
3.70k
        if(nMin == 60)
518
0
        {
519
0
            nMin = 0;
520
0
            ++nHour;
521
0
        }
522
3.70k
        if(!tokTz.isEmpty())
523
76
            rTime.IsUTC = (tokTz == "Z");
524
525
3.70k
        rTime.Hours = static_cast<sal_uInt16>(nHour);
526
3.70k
        rTime.Minutes = static_cast<sal_uInt16>(nMin);
527
3.70k
        rTime.Seconds = static_cast<sal_uInt16>(nSec);
528
3.70k
        rTime.NanoSeconds = nNanoSec;
529
3.70k
    }
530
531
3.70k
    return bSuccess;
532
3.25k
}
533
534
}   // namespace utl
535
536
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */