Branch data TLA Line data Source code
1 : : // Copyright (c) 2014-2022 Thomas Fussell
2 : : // Copyright (c) 2024-2025 xlnt-community
3 : : //
4 : : // Permission is hereby granted, free of charge, to any person obtaining a copy
5 : : // of this software and associated documentation files (the "Software"), to deal
6 : : // in the Software without restriction, including without limitation the rights
7 : : // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : : // copies of the Software, and to permit persons to whom the Software is
9 : : // furnished to do so, subject to the following conditions:
10 : : //
11 : : // The above copyright notice and this permission notice shall be included in
12 : : // all copies or substantial portions of the Software.
13 : : //
14 : : // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : : // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : : // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : : // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : : // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : : // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : : // THE SOFTWARE
21 : : //
22 : : // @license: http://www.opensource.org/licenses/mit-license.php
23 : : // @author: see AUTHORS file
24 : : #include <cmath>
25 : : #include <ctime>
26 : :
27 : : #include <xlnt/utils/date.hpp>
28 : : #include <xlnt/utils/datetime.hpp>
29 : : #include <xlnt/utils/time.hpp>
30 : : #include <detail/serialization/parsers.hpp>
31 : :
32 : : #include <xlnt/utils/optional.hpp>
33 : :
34 : : namespace {
35 : :
36 :CBC 2674 : std::string fill(const std::string &string, std::size_t length = 2)
37 : : {
38 [ + + ]: 2674 : if (string.size() >= length)
39 : : {
40 : 1604 : return string;
41 : : }
42 : :
43 [ + + ]: 2140 : return std::string(length - string.size(), '0') + string;
44 : : }
45 : :
46 : : } // namespace
47 : :
48 : : namespace xlnt {
49 : :
50 : 47 : datetime datetime::from_number(double raw_time, calendar base_date)
51 : : {
52 [ + ]: 47 : auto date_part = date::from_number(static_cast<int>(raw_time), base_date);
53 [ + ]: 47 : auto time_part = time::from_number(raw_time);
54 : :
55 [ + ]: 94 : return datetime(date_part, time_part);
56 : : }
57 : :
58 : 1 : bool datetime::operator==(const datetime &comparand) const
59 : : {
60 : 1 : return year == comparand.year
61 [ + - ]: 1 : && month == comparand.month
62 [ + - ]: 1 : && day == comparand.day
63 [ + - ]: 1 : && hour == comparand.hour
64 [ + - ]: 1 : && minute == comparand.minute
65 [ + - ]: 1 : && second == comparand.second
66 [ + - ]: 1 : && microsecond == comparand.microsecond
67 [ + - + - ]: 2 : && _is_null == comparand._is_null;
68 : : }
69 : :
70 :UBC 0 : bool datetime::operator!=(const datetime &comparand) const
71 : : {
72 : 0 : return !(*this == comparand);
73 : : }
74 : :
75 :CBC 8 : double datetime::to_number(calendar base_date) const
76 : : {
77 [ + + ]: 8 : if (_is_null)
78 : : {
79 [ + ]: 1 : throw xlnt::invalid_attribute("cannot convert invalid/empty datetime to a number");
80 : : }
81 : :
82 [ + + ]: 7 : return date(year, month, day).to_number(base_date)
83 [ + + ]: 7 : + time(hour, minute, second, microsecond).to_number();
84 : : }
85 : :
86 : 3 : std::string datetime::to_string() const
87 : : {
88 [ + + ]: 3 : if (_is_null)
89 : : {
90 : 1 : return {};
91 : : }
92 : : else
93 : : {
94 : 2 : std::string str = std::to_string(year);
95 [ + ]: 2 : str.push_back('/');
96 [ + ]: 2 : str.append(std::to_string(month));
97 [ + ]: 2 : str.push_back('/');
98 [ + ]: 2 : str.append(std::to_string(day));
99 [ + ]: 2 : str.push_back(' ');
100 [ + ]: 2 : str.append(std::to_string(hour));
101 [ + ]: 2 : str.push_back(':');
102 [ + ]: 2 : str.append(std::to_string(minute));
103 [ + ]: 2 : str.push_back(':');
104 [ + ]: 2 : str.append(std::to_string(second));
105 : :
106 [ + - ]: 2 : if (microsecond != 0)
107 : : {
108 [ + ]: 2 : str.push_back('.');
109 [ + + ]: 2 : str.append(fill(std::to_string(microsecond), 6));
110 : : }
111 : :
112 : 2 : return str;
113 : 2 : }
114 : : }
115 : :
116 :UBC 0 : datetime datetime::now()
117 : : {
118 [ # # # ]: 0 : return datetime(date::today(), time::now());
119 : : }
120 : :
121 :CBC 3 : datetime datetime::today()
122 : : {
123 [ + + + ]: 3 : return datetime(date::today(), time(0, 0, 0, 0));
124 : : }
125 : :
126 : 723 : datetime::datetime(int year_, int month_, int day_, int hour_, int minute_, int second_, int microsecond_)
127 : 723 : : year(year_), month(month_), day(day_), hour(hour_), minute(minute_), second(second_), microsecond(microsecond_), _is_null(false)
128 : : {
129 : 723 : }
130 : :
131 : 50 : datetime::datetime(const date &d, const time &t)
132 : 50 : : hour(t.hour),
133 : 50 : minute(t.minute),
134 : 50 : second(t.second),
135 : 50 : microsecond(t.microsecond),
136 : 50 : _is_null(d.is_null())
137 : : {
138 [ + - ]: 50 : if (!d.is_null())
139 : : {
140 : 50 : year = d.get_year();
141 : 50 : month = d.get_month();
142 : 50 : day = d.get_day();
143 : : }
144 : 50 : }
145 : :
146 : 3 : int datetime::weekday() const
147 : : {
148 [ + + ]: 3 : if (!_is_null)
149 : : {
150 [ + + ]: 2 : return date(year, month, day).weekday();
151 : : }
152 : : else
153 : : {
154 : 1 : return -1;
155 : : }
156 : : }
157 : :
158 : 39 : int datetime::get_year() const
159 : : {
160 [ + + ]: 39 : if (_is_null)
161 : : {
162 [ + ]: 1 : throw xlnt::invalid_attribute("access to invalid/empty year of xlnt::datetime");
163 : : }
164 : :
165 : 38 : return year;
166 : : }
167 : :
168 : 35 : int datetime::get_month() const
169 : : {
170 [ + + ]: 35 : if (_is_null)
171 : : {
172 [ + ]: 1 : throw xlnt::invalid_attribute("access to invalid/empty month of xlnt::datetime");
173 : : }
174 : :
175 : 34 : return month;
176 : : }
177 : :
178 : 30 : int datetime::get_day() const
179 : : {
180 [ + + ]: 30 : if (_is_null)
181 : : {
182 [ + ]: 1 : throw xlnt::invalid_attribute("access to invalid/empty day of xlnt::datetime");
183 : : }
184 : :
185 : 29 : return day;
186 : : }
187 : :
188 : 77 : int datetime::get_hour() const
189 : : {
190 [ + + ]: 77 : if (_is_null)
191 : : {
192 [ + ]: 1 : throw xlnt::invalid_attribute("access to invalid/empty hour of xlnt::datetime");
193 : : }
194 : :
195 : 76 : return hour;
196 : : }
197 : :
198 : 48 : int datetime::get_minute() const
199 : : {
200 [ + + ]: 48 : if (_is_null)
201 : : {
202 [ + ]: 1 : throw xlnt::invalid_attribute("access to invalid/empty minute of xlnt::datetime");
203 : : }
204 : :
205 : 47 : return minute;
206 : : }
207 : :
208 : 39 : int datetime::get_second() const
209 : : {
210 [ + + ]: 39 : if (_is_null)
211 : : {
212 [ + ]: 1 : throw xlnt::invalid_attribute("access to invalid/empty second of xlnt::datetime");
213 : : }
214 : :
215 : 38 : return second;
216 : : }
217 : :
218 : 35 : int datetime::get_microsecond() const
219 : : {
220 [ + + ]: 35 : if (_is_null)
221 : : {
222 [ + ]: 1 : throw xlnt::invalid_attribute("access to invalid/empty microsecond of xlnt::datetime");
223 : : }
224 : :
225 : 34 : return microsecond;
226 : : }
227 : :
228 : 28 : datetime datetime::from_iso_string(const std::string &string)
229 : : {
230 : 28 : xlnt::datetime result(1900, 1, 1);
231 : :
232 : 28 : bool ok = true;
233 : 28 : auto next_separator_index = string.find('-');
234 [ + - + + : 28 : ok = ok && detail::parse(string.substr(0, next_separator_index), result.year) == std::errc();
+ + + - -
- ]
235 : 28 : auto previous_separator_index = next_separator_index;
236 [ + + ]: 28 : next_separator_index = ok ? string.find('-', previous_separator_index + 1) : next_separator_index;
237 [ + + + + : 28 : ok = ok && detail::parse(string.substr(previous_separator_index + 1, next_separator_index), result.month) == std::errc();
+ - + + -
- ]
238 : 28 : previous_separator_index = next_separator_index;
239 [ + + ]: 28 : next_separator_index = ok ? string.find('T', previous_separator_index + 1) : next_separator_index;
240 [ + + + + : 28 : ok = ok && detail::parse(string.substr(previous_separator_index + 1, next_separator_index), result.day) == std::errc();
+ - + + -
- ]
241 : 28 : previous_separator_index = next_separator_index;
242 [ + + ]: 28 : next_separator_index = ok ? string.find(':', previous_separator_index + 1) : next_separator_index;
243 [ + + + + : 28 : ok = ok && detail::parse(string.substr(previous_separator_index + 1, next_separator_index), result.hour) == std::errc();
+ - + + -
- ]
244 : 28 : previous_separator_index = next_separator_index;
245 [ + + ]: 28 : next_separator_index = ok ? string.find(':', previous_separator_index + 1) : next_separator_index;
246 [ + + + + : 28 : ok = ok && detail::parse(string.substr(previous_separator_index + 1, next_separator_index), result.minute) == std::errc();
+ - + + -
- ]
247 : 28 : previous_separator_index = next_separator_index;
248 [ + + ]: 28 : next_separator_index = ok ? string.find('.', previous_separator_index + 1) : next_separator_index;
249 : 28 : bool subseconds_available = next_separator_index != std::string::npos;
250 [ + + ]: 28 : if (subseconds_available)
251 : : {
252 : : // First parse the seconds.
253 [ + - + + : 6 : ok = ok && detail::parse(string.substr(previous_separator_index + 1, next_separator_index), result.second) == std::errc();
+ - + - -
- ]
254 : 6 : previous_separator_index = next_separator_index;
255 : :
256 : : }
257 [ + + ]: 28 : next_separator_index = ok ? string.find('Z', previous_separator_index + 1) : next_separator_index;
258 : 28 : size_t num_characters_parsed = 0;
259 [ + + + + : 28 : ok = ok && detail::parse(string.substr(previous_separator_index + 1, next_separator_index), subseconds_available ? result.microsecond : result.second, &num_characters_parsed) == std::errc();
+ + + - +
+ - - ]
260 : :
261 [ + + ]: 28 : if (subseconds_available)
262 : : {
263 : 6 : constexpr size_t expected_digits = 6; // microseconds have 6 digits
264 : 6 : size_t actual_digits = num_characters_parsed;
265 : :
266 [ + + ]: 12 : while (actual_digits > expected_digits)
267 : : {
268 : 6 : result.microsecond /= 10;
269 : 6 : --actual_digits;
270 : : }
271 : :
272 [ + + ]: 12 : while (actual_digits < expected_digits)
273 : : {
274 : 6 : result.microsecond *= 10;
275 : 6 : ++actual_digits;
276 : : }
277 : : }
278 : :
279 [ + + ]: 28 : if (!ok)
280 : : {
281 [ + ]: 1 : throw xlnt::invalid_parameter("invalid ISO date");
282 : : }
283 : :
284 : 54 : return result;
285 : : }
286 : :
287 : 535 : std::string datetime::to_iso_string() const
288 : : {
289 [ + + ]: 535 : if (_is_null)
290 : : {
291 : 1 : return {};
292 : : }
293 : : else
294 : : {
295 : 534 : std::string iso = std::to_string(year);
296 [ + ]: 534 : iso.push_back('-');
297 [ + + ]: 534 : iso.append(fill(std::to_string(month)));
298 [ + ]: 534 : iso.push_back('-');
299 [ + + ]: 534 : iso.append(fill(std::to_string(day)));
300 [ + ]: 534 : iso.push_back('T');
301 [ + + ]: 534 : iso.append(fill(std::to_string(hour)));
302 [ + ]: 534 : iso.push_back(':');
303 [ + + ]: 534 : iso.append(fill(std::to_string(minute)));
304 [ + ]: 534 : iso.push_back(':');
305 [ + + ]: 534 : iso.append(fill(std::to_string(second)));
306 : :
307 [ + + ]: 534 : if (microsecond != 0)
308 : : {
309 [ + ]: 2 : iso.push_back('.');
310 [ + + ]: 2 : iso.append(fill(std::to_string(microsecond), 6));
311 : : }
312 : :
313 [ + ]: 534 : iso.push_back('Z');
314 : :
315 : 534 : return iso;
316 : 534 : }
317 : : }
318 : :
319 : : } // namespace xlnt
|