GnuCash  5.6-150-g038405b370+
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 <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 = &ltzp;
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 ({
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 
164 static LDT
165 LDT_from_unix_local(const time64 time)
166 {
167  try
168  {
169  PTime temp(unix_epoch.date(),
170  boost::posix_time::hours(time / 3600) +
171  boost::posix_time::seconds(time % 3600));
172  auto tz = tzp->get(temp.date().year());
173  return LDT(temp, tz);
174  }
175  catch(boost::gregorian::bad_year&)
176  {
177  throw(std::invalid_argument("Time value is outside the supported year range."));
178  }
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 LDT_with_pushup(const Date& tdate, const Duration& tdur, const TZ_Ptr tz,
190  bool putback)
191 {
192  static const boost::posix_time::hours pushup{1};
193  LDT ldt{tdate, tdur + pushup, tz, LDTBase::NOT_DATE_TIME_ON_ERROR};
194  if (ldt.is_special())
195  {
196  std::string error{"Couldn't create a valid datetime at "};
197  error += to_simple_string(tdate) + " ";
198  error += to_simple_string(tdur) + " TZ ";
199  error += tz->std_zone_abbrev();
200  throw(std::invalid_argument{error});
201  }
202  if (putback)
203  ldt -= pushup;
204  return ldt;
205 }
206 
207 static LDT
208 LDT_from_date_time(const Date& tdate, const Duration& tdur, const TZ_Ptr tz)
209 {
210 
211  try
212  {
213  LDT ldt(tdate, tdur, tz, LDTBase::EXCEPTION_ON_ERROR);
214  return ldt;
215  }
216  catch (const boost::local_time::time_label_invalid& err)
217  {
218  return LDT_with_pushup(tdate, tdur, tz, false);
219  }
220 
221  catch (const boost::local_time::ambiguous_result& err)
222  {
223  return LDT_with_pushup(tdate, tdur, tz, true);
224  }
225 
226  catch(boost::gregorian::bad_year&)
227  {
228  throw(std::invalid_argument("Time value is outside the supported year range."));
229  }
230 
231 }
232 
233 static LDT
234 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  switch (part)
244  {
245  case DayPart::start:
246  return LDT_from_date_time(date, day_begin, tz);
247  case DayPart::end:
248  return LDT_from_date_time(date, day_end, tz);
249  default: // To stop gcc from emitting a control reaches end of non-void function.
250  case DayPart::neutral:
251  PTime pt{date, day_neutral};
252  LDT lt{pt, tz};
253  auto offset = lt.local_time() - lt.utc_time();
254  if (offset < hours(-10))
255  lt -= hours(offset.hours() + 10);
256  if (offset > hours(13))
257  lt += hours(13 - offset.hours());
258  return lt;
259  }
260 }
261 
262 static LDT
263 LDT_from_struct_tm(const struct tm tm)
264 {
265  try
266  {
267  Date tdate{boost::gregorian::date_from_tm(tm)};
268  Duration tdur{boost::posix_time::time_duration(tm.tm_hour, tm.tm_min,
269  tm.tm_sec, 0)};
270  TZ_Ptr tz{tzp->get(tdate.year())};
271  return LDT_from_date_time(tdate, tdur, tz);
272  }
273  catch(const boost::gregorian::bad_year&)
274  {
275  throw(std::invalid_argument{"Time value is outside the supported year range."});
276  }
277 }
278 
279 void
280 _set_tzp(TimeZoneProvider& new_tzp)
281 {
282  tzp = &new_tzp;
283 }
284 
285 void
286 _reset_tzp()
287 {
288  tzp = &ltzp;
289 }
290 
292 {
293 public:
294  GncDateTimeImpl() : m_time(boost::local_time::local_sec_clock::local_time(tzp->get(boost::gregorian::day_clock::local_day().year()))) {}
295  GncDateTimeImpl(const time64 time) : m_time(LDT_from_unix_local(time)) {}
296  GncDateTimeImpl(const struct tm tm) : m_time(LDT_from_struct_tm(tm)) {}
297  GncDateTimeImpl(const GncDateImpl& date, DayPart part = DayPart::neutral);
298  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  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  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 
320 {
321 public:
322  GncDateImpl(): m_greg(boost::gregorian::day_clock::local_day()) {}
323  GncDateImpl(const int year, const int month, const int day) :
324  m_greg(year, static_cast<Month>(month), day) {}
325  GncDateImpl(Date d) : m_greg(d) {}
326  GncDateImpl(const std::string str, const std::string fmt);
327 
328  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 GncDateTimeImpl::GncDateTimeImpl(const GncDateImpl& date, DayPart part) :
350  m_time{LDT_from_date_daypart(date.m_greg, part,
351  tzp->get(date.m_greg.year()))} {}
352 
353 /* Member function definitions for GncDateTimeImpl.
354  */
355 
356 static bool
357 parse_chars_into_num (const char* ptr, const char *end_ptr, int32_t& rv) noexcept
358 {
359  auto result = std::from_chars (ptr, end_ptr, rv);
360  return (result.ec == std::errc() && result.ptr == end_ptr);
361 }
362 
363 static std::optional<PTime>
364 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  if (!str || !parse_chars_into_num (str, str + 4, year))
370  return {};
371 
372  // parse iso-8601 utc format "YYYY-MM-DD HH:MM:SS +0000"
373  if (str[4] == '-' &&
374  parse_chars_into_num (str + 5, str + 7, month) && str[ 7] == '-' &&
375  parse_chars_into_num (str + 8, str + 10, mday) && str[10] == ' ' &&
376  parse_chars_into_num (str + 11, str + 13, hour) && str[13] == ':' &&
377  parse_chars_into_num (str + 14, str + 16, min) && str[16] == ':' &&
378  parse_chars_into_num (str + 17, str + 19, sec) && str[19] == ' ' &&
379  !strcmp (str + 20, "+0000"))
380  {
381  return PTime (boost::gregorian::date (year, month, mday),
382  boost::posix_time::time_duration (hour, min, sec));
383  }
384 
385  // parse compressed iso-8601 format "YYYYMMDDHHMMSS"
386  if (parse_chars_into_num (str + 4, str + 6, month) &&
387  parse_chars_into_num (str + 6, str + 8, mday) &&
388  parse_chars_into_num (str + 8, str + 10, hour) &&
389  parse_chars_into_num (str + 10, str + 12, min) &&
390  parse_chars_into_num (str + 12, str + 14, sec) &&
391  str[14] == '\0')
392  {
393  return PTime (boost::gregorian::date (year, month, mday),
394  boost::posix_time::time_duration (hour, min, sec));
395  }
396 
397  return {};
398 }
399 
400 static TZ_Ptr
401 tz_from_string(std::string str)
402 {
403  if (str.empty()) return utc_zone;
404  std::string tzstr = "XXX" + str;
405  if (tzstr.length() > 6 && tzstr[6] != ':') //6 for XXXsHH, s is + or -
406  tzstr.insert(6, ":");
407  if (tzstr.length() > 9 && tzstr[9] != ':') //9 for XXXsHH:MM
408  {
409  tzstr.insert(9, ":");
410  }
411  return TZ_Ptr(new PTZ(tzstr));
412 }
413 
414 GncDateTimeImpl::GncDateTimeImpl(const char* str) :
415  m_time(unix_epoch, utc_zone)
416 {
417  if (!str || !str[0]) return;
418  TZ_Ptr tzptr;
419  try
420  {
421  if (auto res = fast_iso8601_utc_parse (str))
422  {
423  m_time = LDT_from_date_time(res->date(), res->time_of_day(), utc_zone);
424  return;
425  }
426  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  static const boost::regex non_delim("^(\\d{14}(?:\\.\\d{0,9})?)\\s*([+-]\\d{2}\\s*(:?\\d{2})?)?$");
428  PTime pdt;
429  boost::cmatch sm;
430  if (regex_match(str, sm, non_delim))
431  {
432  std::string time_str(sm[1]);
433  time_str.insert(8, "T");
434  pdt = boost::posix_time::from_iso_string(time_str);
435  }
436  else if (regex_match(str, sm, delim_iso))
437  {
438  pdt = boost::posix_time::time_from_string(sm[1]);
439  }
440  else
441  {
442  throw(std::invalid_argument("The date string was not formatted in a way that GncDateTime(const char*) knows how to parse."));
443  }
444  std::string tzstr("");
445  if (sm[2].matched)
446  tzstr += sm[2];
447  tzptr = tz_from_string(tzstr);
448  m_time = LDT_from_date_time(pdt.date(), pdt.time_of_day(), tzptr);
449  }
450  catch(boost::gregorian::bad_year&)
451  {
452  throw(std::invalid_argument("The date string was outside of the supported year range."));
453  }
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  auto offset = tzptr->base_utc_offset().seconds();
461  if (offset != 0 && std::abs(offset) < 3600)
462  m_time = m_time.local_time_in(utc_zone);
463 }
464 
465 GncDateTimeImpl::operator time64() const
466 {
467  auto duration = m_time.utc_time() - unix_epoch;
468  auto secs = duration.ticks();
469  secs /= ticks_per_second;
470  return secs;
471 }
472 
473 GncDateTimeImpl::operator struct tm() const
474 {
475  struct tm time = to_tm(m_time);
476 #if HAVE_STRUCT_TM_GMTOFF
477  time.tm_gmtoff = offset();
478 #endif
479  return time;
480 }
481 
482 long
483 GncDateTimeImpl::offset() const
484 {
485  auto offset = m_time.local_time() - m_time.utc_time();
486  return offset.total_seconds();
487 }
488 
489 std::unique_ptr<GncDateImpl>
490 GncDateTimeImpl::date() const
491 {
492  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 normalize_format (const std::string& format)
500 {
501  bool is_pct = false;
502  std::string normalized;
503  std::remove_copy_if(
504  format.begin(), format.end(), back_inserter(normalized),
505  [&is_pct](char e){
506  bool r = (is_pct && (e == 'E' || e == 'O' || e == '-'));
507  is_pct = e == '%';
508  return r;
509  });
510  return normalized;
511 }
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 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  auto output_facet(new Facet(normalize_format(format).c_str()));
577  std::stringstream ss;
578  ss.imbue(std::locale(gnc_get_locale(), output_facet));
579  ss << m_time;
580  return ss.str();
581 #endif
582 }
583 
584 std::string
585 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  auto zulu_time = LDT{m_time.utc_time(), utc_zone};
597  auto output_facet(new Facet(normalize_format(format).c_str()));
598  std::stringstream ss;
599  ss.imbue(std::locale(gnc_get_locale(), output_facet));
600  ss << zulu_time;
601  return ss.str();
602 #endif
603 }
604 
605 std::string
606 GncDateTimeImpl::format_iso8601() const
607 {
608  auto str = boost::posix_time::to_iso_extended_string(m_time.utc_time());
609  str[10] = ' ';
610  return str.substr(0, 19);
611 }
612 
613 std::string
614 GncDateTimeImpl::timestamp()
615 {
616  GncDateTimeImpl gdt;
617  auto str = boost::posix_time::to_iso_string(gdt.m_time.local_time());
618  return str.substr(0, 8) + str.substr(9, 15);
619 }
620 
622 {
623  std::unique_ptr<icu::DateFormat> formatter;
624  std::unique_ptr<icu::Calendar> calendar;
625 };
626 
627 static ICUResources&
628 get_icu_resources()
629 {
630  static ICUResources rv;
631 
632  if (!rv.formatter)
633  {
634  icu::Locale locale;
635  if (auto lc_time_locale = setlocale (LC_TIME, nullptr))
636  {
637  std::string localeStr(lc_time_locale);
638  if (size_t dotPos = localeStr.find('.'); dotPos != std::string::npos)
639  localeStr = localeStr.substr(0, dotPos);
640 
641  locale = icu::Locale::createCanonical (localeStr.c_str());
642  }
643 
644  rv.formatter.reset(icu::DateFormat::createDateInstance(icu::DateFormat::kDefault, locale));
645  if (!rv.formatter)
646  throw std::invalid_argument("Cannot create date formatter.");
647 
648  UErrorCode status = U_ZERO_ERROR;
649  rv.calendar.reset(icu::Calendar::createInstance(locale, status));
650  if (U_FAILURE(status))
651  throw std::invalid_argument("Cannot create calendar instance.");
652 
653  rv.calendar->setLenient(false);
654  }
655 
656  return rv;
657 }
658 
659 static Date
660 gregorian_date_from_locale_string (const std::string& str)
661 {
662  ICUResources& resources = get_icu_resources();
663 
664  icu::UnicodeString input = icu::UnicodeString::fromUTF8(str);
665  icu::ParsePosition parsePos;
666  UDate date = resources.formatter->parse(input, parsePos);
667  if (parsePos.getErrorIndex() != -1 || parsePos.getIndex() != input.length())
668  throw std::invalid_argument ("Cannot parse string");
669 
670  UErrorCode status = U_ZERO_ERROR;
671  resources.calendar->setTime(date, status);
672  if (U_FAILURE(status))
673  throw std::invalid_argument ("Cannot set calendar time");
674 
675  return Date (resources.calendar->get(UCAL_YEAR, status),
676  resources.calendar->get(UCAL_MONTH, status) + 1,
677  resources.calendar->get(UCAL_DATE, status));
678 }
679 
680 /* Member function definitions for GncDateImpl.
681  */
682 GncDateImpl::GncDateImpl(const std::string str, const std::string fmt) :
683  m_greg(boost::gregorian::day_clock::local_day()) /* Temporarily initialized to today, will be used and adjusted in the code below */
684 {
685  auto iter = std::find_if(GncDate::c_formats.cbegin(), GncDate::c_formats.cend(),
686  [&fmt](const GncDateFormat& v){ return (v.m_fmt == fmt); } );
687  if (iter == GncDate::c_formats.cend())
688  throw std::invalid_argument(N_("Unknown date format specifier passed as argument."));
689 
690  if (iter->m_str_to_date)
691  {
692  try
693  {
694  m_greg = (*iter->m_str_to_date)(str);
695  return;
696  }
697  catch (...) {} // with any string->date exception, try regex
698  }
699 
700  if (iter->m_re.empty())
701  throw std::invalid_argument ("No regex pattern available");
702 
703  boost::regex r(iter->m_re);
704  boost::smatch what;
705  if(!boost::regex_search(str, what, r)) // regex didn't find a match
706  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  auto fmt_has_year = (fmt.find('y') != std::string::npos);
710  if (!fmt_has_year && (what.length("YEAR") != 0))
711  throw std::invalid_argument (N_("Value appears to contain a year while the selected format forbids this."));
712 
713  int year;
714  if (fmt_has_year)
715  {
716  /* The input dates have a year, so use that one */
717  year = std::stoi (what.str("YEAR"));
718 
719  /* We assume two-digit years to be in the range 1969 - 2068. */
720  if (year < 69)
721  year += 2000;
722  else if (year < 100)
723  year += 1900;
724  }
725  else /* The input dates have no year, so use current year */
726  year = m_greg.year(); // Can use m_greg here as it was already initialized in the initializer list earlier
727 
728  m_greg = Date(year,
729  static_cast<Month>(std::stoi (what.str("MONTH"))),
730  std::stoi (what.str("DAY")));
731 }
732 
733 gnc_ymd
734 GncDateImpl::year_month_day() const
735 {
736  auto boost_ymd = m_greg.year_month_day();
737  return {boost_ymd.year, boost_ymd.month.as_number(), boost_ymd.day};
738 }
739 
740 std::string
741 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  std::stringstream ss;
748  //The stream destructor frees the facet, so it must be heap-allocated.
749  auto output_facet(new Facet(normalize_format(format).c_str()));
750  ss.imbue(std::locale(gnc_get_locale(), output_facet));
751  ss << m_greg;
752  return ss.str();
753 #endif
754 }
755 
756 bool operator<(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg < b.m_greg; }
757 bool operator>(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg > b.m_greg; }
758 bool operator==(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg == b.m_greg; }
759 bool operator<=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg <= b.m_greg; }
760 bool operator>=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg >= b.m_greg; }
761 bool operator!=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg != b.m_greg; }
762 
763 /* =================== Presentation-class Implementations ====================*/
764 /* GncDateTime */
765 
768  m_impl(new GncDateTimeImpl(time)) {}
769 GncDateTime::GncDateTime(const struct tm tm) :
770  m_impl(new GncDateTimeImpl(tm)) {}
771 GncDateTime::GncDateTime(const std::string& str) :
772  m_impl(new GncDateTimeImpl(str)) {}
773 GncDateTime::GncDateTime(const char* str) :
774  m_impl(new GncDateTimeImpl(str)) {}
775 GncDateTime::~GncDateTime() = default;
776 
777 GncDateTime::GncDateTime(const GncDate& date, DayPart part) :
778  m_impl(new GncDateTimeImpl(*(date.m_impl), part)) {}
779 
780 void
782 {
783  m_impl->now();
784 }
785 
786 GncDateTime::operator time64() const
787 {
788  return m_impl->operator time64();
789 }
790 
791 GncDateTime::operator struct tm() const
792 {
793  return m_impl->operator struct tm();
794 }
795 
796 long
798 {
799  return m_impl->offset();
800 }
801 
802 struct tm
803 GncDateTime::utc_tm() const
804 {
805  return m_impl->utc_tm();
806 }
807 
808 GncDate
810 {
811  return GncDate(m_impl->date());
812 }
813 
814 std::string
815 GncDateTime::format(const char* format) const
816 {
817  return m_impl->format(format);
818 }
819 
820 std::string
821 GncDateTime::format_zulu(const char* format) const
822 {
823  return m_impl->format_zulu(format);
824 }
825 
826 std::string
828 {
829  return m_impl->format_iso8601();
830 }
831 
832 std::string
834 {
835  return GncDateTimeImpl::timestamp();
836 }
837 
838 /* GncDate */
839 GncDate::GncDate() : m_impl{new GncDateImpl} {}
840 GncDate::GncDate(int year, int month, int day) :
841 m_impl(new GncDateImpl(year, month, day)) {}
842 GncDate::GncDate(const std::string str, const std::string fmt) :
843 m_impl(new GncDateImpl(str, fmt)) {}
844 GncDate::GncDate(std::unique_ptr<GncDateImpl> impl) :
845 m_impl(std::move(impl)) {}
847 m_impl(new GncDateImpl(*a.m_impl)) {}
848 GncDate::GncDate(GncDate&&) = default;
849 GncDate::~GncDate() = default;
850 
851 GncDate&
853 {
854  m_impl.reset(new GncDateImpl(*a.m_impl));
855  return *this;
856 }
857 GncDate&
858 GncDate::operator=(GncDate&&) = default;
859 
860 void
862 {
863  m_impl->today();
864 }
865 
866 std::string
867 GncDate::format(const char* format)
868 {
869  return m_impl->format(format);
870 }
871 
872 gnc_ymd
874 {
875  return m_impl->year_month_day();
876 }
877 
878 bool operator<(const GncDate& a, const GncDate& b) { return *(a.m_impl) < *(b.m_impl); }
879 bool operator>(const GncDate& a, const GncDate& b) { return *(a.m_impl) > *(b.m_impl); }
880 bool operator==(const GncDate& a, const GncDate& b) { return *(a.m_impl) == *(b.m_impl); }
881 bool operator<=(const GncDate& a, const GncDate& b) { return *(a.m_impl) <= *(b.m_impl); }
882 bool operator>=(const GncDate& a, const GncDate& b) { return *(a.m_impl) >= *(b.m_impl); }
883 bool operator!=(const GncDate& a, const GncDate& b) { return *(a.m_impl) != *(b.m_impl); }
GncDate date() const
Obtain the date from the time, as a GncDate, in the current timezone.
std::string format_iso8601() const
Format the GncDateTime into a gnucash-style iso8601 string in UTC.
GnuCash DateTime class.
void today()
Set the date object to the computer clock&#39;s current day.
STL namespace.
long offset() const
Obtain the UTC offset in seconds.
std::string format(const char *format)
Format the GncDate into a std::string.
gnc_ymd year_month_day() const
Get the year, month, and day from the date as a gnc_ymd.
~GncDate()
Default destructor.
static std::string timestamp()
Get an undelimited string representing the current date and time.
std::string format_zulu(const char *format) const
Format the GncDateTime into a std::string in GMT.
GncDate()
Construct a GncDate representing the current day.
GncDate & operator=(const GncDate &)
Copy assignment operator.
Private implementation of GncDate.
GncDateTime()
Construct a GncDateTime representing the current time in the current timezone.
std::string format(const char *format) const
Format the GncDateTime into a std::string.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
struct tm utc_tm() const
Obtain a struct tm representing the time in UTC.
static const std::vector< GncDateFormat > c_formats
A vector with all the date formats supported by the string constructor.
GnuCash Date class.
void now()
Set the GncDateTime to the date and time indicated in the computer&#39;s clock.