Branch data Line data Source code
1 : : /********************************************************************\
2 : : * gnc-datetime.cpp -- Date and Time classes for GnuCash *
3 : : * *
4 : : * Copyright 2015 John Ralls <jralls@ceridwen.us> *
5 : : * *
6 : : * This program is free software; you can redistribute it and/or *
7 : : * modify it under the terms of the GNU General Public License as *
8 : : * published by the Free Software Foundation; either version 2 of *
9 : : * the License, or (at your option) any later version. *
10 : : * *
11 : : * This program is distributed in the hope that it will be useful, *
12 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 : : * GNU General Public License for more details. *
15 : : * *
16 : : * You should have received a copy of the GNU General Public License*
17 : : * along with this program; if not, contact: *
18 : : * *
19 : : * Free Software Foundation Voice: +1-617-542-5942 *
20 : : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21 : : * Boston, MA 02110-1301, USA gnu@gnu.org *
22 : : * *
23 : : \********************************************************************/
24 : :
25 : : #include <config.h>
26 : : #include "platform.h"
27 : : #include <boost/date_time/gregorian/gregorian.hpp>
28 : : #include <boost/date_time/posix_time/posix_time.hpp>
29 : : #include <boost/date_time/local_time/local_time.hpp>
30 : : #include <boost/locale.hpp>
31 : : #include <boost/regex.hpp>
32 : : #include <unicode/smpdtfmt.h>
33 : : #include <unicode/locid.h>
34 : : #include <unicode/udat.h>
35 : : #include <unicode/parsepos.h>
36 : : #include <unicode/calendar.h>
37 : : #include <libintl.h>
38 : : #include <locale.h>
39 : : #include <map>
40 : : #include <memory>
41 : : #include <iostream>
42 : : #include <sstream>
43 : : #include <string>
44 : : #include <vector>
45 : : #include <optional>
46 : : #include <charconv>
47 : : #ifdef __MINGW32__
48 : : #include <codecvt>
49 : : #endif
50 : : #include <gnc-locale-utils.hpp>
51 : : #include "gnc-timezone.hpp"
52 : : #include "gnc-datetime.hpp"
53 : :
54 : : #define N_(string) string //So that xgettext will find it
55 : :
56 : : using PTZ = boost::local_time::posix_time_zone;
57 : : using Date = boost::gregorian::date;
58 : : using Month = boost::gregorian::greg_month;
59 : : using PTime = boost::posix_time::ptime;
60 : : using LDT = boost::local_time::local_date_time;
61 : : using Duration = boost::posix_time::time_duration;
62 : : using LDTBase = boost::local_time::local_date_time_base<PTime, boost::date_time::time_zone_base<PTime, char>>;
63 : : using boost::date_time::not_a_date_time;
64 : : using time64 = int64_t;
65 : :
66 : : static const TimeZoneProvider ltzp;
67 : : static const TimeZoneProvider* tzp = <zp;
68 : :
69 : : // For converting to/from POSIX time.
70 : : static const PTime unix_epoch (Date(1970, boost::gregorian::Jan, 1),
71 : : boost::posix_time::seconds(0));
72 : : static const TZ_Ptr utc_zone(new boost::local_time::posix_time_zone("UTC-0"));
73 : :
74 : : /* Backdoor to enable unittests to temporarily override the timezone: */
75 : : void _set_tzp(TimeZoneProvider& tz);
76 : : void _reset_tzp();
77 : :
78 : : static Date gregorian_date_from_locale_string (const std::string& str);
79 : :
80 : : /* To ensure things aren't overly screwed up by setting the nanosecond clock for boost::date_time. Don't do it, though, it doesn't get us anything and slows down the date/time library. */
81 : : #ifndef BOOST_DATE_TIME_HAS_NANOSECONDS
82 : : static constexpr auto ticks_per_second = INT64_C(1000000);
83 : : #else
84 : : static constexpr auto ticks_per_second = INT64_C(1000000000);
85 : : #endif
86 : :
87 : : /* Vector of date formats understood by gnucash and corresponding regex
88 : : * and/or string->gregorian_date to parse each from an external source
89 : : * Note: while the format names are using a "-" as separator, the
90 : : * regexes will accept any of "-/.' " and will also work for dates
91 : : * without separators.
92 : : */
93 : : const std::vector<GncDateFormat> GncDate::c_formats ({
94 : : GncDateFormat {
95 : : N_("y-m-d"),
96 : : boost::gregorian::from_string,
97 : : "(?:" // either y-m-d
98 : : "(?<YEAR>[0-9]+)[-/.' ]+"
99 : : "(?<MONTH>[0-9]+)[-/.' ]+"
100 : : "(?<DAY>[0-9]+)"
101 : : "|" // or CCYYMMDD
102 : : "(?<YEAR>[0-9]{4})"
103 : : "(?<MONTH>[0-9]{2})"
104 : : "(?<DAY>[0-9]{2})"
105 : : ")"
106 : : },
107 : : GncDateFormat {
108 : : N_("d-m-y"),
109 : : boost::gregorian::from_uk_string,
110 : : "(?:" // either d-m-y
111 : : "(?<DAY>[0-9]+)[-/.' ]+"
112 : : "(?<MONTH>[0-9]+)[-/.' ]+"
113 : : "(?<YEAR>[0-9]+)"
114 : : "|" // or DDMMCCYY
115 : : "(?<DAY>[0-9]{2})"
116 : : "(?<MONTH>[0-9]{2})"
117 : : "(?<YEAR>[0-9]{4})"
118 : : ")"
119 : : },
120 : : GncDateFormat {
121 : : N_("m-d-y"),
122 : : boost::gregorian::from_us_string,
123 : : "(?:" // either m-d-y
124 : : "(?<MONTH>[0-9]+)[-/.' ]+"
125 : : "(?<DAY>[0-9]+)[-/.' ]+"
126 : : "(?<YEAR>[0-9]+)"
127 : : "|" // or MMDDCCYY
128 : : "(?<MONTH>[0-9]{2})"
129 : : "(?<DAY>[0-9]{2})"
130 : : "(?<YEAR>[0-9]{4})"
131 : : ")"
132 : : },
133 : : // Note year is still checked for in the regexes below
134 : : // This is to be able to raise an error if one is found for a yearless date format
135 : : GncDateFormat {
136 : : (N_("d-m")),
137 : : "(?:" // either d-m(-y)
138 : : "(?<DAY>[0-9]+)[-/.' ]+"
139 : : "(?<MONTH>[0-9]+)(?:[-/.' ]+"
140 : : "(?<YEAR>[0-9]+))?"
141 : : "|" // or DDMM(CCYY)
142 : : "(?<DAY>[0-9]{2})"
143 : : "(?<MONTH>[0-9]{2})"
144 : : "(?<YEAR>[0-9]+)?"
145 : : ")"
146 : : },
147 : : GncDateFormat {
148 : : (N_("m-d")),
149 : : "(?:" // either m-d(-y)
150 : : "(?<MONTH>[0-9]+)[-/.' ]+"
151 : : "(?<DAY>[0-9]+)(?:[-/.' ]+"
152 : : "(?<YEAR>[0-9]+))?"
153 : : "|" // or MMDD(CCYY)
154 : : "(?<MONTH>[0-9]{2})"
155 : : "(?<DAY>[0-9]{2})"
156 : : "(?<YEAR>[0-9]+)?"
157 : : ")"
158 : : },
159 : : GncDateFormat { N_("Locale"), gregorian_date_from_locale_string },
160 : : });
161 : :
162 : : /** Private implementation of GncDateTime. See the documentation for that class.
163 : : */
164 : : static LDT
165 : 127055 : LDT_from_unix_local(const time64 time)
166 : : {
167 : : try
168 : : {
169 : : PTime temp(unix_epoch.date(),
170 : 127055 : boost::posix_time::hours(time / 3600) +
171 : 254110 : boost::posix_time::seconds(time % 3600));
172 : 127055 : auto tz = tzp->get(temp.date().year());
173 : 254106 : return LDT(temp, tz);
174 : 127053 : }
175 : 2 : catch(boost::gregorian::bad_year&)
176 : : {
177 : 2 : throw(std::invalid_argument("Time value is outside the supported year range."));
178 : 2 : }
179 : : }
180 : : /* If a date-time falls in a DST transition the LDT constructor will
181 : : * fail because either the date-time doesn't exist (when starting DST
182 : : * because the transition skips an hour) or is ambiguous (when ending
183 : : * because the transition hour is repeated). We try again an hour
184 : : * later to be outside the DST transition. When starting DST that's
185 : : * now the correct time but at the end of DST we need to set the
186 : : * returned time back an hour.
187 : : */
188 : : static LDT
189 : 0 : LDT_with_pushup(const Date& tdate, const Duration& tdur, const TZ_Ptr tz,
190 : : bool putback)
191 : : {
192 : 0 : static const boost::posix_time::hours pushup{1};
193 : 0 : LDT ldt{tdate, tdur + pushup, tz, LDTBase::NOT_DATE_TIME_ON_ERROR};
194 : 0 : if (ldt.is_special())
195 : : {
196 : 0 : std::string error{"Couldn't create a valid datetime at "};
197 : 0 : error += to_simple_string(tdate) + " ";
198 : 0 : error += to_simple_string(tdur) + " TZ ";
199 : 0 : error += tz->std_zone_abbrev();
200 : 0 : throw(std::invalid_argument{error});
201 : 0 : }
202 : 0 : if (putback)
203 : 0 : ldt -= pushup;
204 : 0 : return ldt;
205 : 0 : }
206 : :
207 : : static LDT
208 : 85963 : LDT_from_date_time(const Date& tdate, const Duration& tdur, const TZ_Ptr tz)
209 : : {
210 : :
211 : : try
212 : : {
213 : 85963 : LDT ldt(tdate, tdur, tz, LDTBase::EXCEPTION_ON_ERROR);
214 : 85963 : return ldt;
215 : 85963 : }
216 : 0 : catch (const boost::local_time::time_label_invalid& err)
217 : : {
218 : 0 : return LDT_with_pushup(tdate, tdur, tz, false);
219 : 0 : }
220 : :
221 : 0 : catch (const boost::local_time::ambiguous_result& err)
222 : : {
223 : 0 : return LDT_with_pushup(tdate, tdur, tz, true);
224 : 0 : }
225 : :
226 : 0 : catch(boost::gregorian::bad_year&)
227 : : {
228 : 0 : throw(std::invalid_argument("Time value is outside the supported year range."));
229 : 0 : }
230 : :
231 : : }
232 : :
233 : : static LDT
234 : 11949 : LDT_from_date_daypart(const Date& date, DayPart part, const TZ_Ptr tz)
235 : : {
236 : : using hours = boost::posix_time::hours;
237 : :
238 : : static const Duration day_begin{0, 0, 0};
239 : : static const Duration day_neutral{10, 59, 0};
240 : : static const Duration day_end{23, 59, 59};
241 : :
242 : :
243 : 11949 : switch (part)
244 : : {
245 : 6824 : case DayPart::start:
246 : 6824 : return LDT_from_date_time(date, day_begin, tz);
247 : 2844 : case DayPart::end:
248 : 2844 : return LDT_from_date_time(date, day_end, tz);
249 : 2281 : default: // To stop gcc from emitting a control reaches end of non-void function.
250 : : case DayPart::neutral:
251 : 2281 : PTime pt{date, day_neutral};
252 : 4562 : LDT lt{pt, tz};
253 : 2281 : auto offset = lt.local_time() - lt.utc_time();
254 : 2281 : if (offset < hours(-10))
255 : 0 : lt -= hours(offset.hours() + 10);
256 : 2281 : if (offset > hours(13))
257 : 0 : lt += hours(13 - offset.hours());
258 : 2281 : return lt;
259 : : }
260 : : }
261 : :
262 : : static LDT
263 : 73543 : LDT_from_struct_tm(const struct tm tm)
264 : : {
265 : : try
266 : : {
267 : 73543 : Date tdate{boost::gregorian::date_from_tm(tm)};
268 : 73543 : Duration tdur{boost::posix_time::time_duration(tm.tm_hour, tm.tm_min,
269 : 73543 : tm.tm_sec, 0)};
270 : 73543 : TZ_Ptr tz{tzp->get(tdate.year())};
271 : 147086 : return LDT_from_date_time(tdate, tdur, tz);
272 : 73543 : }
273 : 0 : catch(const boost::gregorian::bad_year&)
274 : : {
275 : 0 : throw(std::invalid_argument{"Time value is outside the supported year range."});
276 : 0 : }
277 : : }
278 : :
279 : : void
280 : 0 : _set_tzp(TimeZoneProvider& new_tzp)
281 : : {
282 : 0 : tzp = &new_tzp;
283 : 0 : }
284 : :
285 : : void
286 : 0 : _reset_tzp()
287 : : {
288 : 0 : tzp = <zp;
289 : 0 : }
290 : :
291 : : class GncDateTimeImpl
292 : : {
293 : : public:
294 : 9659 : GncDateTimeImpl() : m_time(boost::local_time::local_sec_clock::local_time(tzp->get(boost::gregorian::day_clock::local_day().year()))) {}
295 : 127055 : GncDateTimeImpl(const time64 time) : m_time(LDT_from_unix_local(time)) {}
296 : 73543 : GncDateTimeImpl(const struct tm tm) : m_time(LDT_from_struct_tm(tm)) {}
297 : : GncDateTimeImpl(const GncDateImpl& date, DayPart part = DayPart::neutral);
298 : 97 : GncDateTimeImpl(const std::string& str) : GncDateTimeImpl (str.c_str()) {};
299 : : GncDateTimeImpl(const char* str);
300 : : GncDateTimeImpl(PTime&& pt) : m_time(pt, tzp->get(pt.date().year())) {}
301 : : GncDateTimeImpl(LDT&& ldt) : m_time(ldt) {}
302 : :
303 : : operator time64() const;
304 : : operator struct tm() const;
305 : 0 : void now() { m_time = boost::local_time::local_sec_clock::local_time(tzp->get(boost::gregorian::day_clock::local_day().year())); }
306 : : long offset() const;
307 : 14 : struct tm utc_tm() const { return to_tm(m_time.utc_time()); }
308 : : std::unique_ptr<GncDateImpl> date() const;
309 : : std::string format(const char* format) const;
310 : : std::string format_zulu(const char* format) const;
311 : : std::string format_iso8601() const;
312 : : static std::string timestamp();
313 : : private:
314 : : LDT m_time;
315 : : };
316 : :
317 : : /** Private implementation of GncDate. See the documentation for that class.
318 : : */
319 : : class GncDateImpl
320 : : {
321 : : public:
322 : 434 : GncDateImpl(): m_greg(boost::gregorian::day_clock::local_day()) {}
323 : 12037 : GncDateImpl(const int year, const int month, const int day) :
324 : 12037 : m_greg(year, static_cast<Month>(month), day) {}
325 : 208 : GncDateImpl(Date d) : m_greg(d) {}
326 : : GncDateImpl(const std::string str, const std::string fmt);
327 : :
328 : 0 : void today() { m_greg = boost::gregorian::day_clock::local_day(); }
329 : : gnc_ymd year_month_day() const;
330 : : std::string format(const char* format) const;
331 : : std::string format_zulu(const char* format) const {
332 : : return this->format(format);
333 : : }
334 : : private:
335 : : Date m_greg;
336 : :
337 : : friend GncDateTimeImpl::GncDateTimeImpl(const GncDateImpl&, DayPart);
338 : : friend bool operator<(const GncDateImpl&, const GncDateImpl&);
339 : : friend bool operator>(const GncDateImpl&, const GncDateImpl&);
340 : : friend bool operator==(const GncDateImpl&, const GncDateImpl&);
341 : : friend bool operator<=(const GncDateImpl&, const GncDateImpl&);
342 : : friend bool operator>=(const GncDateImpl&, const GncDateImpl&);
343 : : friend bool operator!=(const GncDateImpl&, const GncDateImpl&);
344 : : };
345 : :
346 : : /* Needs to be separately defined so that the friend decl can grant
347 : : * access to date.m_greg.
348 : : */
349 : 11949 : GncDateTimeImpl::GncDateTimeImpl(const GncDateImpl& date, DayPart part) :
350 : 11949 : m_time{LDT_from_date_daypart(date.m_greg, part,
351 : 23898 : tzp->get(date.m_greg.year()))} {}
352 : :
353 : : /* Member function definitions for GncDateTimeImpl.
354 : : */
355 : :
356 : : static bool
357 : 20486 : parse_chars_into_num (const char* ptr, const char *end_ptr, int32_t& rv) noexcept
358 : : {
359 : 20486 : auto result = std::from_chars (ptr, end_ptr, rv);
360 : 20486 : return (result.ec == std::errc() && result.ptr == end_ptr);
361 : : }
362 : :
363 : : static std::optional<PTime>
364 : 2752 : fast_iso8601_utc_parse (const char* str)
365 : : {
366 : : int32_t year, month, mday, hour, min, sec;
367 : :
368 : : // parse the first 4 bytes into year
369 : 2752 : if (!str || !parse_chars_into_num (str, str + 4, year))
370 : 0 : return {};
371 : :
372 : : // parse iso-8601 utc format "YYYY-MM-DD HH:MM:SS +0000"
373 : 2751 : if (str[4] == '-' &&
374 : 8253 : parse_chars_into_num (str + 5, str + 7, month) && str[ 7] == '-' &&
375 : 8253 : parse_chars_into_num (str + 8, str + 10, mday) && str[10] == ' ' &&
376 : 8253 : parse_chars_into_num (str + 11, str + 13, hour) && str[13] == ':' &&
377 : 8253 : parse_chars_into_num (str + 14, str + 16, min) && str[16] == ':' &&
378 : 10904 : parse_chars_into_num (str + 17, str + 19, sec) && str[19] == ' ' &&
379 : 2650 : !strcmp (str + 20, "+0000"))
380 : : {
381 : 764 : return PTime (boost::gregorian::date (year, month, mday),
382 : 764 : boost::posix_time::time_duration (hour, min, sec));
383 : : }
384 : :
385 : : // parse compressed iso-8601 format "YYYYMMDDHHMMSS"
386 : 3976 : if (parse_chars_into_num (str + 4, str + 6, month) &&
387 : 1989 : parse_chars_into_num (str + 6, str + 8, mday) &&
388 : 2 : parse_chars_into_num (str + 8, str + 10, hour) &&
389 : 2 : parse_chars_into_num (str + 10, str + 12, min) &&
390 : 3977 : parse_chars_into_num (str + 12, str + 14, sec) &&
391 : 1 : str[14] == '\0')
392 : : {
393 : 1 : return PTime (boost::gregorian::date (year, month, mday),
394 : 1 : boost::posix_time::time_duration (hour, min, sec));
395 : : }
396 : :
397 : 1987 : return {};
398 : : }
399 : :
400 : : static TZ_Ptr
401 : 1987 : tz_from_string(std::string str)
402 : : {
403 : 1987 : if (str.empty()) return utc_zone;
404 : 1888 : std::string tzstr = "XXX" + str;
405 : 1888 : if (tzstr.length() > 6 && tzstr[6] != ':') //6 for XXXsHH, s is + or -
406 : 1880 : tzstr.insert(6, ":");
407 : 1888 : if (tzstr.length() > 9 && tzstr[9] != ':') //9 for XXXsHH:MM
408 : : {
409 : 0 : tzstr.insert(9, ":");
410 : : }
411 : 1888 : return TZ_Ptr(new PTZ(tzstr));
412 : 1888 : }
413 : :
414 : 2753 : GncDateTimeImpl::GncDateTimeImpl(const char* str) :
415 : 2753 : m_time(unix_epoch, utc_zone)
416 : : {
417 : 3518 : if (!str || !str[0]) return;
418 : 2752 : TZ_Ptr tzptr;
419 : : try
420 : : {
421 : 2752 : if (auto res = fast_iso8601_utc_parse (str))
422 : : {
423 : 765 : m_time = LDT_from_date_time(res->date(), res->time_of_day(), utc_zone);
424 : 765 : return;
425 : : }
426 : 1987 : static const boost::regex delim_iso("^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(?:\\.\\d{0,9})?)\\s*([+-]\\d{2}(?::?\\d{2})?)?$");
427 : 1987 : static const boost::regex non_delim("^(\\d{14}(?:\\.\\d{0,9})?)\\s*([+-]\\d{2}\\s*(:?\\d{2})?)?$");
428 : 1987 : PTime pdt;
429 : 1987 : boost::cmatch sm;
430 : 1987 : if (regex_match(str, sm, non_delim))
431 : : {
432 : 0 : std::string time_str(sm[1]);
433 : 0 : time_str.insert(8, "T");
434 : 0 : pdt = boost::posix_time::from_iso_string(time_str);
435 : 0 : }
436 : 1987 : else if (regex_match(str, sm, delim_iso))
437 : : {
438 : 1987 : pdt = boost::posix_time::time_from_string(sm[1]);
439 : : }
440 : : else
441 : : {
442 : 0 : throw(std::invalid_argument("The date string was not formatted in a way that GncDateTime(const char*) knows how to parse."));
443 : : }
444 : 1987 : std::string tzstr("");
445 : 1987 : if (sm[2].matched)
446 : 1888 : tzstr += sm[2];
447 : 1987 : tzptr = tz_from_string(tzstr);
448 : 1987 : m_time = LDT_from_date_time(pdt.date(), pdt.time_of_day(), tzptr);
449 : 1987 : }
450 : 0 : catch(boost::gregorian::bad_year&)
451 : : {
452 : 0 : throw(std::invalid_argument("The date string was outside of the supported year range."));
453 : 0 : }
454 : : /* Bug 767824: A GLib bug in parsing the UTC timezone on Windows may have
455 : : * created a bogus timezone of a random number of minutes. Since there are
456 : : * no fractional-hour timezones around the prime meridian we can safely
457 : : * check for this in files by resetting to UTC if there's a
458 : : * less-than-an-hour offset.
459 : : */
460 : 1987 : auto offset = tzptr->base_utc_offset().seconds();
461 : 1987 : if (offset != 0 && std::abs(offset) < 3600)
462 : 0 : m_time = m_time.local_time_in(utc_zone);
463 : 2752 : }
464 : :
465 : 97707 : GncDateTimeImpl::operator time64() const
466 : : {
467 : 97707 : auto duration = m_time.utc_time() - unix_epoch;
468 : 97707 : auto secs = duration.ticks();
469 : 97707 : secs /= ticks_per_second;
470 : 97707 : return secs;
471 : : }
472 : :
473 : 168582 : GncDateTimeImpl::operator struct tm() const
474 : : {
475 : 168582 : struct tm time = to_tm(m_time);
476 : : #if HAVE_STRUCT_TM_GMTOFF
477 : 168582 : time.tm_gmtoff = offset();
478 : : #endif
479 : 168582 : return time;
480 : : }
481 : :
482 : : long
483 : 168618 : GncDateTimeImpl::offset() const
484 : : {
485 : 168618 : auto offset = m_time.local_time() - m_time.utc_time();
486 : 337236 : return offset.total_seconds();
487 : : }
488 : :
489 : : std::unique_ptr<GncDateImpl>
490 : 208 : GncDateTimeImpl::date() const
491 : : {
492 : 208 : return std::unique_ptr<GncDateImpl>(new GncDateImpl(m_time.local_time().date()));
493 : : }
494 : :
495 : : /* The 'O', 'E', and '-' format modifiers are not supported by
496 : : * boost's output facets. Remove them.
497 : : */
498 : : static inline std::string
499 : 11822 : normalize_format (const std::string& format)
500 : : {
501 : 11822 : bool is_pct = false;
502 : 11822 : std::string normalized;
503 : 11822 : std::remove_copy_if(
504 : : format.begin(), format.end(), back_inserter(normalized),
505 : 91796 : [&is_pct](char e){
506 : 91796 : bool r = (is_pct && (e == 'E' || e == 'O' || e == '-'));
507 : 91796 : is_pct = e == '%';
508 : 91796 : return r;
509 : : });
510 : 23644 : return normalized;
511 : 0 : }
512 : : #ifdef __MINGW32__
513 : : constexpr size_t DATEBUFLEN = 100;
514 : : static std::string
515 : : win_date_format(std::string format, struct tm tm)
516 : : {
517 : : wchar_t buf[DATEBUFLEN];
518 : : memset(buf, 0, DATEBUFLEN);
519 : : std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> conv;
520 : : auto numchars = wcsftime(buf, DATEBUFLEN - 1, conv.from_bytes(format).c_str(), &tm);
521 : : return conv.to_bytes(buf);
522 : : }
523 : :
524 : : /* Microsoft's strftime uses the time zone flags differently from
525 : : * boost::date_time so we need to handle any before passing the
526 : : * format string to strftime.
527 : : */
528 : : inline std::string
529 : : win_format_tz_abbrev (std::string format, TZ_Ptr tz, bool is_dst)
530 : : {
531 : : size_t pos = format.find("%z");
532 : : if (pos != std::string::npos)
533 : : {
534 : : auto tzabbr = tz->has_dst() && is_dst ? tz->dst_zone_abbrev() :
535 : : tz->std_zone_abbrev();
536 : : format.replace(pos, 2, tzabbr);
537 : : }
538 : : return format;
539 : : }
540 : :
541 : : inline std::string
542 : : win_format_tz_name (std::string format, TZ_Ptr tz, bool is_dst)
543 : : {
544 : : size_t pos = format.find("%Z");
545 : : if (pos != std::string::npos)
546 : : {
547 : : auto tzname = tz->has_dst() && is_dst ? tz->dst_zone_name() :
548 : : tz->std_zone_name();
549 : : format.replace(pos, 2, tzname);
550 : : }
551 : : return format;
552 : : }
553 : :
554 : : inline std::string
555 : : win_format_tz_posix (std::string format, TZ_Ptr tz)
556 : : {
557 : : size_t pos = format.find("%ZP");
558 : : if (pos != std::string::npos)
559 : : format.replace(pos, 2, tz->to_posix_string());
560 : : return format;
561 : : }
562 : :
563 : : #endif
564 : : std::string
565 : 11746 : GncDateTimeImpl::format(const char* format) const
566 : : {
567 : : #ifdef __MINGW32__
568 : : auto tz = m_time.zone();
569 : : auto tm = static_cast<struct tm>(*this);
570 : : auto sformat = win_format_tz_abbrev(format, tz, tm.tm_isdst);
571 : : sformat = win_format_tz_name(sformat, tz, tm.tm_isdst);
572 : : sformat = win_format_tz_posix(sformat, tz);
573 : : return win_date_format(sformat, tm);
574 : : #else
575 : : using Facet = boost::local_time::local_time_facet;
576 : 35238 : auto output_facet(new Facet(normalize_format(format).c_str()));
577 : 11746 : std::stringstream ss;
578 : 11746 : ss.imbue(std::locale(gnc_get_locale(), output_facet));
579 : 11746 : ss << m_time;
580 : 23492 : return ss.str();
581 : : #endif
582 : 11746 : }
583 : :
584 : : std::string
585 : 0 : GncDateTimeImpl::format_zulu(const char* format) const
586 : : {
587 : : #ifdef __MINGW32__
588 : : auto tz = m_time.zone();
589 : : auto tm = static_cast<struct tm>(*this);
590 : : auto sformat = win_format_tz_abbrev(format, tz, tm.tm_isdst);
591 : : sformat = win_format_tz_name(sformat, tz, tm.tm_isdst);
592 : : sformat = win_format_tz_posix(sformat, tz);
593 : : return win_date_format(sformat, utc_tm());
594 : : #else
595 : : using Facet = boost::local_time::local_time_facet;
596 : 0 : auto zulu_time = LDT{m_time.utc_time(), utc_zone};
597 : 0 : auto output_facet(new Facet(normalize_format(format).c_str()));
598 : 0 : std::stringstream ss;
599 : 0 : ss.imbue(std::locale(gnc_get_locale(), output_facet));
600 : 0 : ss << zulu_time;
601 : 0 : return ss.str();
602 : : #endif
603 : 0 : }
604 : :
605 : : std::string
606 : 20046 : GncDateTimeImpl::format_iso8601() const
607 : : {
608 : 20046 : auto str = boost::posix_time::to_iso_extended_string(m_time.utc_time());
609 : 20046 : str[10] = ' ';
610 : 40092 : return str.substr(0, 19);
611 : 20046 : }
612 : :
613 : : std::string
614 : 44 : GncDateTimeImpl::timestamp()
615 : : {
616 : 44 : GncDateTimeImpl gdt;
617 : 44 : auto str = boost::posix_time::to_iso_string(gdt.m_time.local_time());
618 : 88 : return str.substr(0, 8) + str.substr(9, 15);
619 : 44 : }
620 : :
621 : : struct ICUResources
622 : : {
623 : : std::unique_ptr<icu::DateFormat> formatter;
624 : : std::unique_ptr<icu::Calendar> calendar;
625 : : };
626 : :
627 : : static ICUResources&
628 : 0 : get_icu_resources()
629 : : {
630 : 0 : static ICUResources rv;
631 : :
632 : 0 : if (!rv.formatter)
633 : : {
634 : 0 : icu::Locale locale;
635 : 0 : if (auto lc_time_locale = setlocale (LC_TIME, nullptr))
636 : : {
637 : 0 : std::string localeStr(lc_time_locale);
638 : 0 : if (size_t dotPos = localeStr.find('.'); dotPos != std::string::npos)
639 : 0 : localeStr = localeStr.substr(0, dotPos);
640 : :
641 : 0 : locale = icu::Locale::createCanonical (localeStr.c_str());
642 : 0 : }
643 : :
644 : 0 : rv.formatter.reset(icu::DateFormat::createDateInstance(icu::DateFormat::kDefault, locale));
645 : 0 : if (!rv.formatter)
646 : 0 : throw std::invalid_argument("Cannot create date formatter.");
647 : :
648 : 0 : UErrorCode status = U_ZERO_ERROR;
649 : 0 : rv.calendar.reset(icu::Calendar::createInstance(locale, status));
650 : 0 : if (U_FAILURE(status))
651 : 0 : throw std::invalid_argument("Cannot create calendar instance.");
652 : :
653 : 0 : rv.calendar->setLenient(false);
654 : 0 : }
655 : :
656 : 0 : return rv;
657 : : }
658 : :
659 : : static Date
660 : 0 : gregorian_date_from_locale_string (const std::string& str)
661 : : {
662 : 0 : ICUResources& resources = get_icu_resources();
663 : :
664 : 0 : icu::UnicodeString input = icu::UnicodeString::fromUTF8(str);
665 : 0 : icu::ParsePosition parsePos;
666 : 0 : UDate date = resources.formatter->parse(input, parsePos);
667 : 0 : if (parsePos.getErrorIndex() != -1 || parsePos.getIndex() != input.length())
668 : 0 : throw std::invalid_argument ("Cannot parse string");
669 : :
670 : 0 : UErrorCode status = U_ZERO_ERROR;
671 : 0 : resources.calendar->setTime(date, status);
672 : 0 : if (U_FAILURE(status))
673 : 0 : throw std::invalid_argument ("Cannot set calendar time");
674 : :
675 : 0 : return Date (resources.calendar->get(UCAL_YEAR, status),
676 : 0 : resources.calendar->get(UCAL_MONTH, status) + 1,
677 : 0 : resources.calendar->get(UCAL_DATE, status));
678 : 0 : }
679 : :
680 : : /* Member function definitions for GncDateImpl.
681 : : */
682 : 5 : GncDateImpl::GncDateImpl(const std::string str, const std::string fmt) :
683 : 5 : m_greg(boost::gregorian::day_clock::local_day()) /* Temporarily initialized to today, will be used and adjusted in the code below */
684 : : {
685 : 5 : auto iter = std::find_if(GncDate::c_formats.cbegin(), GncDate::c_formats.cend(),
686 : 15 : [&fmt](const GncDateFormat& v){ return (v.m_fmt == fmt); } );
687 : 5 : if (iter == GncDate::c_formats.cend())
688 : 0 : throw std::invalid_argument(N_("Unknown date format specifier passed as argument."));
689 : :
690 : 5 : if (iter->m_str_to_date)
691 : : {
692 : : try
693 : : {
694 : 5 : m_greg = (*iter->m_str_to_date)(str);
695 : 5 : return;
696 : : }
697 : 0 : catch (...) {} // with any string->date exception, try regex
698 : : }
699 : :
700 : 0 : if (iter->m_re.empty())
701 : 0 : throw std::invalid_argument ("No regex pattern available");
702 : :
703 : 0 : boost::regex r(iter->m_re);
704 : 0 : boost::smatch what;
705 : 0 : if(!boost::regex_search(str, what, r)) // regex didn't find a match
706 : 0 : throw std::invalid_argument (N_("Value can't be parsed into a date using the selected date format."));
707 : :
708 : : // Bail out if a year was found with a yearless format specifier
709 : 0 : auto fmt_has_year = (fmt.find('y') != std::string::npos);
710 : 0 : if (!fmt_has_year && (what.length("YEAR") != 0))
711 : 0 : throw std::invalid_argument (N_("Value appears to contain a year while the selected format forbids this."));
712 : :
713 : : int year;
714 : 0 : if (fmt_has_year)
715 : : {
716 : : /* The input dates have a year, so use that one */
717 : 0 : year = std::stoi (what.str("YEAR"));
718 : :
719 : : /* We assume two-digit years to be in the range 1969 - 2068. */
720 : 0 : if (year < 69)
721 : 0 : year += 2000;
722 : 0 : else if (year < 100)
723 : 0 : year += 1900;
724 : : }
725 : : else /* The input dates have no year, so use current year */
726 : 0 : year = m_greg.year(); // Can use m_greg here as it was already initialized in the initializer list earlier
727 : :
728 : 0 : m_greg = Date(year,
729 : 0 : static_cast<Month>(std::stoi (what.str("MONTH"))),
730 : 0 : std::stoi (what.str("DAY")));
731 : 0 : }
732 : :
733 : : gnc_ymd
734 : 642 : GncDateImpl::year_month_day() const
735 : : {
736 : 642 : auto boost_ymd = m_greg.year_month_day();
737 : 1284 : return {boost_ymd.year, boost_ymd.month.as_number(), boost_ymd.day};
738 : : }
739 : :
740 : : std::string
741 : 76 : GncDateImpl::format(const char* format) const
742 : : {
743 : : #ifdef __MINGW32__
744 : : return win_date_format(format, to_tm(m_greg));
745 : : #else
746 : : using Facet = boost::gregorian::date_facet;
747 : 76 : std::stringstream ss;
748 : : //The stream destructor frees the facet, so it must be heap-allocated.
749 : 228 : auto output_facet(new Facet(normalize_format(format).c_str()));
750 : 76 : ss.imbue(std::locale(gnc_get_locale(), output_facet));
751 : 76 : ss << m_greg;
752 : 152 : return ss.str();
753 : : #endif
754 : 76 : }
755 : :
756 : 0 : bool operator<(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg < b.m_greg; }
757 : 0 : bool operator>(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg > b.m_greg; }
758 : 0 : bool operator==(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg == b.m_greg; }
759 : 0 : bool operator<=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg <= b.m_greg; }
760 : 0 : bool operator>=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg >= b.m_greg; }
761 : 0 : bool operator!=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg != b.m_greg; }
762 : :
763 : : /* =================== Presentation-class Implementations ====================*/
764 : : /* GncDateTime */
765 : :
766 : 9615 : GncDateTime::GncDateTime() : m_impl(new GncDateTimeImpl) {}
767 : 127055 : GncDateTime::GncDateTime(const time64 time) :
768 : 127055 : m_impl(new GncDateTimeImpl(time)) {}
769 : 73543 : GncDateTime::GncDateTime(const struct tm tm) :
770 : 73543 : m_impl(new GncDateTimeImpl(tm)) {}
771 : 97 : GncDateTime::GncDateTime(const std::string& str) :
772 : 97 : m_impl(new GncDateTimeImpl(str)) {}
773 : 2656 : GncDateTime::GncDateTime(const char* str) :
774 : 2656 : m_impl(new GncDateTimeImpl(str)) {}
775 : 224913 : GncDateTime::~GncDateTime() = default;
776 : :
777 : 11949 : GncDateTime::GncDateTime(const GncDate& date, DayPart part) :
778 : 11949 : m_impl(new GncDateTimeImpl(*(date.m_impl), part)) {}
779 : :
780 : : void
781 : 0 : GncDateTime::now()
782 : : {
783 : 0 : m_impl->now();
784 : 0 : }
785 : :
786 : 97707 : GncDateTime::operator time64() const
787 : : {
788 : 97707 : return m_impl->operator time64();
789 : : }
790 : :
791 : 168582 : GncDateTime::operator struct tm() const
792 : : {
793 : 168582 : return m_impl->operator struct tm();
794 : : }
795 : :
796 : : long
797 : 36 : GncDateTime::offset() const
798 : : {
799 : 36 : return m_impl->offset();
800 : : }
801 : :
802 : : struct tm
803 : 14 : GncDateTime::utc_tm() const
804 : : {
805 : 14 : return m_impl->utc_tm();
806 : : }
807 : :
808 : : GncDate
809 : 208 : GncDateTime::date() const
810 : : {
811 : 208 : return GncDate(m_impl->date());
812 : : }
813 : :
814 : : std::string
815 : 11746 : GncDateTime::format(const char* format) const
816 : : {
817 : 11746 : return m_impl->format(format);
818 : : }
819 : :
820 : : std::string
821 : 0 : GncDateTime::format_zulu(const char* format) const
822 : : {
823 : 0 : return m_impl->format_zulu(format);
824 : : }
825 : :
826 : : std::string
827 : 20046 : GncDateTime::format_iso8601() const
828 : : {
829 : 20046 : return m_impl->format_iso8601();
830 : : }
831 : :
832 : : std::string
833 : 44 : GncDateTime::timestamp()
834 : : {
835 : 44 : return GncDateTimeImpl::timestamp();
836 : : }
837 : :
838 : : /* GncDate */
839 : 434 : GncDate::GncDate() : m_impl{new GncDateImpl} {}
840 : 12037 : GncDate::GncDate(int year, int month, int day) :
841 : 12037 : m_impl(new GncDateImpl(year, month, day)) {}
842 : 5 : GncDate::GncDate(const std::string str, const std::string fmt) :
843 : 5 : m_impl(new GncDateImpl(str, fmt)) {}
844 : 208 : GncDate::GncDate(std::unique_ptr<GncDateImpl> impl) :
845 : 208 : m_impl(std::move(impl)) {}
846 : 0 : GncDate::GncDate(const GncDate& a) :
847 : 0 : m_impl(new GncDateImpl(*a.m_impl)) {}
848 : 0 : GncDate::GncDate(GncDate&&) = default;
849 : 12667 : GncDate::~GncDate() = default;
850 : :
851 : : GncDate&
852 : 0 : GncDate::operator=(const GncDate& a)
853 : : {
854 : 0 : m_impl.reset(new GncDateImpl(*a.m_impl));
855 : 0 : return *this;
856 : : }
857 : : GncDate&
858 : 0 : GncDate::operator=(GncDate&&) = default;
859 : :
860 : : void
861 : 0 : GncDate::today()
862 : : {
863 : 0 : m_impl->today();
864 : 0 : }
865 : :
866 : : std::string
867 : 76 : GncDate::format(const char* format)
868 : : {
869 : 76 : return m_impl->format(format);
870 : : }
871 : :
872 : : gnc_ymd
873 : 642 : GncDate::year_month_day() const
874 : : {
875 : 642 : return m_impl->year_month_day();
876 : : }
877 : :
878 : 0 : bool operator<(const GncDate& a, const GncDate& b) { return *(a.m_impl) < *(b.m_impl); }
879 : 0 : bool operator>(const GncDate& a, const GncDate& b) { return *(a.m_impl) > *(b.m_impl); }
880 : 0 : bool operator==(const GncDate& a, const GncDate& b) { return *(a.m_impl) == *(b.m_impl); }
881 : 0 : bool operator<=(const GncDate& a, const GncDate& b) { return *(a.m_impl) <= *(b.m_impl); }
882 : 0 : bool operator>=(const GncDate& a, const GncDate& b) { return *(a.m_impl) >= *(b.m_impl); }
883 : 0 : bool operator!=(const GncDate& a, const GncDate& b) { return *(a.m_impl) != *(b.m_impl); }
|