/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: */ |