Coverage Report

Created: 2026-06-30 11:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/comphelper/source/misc/date.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
 * 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 <comphelper/date.hxx>
21
22
namespace comphelper::date
23
{
24
// Once upon a time the number of days we internally handled in tools' class
25
// Date was limited to MAX_DAYS 3636532. That changed with a full 16-bit year.
26
// Assuming the first valid positive date in a proleptic Gregorian calendar is
27
// 0001-01-01, this resulted in an end date of 9957-06-26.
28
// Hence we documented that years up to and including 9956 are handled.
29
/* XXX: it is unclear history why this value was chosen, the representable
30
 * 9999-12-31 would be 3652060 days from 0001-01-01. Even 9998-12-31 to
31
 * distinguish from a maximum possible date would be 3651695.
32
 * There is connectivity/source/commontools/dbconversion.cxx that still has the
33
 * same value to calculate with css::util::Date */
34
/* XXX can that dbconversion cope with years > 9999 or negative years at all?
35
 * Database fields may be limited to positive 4 digits. */
36
37
constexpr sal_Int16 kYearMax = SAL_MAX_INT16;
38
constexpr sal_Int16 kYearMin = SAL_MIN_INT16;
39
40
constexpr sal_Int32 MIN_DAYS = convertDateToDays(1, 1, kYearMin); // -32768-01-01
41
static_assert(MIN_DAYS == -11968265);
42
constexpr sal_Int32 MAX_DAYS = convertDateToDays(31, 12, kYearMax); // 32767-12-31
43
static_assert(MAX_DAYS == 11967900);
44
45
constexpr sal_Int32 nNullDateDays = convertDateToDays(30, 12, 1899);
46
static_assert(nNullDateDays == 693594);
47
48
sal_Int32 convertDateToDaysNormalizing(sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear)
49
94.6k
{
50
    // Speed-up the common null-date 1899-12-30.
51
94.6k
    if (nYear == 1899 && nMonth == 12 && nDay == 30)
52
80.5k
        return nNullDateDays;
53
54
14.1k
    normalize(nDay, nMonth, nYear);
55
14.1k
    return convertDateToDays(nDay, nMonth, nYear);
56
94.6k
}
57
58
bool isValidDate(sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear)
59
1.20M
{
60
1.20M
    if (nYear == 0)
61
12.3k
        return false;
62
1.19M
    if (nMonth < 1 || 12 < nMonth)
63
26.2k
        return false;
64
1.16M
    if (nDay < 1 || (nDay > comphelper::date::getDaysInMonth(nMonth, nYear)))
65
4.83k
        return false;
66
1.16M
    return true;
67
1.16M
}
68
69
void convertDaysToDate(sal_Int32 nDays, sal_uInt16& rDay, sal_uInt16& rMonth, sal_Int16& rYear)
70
231k
{
71
231k
    if (nDays <= MIN_DAYS)
72
765
    {
73
765
        rDay = 1;
74
765
        rMonth = 1;
75
765
        rYear = kYearMin;
76
765
        return;
77
765
    }
78
230k
    if (nDays >= MAX_DAYS)
79
393
    {
80
393
        rDay = 31;
81
393
        rMonth = 12;
82
393
        rYear = kYearMax;
83
393
        return;
84
393
    }
85
86
    // Day 0 is -0001-12-31, day 1 is 0001-01-01
87
230k
    const sal_Int16 nSign = (nDays <= 0 ? -1 : 1);
88
230k
    sal_Int32 nTempDays;
89
230k
    sal_Int32 i = 0;
90
230k
    bool bCalc;
91
92
230k
    do
93
11.2M
    {
94
11.2M
        rYear = static_cast<sal_Int16>((nDays / 365) - (i * nSign));
95
11.2M
        if (rYear == 0)
96
166
            rYear = nSign;
97
11.2M
        nTempDays = nDays - YearToDays(rYear);
98
11.2M
        bCalc = false;
99
11.2M
        if (nTempDays < 1)
100
169k
        {
101
169k
            i += nSign;
102
169k
            bCalc = true;
103
169k
        }
104
11.1M
        else
105
11.1M
        {
106
11.1M
            if (nTempDays > 365)
107
10.8M
            {
108
10.8M
                if ((nTempDays != 366) || !isLeapYear(rYear))
109
10.8M
                {
110
10.8M
                    i -= nSign;
111
10.8M
                    bCalc = true;
112
10.8M
                }
113
10.8M
            }
114
11.1M
        }
115
11.2M
    } while (bCalc);
116
117
230k
    rMonth = 1;
118
1.78M
    while (nTempDays > getDaysInMonth(rMonth, rYear))
119
1.55M
    {
120
1.55M
        nTempDays -= getDaysInMonth(rMonth, rYear);
121
1.55M
        ++rMonth;
122
1.55M
    }
123
124
230k
    rDay = static_cast<sal_uInt16>(nTempDays);
125
230k
}
126
127
bool normalize(sal_uInt16& rDay, sal_uInt16& rMonth, sal_Int16& rYear)
128
825k
{
129
825k
    if (isValidDate(rDay, rMonth, rYear))
130
782k
        return false;
131
132
43.4k
    if (rDay == 0 && rMonth == 0 && rYear == 0)
133
6.77k
        return false; // empty date
134
135
36.7k
    if (rDay == 0)
136
22.8k
    {
137
22.8k
        if (rMonth == 0)
138
14.7k
            ; // nothing, handled below
139
8.03k
        else
140
8.03k
            --rMonth;
141
        // Last day of month is determined at the end.
142
22.8k
    }
143
144
36.7k
    if (rMonth > 12)
145
11.0k
    {
146
11.0k
        rYear += rMonth / 12;
147
11.0k
        rMonth = rMonth % 12;
148
11.0k
        if (rYear == 0)
149
19
            rYear = 1;
150
11.0k
    }
151
36.7k
    if (rMonth == 0)
152
19.7k
    {
153
19.7k
        --rYear;
154
19.7k
        if (rYear == 0)
155
1.59k
            rYear = -1;
156
19.7k
        rMonth = 12;
157
19.7k
    }
158
159
36.7k
    if (rYear < 0)
160
7.70k
    {
161
7.70k
        sal_uInt16 nDays;
162
2.00M
        while (rDay > (nDays = getDaysInMonth(rMonth, rYear)))
163
1.99M
        {
164
1.99M
            rDay -= nDays;
165
1.99M
            if (rMonth > 1)
166
1.82M
                --rMonth;
167
165k
            else
168
165k
            {
169
165k
                if (rYear == kYearMin)
170
16
                {
171
16
                    rDay = 1;
172
16
                    rMonth = 1;
173
16
                    return true;
174
16
                }
175
165k
                --rYear;
176
165k
                rMonth = 12;
177
165k
            }
178
1.99M
        }
179
7.70k
    }
180
29.0k
    else
181
29.0k
    {
182
29.0k
        sal_uInt16 nDays;
183
1.08M
        while (rDay > (nDays = getDaysInMonth(rMonth, rYear)))
184
1.05M
        {
185
1.05M
            rDay -= nDays;
186
1.05M
            if (rMonth < 12)
187
965k
                ++rMonth;
188
88.5k
            else
189
88.5k
            {
190
88.5k
                if (rYear == kYearMax)
191
343
                {
192
343
                    rDay = 31;
193
343
                    rMonth = 12;
194
343
                    return true;
195
343
                }
196
88.2k
                ++rYear;
197
88.2k
                rMonth = 1;
198
88.2k
            }
199
1.05M
        }
200
29.0k
    }
201
202
36.3k
    if (rDay == 0)
203
22.8k
        rDay = getDaysInMonth(rMonth, rYear);
204
205
36.3k
    return true;
206
36.7k
}
207
208
} // namespace comphelper::date
209
210
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */