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