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 <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 
58 using PTZ = boost::local_time::posix_time_zone;
59 using Date = boost::gregorian::date;
60 using Month = boost::gregorian::greg_month;
61 using PTime = boost::posix_time::ptime;
62 using LDT = boost::local_time::local_date_time;
63 using Duration = boost::posix_time::time_duration;
64 using LDTBase = boost::local_time::local_date_time_base<PTime, boost::date_time::time_zone_base<PTime, char>>;
65 using boost::date_time::not_a_date_time;
66 using time64 = int64_t;
67 
68 static const TimeZoneProvider ltzp;
69 static const TimeZoneProvider* tzp = &ltzp;
70 
71 // For converting to/from POSIX time.
72 static const PTime unix_epoch (Date(1970, boost::gregorian::Jan, 1),
73  boost::posix_time::seconds(0));
74 static 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: */
77 void _set_tzp(TimeZoneProvider& tz);
78 void _reset_tzp();
79 
80 static 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
84 static constexpr auto ticks_per_second = INT64_C(1000000);
85 #else
86 static 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  */
95 const 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  },
109  GncDateFormat {
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  },
122  GncDateFormat {
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
137  GncDateFormat {
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  },
149  GncDateFormat {
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 
166 static LDT
167 LDT_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  */
194 static LDT
195 LDT_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 
213 static LDT
214 LDT_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 
243 static LDT
244 LDT_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 
272 static LDT
273 LDT_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 
293 void
294 _set_tzp(TimeZoneProvider& new_tzp)
295 {
296  tzp = &new_tzp;
297 }
298 
299 void
300 _reset_tzp()
301 {
302  tzp = &ltzp;
303 }
304 
306 {
307 public:
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();
330 private:
331  LDT m_time;
332 };
333 
337 {
338 public:
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  }
351 private:
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  */
366 GncDateTimeImpl::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 
373 static bool
374 parse_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 
380 static std::optional<PTime>
381 fast_iso8601_utc_parse (const char* str)
382 {
383  int32_t year, month, mday, hour, min, sec;
384 
385  // parse the first 4 bytes into year
386  if (!str || !parse_chars_into_num (str, str + 4, year))
387  return {};
388 
389  // parse iso-8601 utc format "YYYY-MM-DD HH:MM:SS +0000"
390  if (str[4] == '-' &&
391  parse_chars_into_num (str + 5, str + 7, month) && str[ 7] == '-' &&
392  parse_chars_into_num (str + 8, str + 10, mday) && str[10] == ' ' &&
393  parse_chars_into_num (str + 11, str + 13, hour) && str[13] == ':' &&
394  parse_chars_into_num (str + 14, str + 16, min) && str[16] == ':' &&
395  parse_chars_into_num (str + 17, str + 19, sec) && str[19] == ' ' &&
396  !strcmp (str + 20, "+0000"))
397  {
398  return PTime (boost::gregorian::date (year, month, mday),
399  boost::posix_time::time_duration (hour, min, sec));
400  }
401 
402  // parse compressed iso-8601 format "YYYYMMDDHHMMSS"
403  if (parse_chars_into_num (str + 4, str + 6, month) &&
404  parse_chars_into_num (str + 6, str + 8, mday) &&
405  parse_chars_into_num (str + 8, str + 10, hour) &&
406  parse_chars_into_num (str + 10, str + 12, min) &&
407  parse_chars_into_num (str + 12, str + 14, sec) &&
408  str[14] == '\0')
409  {
410  return PTime (boost::gregorian::date (year, month, mday),
411  boost::posix_time::time_duration (hour, min, sec));
412  }
413 
414  return {};
415 }
416 
417 static TZ_Ptr
418 tz_from_string(std::string str)
419 {
420  if (str.empty()) return utc_zone;
421  std::string tzstr = "XXX" + str;
422  if (tzstr.length() > 6 && tzstr[6] != ':') //6 for XXXsHH, s is + or -
423  tzstr.insert(6, ":");
424  if (tzstr.length() > 9 && tzstr[9] != ':') //9 for XXXsHH:MM
425  {
426  tzstr.insert(9, ":");
427  }
428  return TZ_Ptr(new PTZ(tzstr));
429 }
430 
431 GncDateTimeImpl::GncDateTimeImpl(const char* str) :
432  m_time(unix_epoch, utc_zone)
433 {
434  if (!str || !str[0]) return;
435  TZ_Ptr tzptr;
436  try
437  {
438  if (auto res = fast_iso8601_utc_parse (str))
439  {
440  m_time = LDT_from_date_time(res->date(), res->time_of_day(), utc_zone);
441  return;
442  }
443  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})?)?$");
444  static const boost::regex non_delim("^(\\d{14}(?:\\.\\d{0,9})?)\\s*([+-]\\d{2}\\s*(:?\\d{2})?)?$");
445  PTime pdt;
446  boost::cmatch sm;
447  if (regex_match(str, sm, non_delim))
448  {
449  std::string time_str(sm[1]);
450  time_str.insert(8, "T");
451  pdt = boost::posix_time::from_iso_string(time_str);
452  }
453  else if (regex_match(str, sm, delim_iso))
454  {
455  pdt = boost::posix_time::time_from_string(sm[1]);
456  }
457  else
458  {
459  throw(std::invalid_argument("The date string was not formatted in a way that GncDateTime(const char*) knows how to parse."));
460  }
461  std::string tzstr("");
462  if (sm[2].matched)
463  tzstr += sm[2];
464  tzptr = tz_from_string(tzstr);
465  m_time = LDT_from_date_time(pdt.date(), pdt.time_of_day(), tzptr);
466  }
467  catch(boost::gregorian::bad_year&)
468  {
469  throw(std::invalid_argument("The date string was outside of the supported year range."));
470  }
471  catch(std::out_of_range&)
472  {
473  throw(std::invalid_argument("The date string was outside of the supported year range."));
474  }
475  /* Bug 767824: A GLib bug in parsing the UTC timezone on Windows may have
476  * created a bogus timezone of a random number of minutes. Since there are
477  * no fractional-hour timezones around the prime meridian we can safely
478  * check for this in files by resetting to UTC if there's a
479  * less-than-an-hour offset.
480  */
481  auto offset = tzptr->base_utc_offset().seconds();
482  if (offset != 0 && std::abs(offset) < 3600)
483  m_time = m_time.local_time_in(utc_zone);
484 }
485 
486 GncDateTimeImpl::operator time64() const
487 {
488  auto duration = m_time.utc_time() - unix_epoch;
489  auto secs = duration.ticks();
490  secs /= ticks_per_second;
491  return secs;
492 }
493 
494 GncDateTimeImpl::operator struct tm() const
495 {
496  struct tm time = to_tm(m_time);
497 #if HAVE_STRUCT_TM_GMTOFF
498  time.tm_gmtoff = offset();
499 #endif
500  return time;
501 }
502 
503 long
504 GncDateTimeImpl::offset() const
505 {
506  auto offset = m_time.local_time() - m_time.utc_time();
507  return offset.total_seconds();
508 }
509 
510 std::unique_ptr<GncDateImpl>
511 GncDateTimeImpl::date() const
512 {
513  return std::unique_ptr<GncDateImpl>(new GncDateImpl(m_time.local_time().date()));
514 }
515 
516 /* The 'O', 'E', and '-' format modifiers are not supported by
517  * boost's output facets. Remove them.
518  */
519 static inline std::string
520 normalize_format (const std::string& format)
521 {
522  bool is_pct = false;
523  std::string normalized;
524  std::remove_copy_if(
525  format.begin(), format.end(), back_inserter(normalized),
526  [&is_pct](char e){
527  bool r = (is_pct && (e == 'E' || e == 'O' || e == '-'));
528  is_pct = e == '%';
529  return r;
530  });
531  return normalized;
532 }
533 #ifdef __MINGW32__
534 constexpr size_t DATEBUFLEN = 100;
535 static std::string
536 win_date_format(std::string format, struct tm tm)
537 {
538  wchar_t buf[DATEBUFLEN];
539  memset(buf, 0, DATEBUFLEN);
540  std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> conv;
541  auto numchars = wcsftime(buf, DATEBUFLEN - 1, conv.from_bytes(format).c_str(), &tm);
542  return conv.to_bytes(buf);
543 }
544 
545 /* Microsoft's strftime uses the time zone flags differently from
546  * boost::date_time so we need to handle any before passing the
547  * format string to strftime.
548  */
549 inline std::string
550 win_format_tz_abbrev (std::string format, TZ_Ptr tz, bool is_dst)
551 {
552  size_t pos = format.find("%z");
553  if (pos != std::string::npos)
554  {
555  auto tzabbr = tz->has_dst() && is_dst ? tz->dst_zone_abbrev() :
556  tz->std_zone_abbrev();
557  format.replace(pos, 2, tzabbr);
558  }
559  return format;
560 }
561 
562 inline std::string
563 win_format_tz_name (std::string format, TZ_Ptr tz, bool is_dst)
564 {
565  size_t pos = format.find("%Z");
566  if (pos != std::string::npos)
567  {
568  auto tzname = tz->has_dst() && is_dst ? tz->dst_zone_name() :
569  tz->std_zone_name();
570  format.replace(pos, 2, tzname);
571  }
572  return format;
573 }
574 
575 inline std::string
576 win_format_tz_posix (std::string format, TZ_Ptr tz)
577 {
578  size_t pos = format.find("%ZP");
579  if (pos != std::string::npos)
580  format.replace(pos, 2, tz->to_posix_string());
581  return format;
582 }
583 
584 #endif
585 std::string
586 GncDateTimeImpl::format(const char* format) const
587 {
588 #ifdef __MINGW32__
589  auto tz = m_time.zone();
590  auto tm = static_cast<struct tm>(*this);
591  auto sformat = win_format_tz_abbrev(format, tz, tm.tm_isdst);
592  sformat = win_format_tz_name(sformat, tz, tm.tm_isdst);
593  sformat = win_format_tz_posix(sformat, tz);
594  return win_date_format(sformat, tm);
595 #else
596  using Facet = boost::local_time::local_time_facet;
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 << m_time;
601  return ss.str();
602 #endif
603 }
604 
605 std::string
606 GncDateTimeImpl::format_zulu(const char* format) const
607 {
608 #ifdef __MINGW32__
609  auto tz = utc_zone;
610  auto tm = static_cast<struct tm>(*this);
611  auto sformat = win_format_tz_abbrev(format, tz, tm.tm_isdst);
612  sformat = win_format_tz_name(sformat, tz, tm.tm_isdst);
613  sformat = win_format_tz_posix(sformat, tz);
614  return win_date_format(sformat, utc_tm());
615 #else
616  using Facet = boost::local_time::local_time_facet;
617  auto zulu_time = LDT{m_time.utc_time(), utc_zone};
618  auto output_facet(new Facet(normalize_format(format).c_str()));
619  std::stringstream ss;
620  ss.imbue(std::locale(gnc_get_locale(), output_facet));
621  ss << zulu_time;
622  return ss.str();
623 #endif
624 }
625 
626 std::string
627 GncDateTimeImpl::format_iso8601() const
628 {
629  auto str = boost::posix_time::to_iso_extended_string(m_time.utc_time());
630  str[10] = ' ';
631  return str.substr(0, 19);
632 }
633 
634 std::string
635 GncDateTimeImpl::timestamp()
636 {
637  GncDateTimeImpl gdt;
638  auto str = boost::posix_time::to_iso_string(gdt.m_time.local_time());
639  return str.substr(0, 8) + str.substr(9, 15);
640 }
641 
643 {
644  std::unique_ptr<icu::DateFormat> formatter;
645  std::unique_ptr<icu::Calendar> calendar;
646 };
647 
648 static ICUResources&
649 get_icu_resources()
650 {
651  static ICUResources rv;
652 
653  if (!rv.formatter)
654  {
655  icu::Locale locale;
656  if (auto lc_time_locale = setlocale (LC_TIME, nullptr))
657  {
658  std::string localeStr(lc_time_locale);
659  if (size_t dotPos = localeStr.find('.'); dotPos != std::string::npos)
660  localeStr = localeStr.substr(0, dotPos);
661 
662  locale = icu::Locale::createCanonical (localeStr.c_str());
663  }
664 
665  rv.formatter.reset(icu::DateFormat::createDateInstance(icu::DateFormat::kDefault, locale));
666  if (!rv.formatter)
667  throw std::invalid_argument("Cannot create date formatter.");
668 
669  UErrorCode status = U_ZERO_ERROR;
670  rv.calendar.reset(icu::Calendar::createInstance(locale, status));
671  if (U_FAILURE(status))
672  throw std::invalid_argument("Cannot create calendar instance.");
673 
674  rv.calendar->setLenient(false);
675  }
676 
677  return rv;
678 }
679 
680 static Date
681 gregorian_date_from_locale_string (const std::string& str)
682 {
683  ICUResources& resources = get_icu_resources();
684 
685  icu::UnicodeString input = icu::UnicodeString::fromUTF8(str);
686  icu::ParsePosition parsePos;
687  UDate date = resources.formatter->parse(input, parsePos);
688  if (parsePos.getErrorIndex() != -1 || parsePos.getIndex() != input.length())
689  throw std::invalid_argument ("Cannot parse string");
690 
691  UErrorCode status = U_ZERO_ERROR;
692  resources.calendar->setTime(date, status);
693  if (U_FAILURE(status))
694  throw std::invalid_argument ("Cannot set calendar time");
695 
696  return Date (resources.calendar->get(UCAL_YEAR, status),
697  resources.calendar->get(UCAL_MONTH, status) + 1,
698  resources.calendar->get(UCAL_DATE, status));
699 }
700 
701 /* Member function definitions for GncDateImpl.
702  */
703 GncDateImpl::GncDateImpl(const std::string str, const std::string fmt) :
704  m_greg(boost::gregorian::day_clock::local_day()) /* Temporarily initialized to today, will be used and adjusted in the code below */
705 {
706  auto iter = std::find_if(GncDate::c_formats.cbegin(), GncDate::c_formats.cend(),
707  [&fmt](const GncDateFormat& v){ return (v.m_fmt == fmt); } );
708  if (iter == GncDate::c_formats.cend())
709  throw std::invalid_argument(N_("Unknown date format specifier passed as argument."));
710 
711  if (iter->m_str_to_date)
712  {
713  try
714  {
715  m_greg = (*iter->m_str_to_date)(str);
716  return;
717  }
718  catch (...) {} // with any string->date exception, try regex
719  }
720 
721  if (iter->m_re.empty())
722  throw std::invalid_argument ("No regex pattern available");
723 
724  boost::regex r(iter->m_re);
725  boost::smatch what;
726  if(!boost::regex_search(str, what, r)) // regex didn't find a match
727  throw std::invalid_argument (N_("Value can't be parsed into a date using the selected date format."));
728 
729  // Bail out if a year was found with a yearless format specifier
730  auto fmt_has_year = (fmt.find('y') != std::string::npos);
731  if (!fmt_has_year && (what.length("YEAR") != 0))
732  throw std::invalid_argument (N_("Value appears to contain a year while the selected format forbids this."));
733 
734  int year;
735  if (fmt_has_year)
736  {
737  /* The input dates have a year, so use that one */
738  year = std::stoi (what.str("YEAR"));
739 
740  /* We assume two-digit years to be in the range 1969 - 2068. */
741  if (year < 69)
742  year += 2000;
743  else if (year < 100)
744  year += 1900;
745  }
746  else /* The input dates have no year, so use current year */
747  year = m_greg.year(); // Can use m_greg here as it was already initialized in the initializer list earlier
748 
749  m_greg = Date(year,
750  static_cast<Month>(std::stoi (what.str("MONTH"))),
751  std::stoi (what.str("DAY")));
752 }
753 
754 gnc_ymd
755 GncDateImpl::year_month_day() const
756 {
757  auto boost_ymd = m_greg.year_month_day();
758  return {boost_ymd.year, boost_ymd.month.as_number(), boost_ymd.day};
759 }
760 
761 std::string
762 GncDateImpl::format(const char* format) const
763 {
764 #ifdef __MINGW32__
765  return win_date_format(format, to_tm(m_greg));
766 #else
767  using Facet = boost::gregorian::date_facet;
768  std::stringstream ss;
769  //The stream destructor frees the facet, so it must be heap-allocated.
770  auto output_facet(new Facet(normalize_format(format).c_str()));
771  ss.imbue(std::locale(gnc_get_locale(), output_facet));
772  ss << m_greg;
773  return ss.str();
774 #endif
775 }
776 
777 bool operator<(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg < b.m_greg; }
778 bool operator>(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg > b.m_greg; }
779 bool operator==(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg == b.m_greg; }
780 bool operator<=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg <= b.m_greg; }
781 bool operator>=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg >= b.m_greg; }
782 bool operator!=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg != b.m_greg; }
783 
784 /* =================== Presentation-class Implementations ====================*/
785 /* GncDateTime */
786 
789  m_impl(new GncDateTimeImpl(time)) {}
790 GncDateTime::GncDateTime(const struct tm tm) :
791  m_impl(new GncDateTimeImpl(tm)) {}
792 GncDateTime::GncDateTime(const std::string& str) :
793  m_impl(new GncDateTimeImpl(str)) {}
794 GncDateTime::GncDateTime(const char* str) :
795  m_impl(new GncDateTimeImpl(str)) {}
796 GncDateTime::~GncDateTime() = default;
797 
798 GncDateTime::GncDateTime(const GncDate& date, DayPart part) :
799  m_impl(new GncDateTimeImpl(*(date.m_impl), part)) {}
800 
801 void
803 {
804  m_impl->now();
805 }
806 
807 GncDateTime::operator time64() const
808 {
809  return m_impl->operator time64();
810 }
811 
812 GncDateTime::operator struct tm() const
813 {
814  return m_impl->operator struct tm();
815 }
816 
817 long
819 {
820  return m_impl->offset();
821 }
822 
823 struct tm
824 GncDateTime::utc_tm() const
825 {
826  return m_impl->utc_tm();
827 }
828 
829 GncDate
831 {
832  return GncDate(m_impl->date());
833 }
834 
835 std::string
836 GncDateTime::format(const char* format) const
837 {
838  return m_impl->format(format);
839 }
840 
841 std::string
842 GncDateTime::format_zulu(const char* format) const
843 {
844  return m_impl->format_zulu(format);
845 }
846 
847 std::string
849 {
850  return m_impl->format_iso8601();
851 }
852 
853 std::string
855 {
856  return GncDateTimeImpl::timestamp();
857 }
858 
859 /* GncDate */
860 GncDate::GncDate() : m_impl{new GncDateImpl} {}
861 GncDate::GncDate(int year, int month, int day) :
862 m_impl(new GncDateImpl(year, month, day)) {}
863 GncDate::GncDate(const std::string str, const std::string fmt) :
864 m_impl(new GncDateImpl(str, fmt)) {}
865 GncDate::GncDate(std::unique_ptr<GncDateImpl> impl) :
866 m_impl(std::move(impl)) {}
868 m_impl(new GncDateImpl(*a.m_impl)) {}
869 GncDate::GncDate(GncDate&&) = default;
870 GncDate::~GncDate() = default;
871 
872 GncDate&
874 {
875  m_impl.reset(new GncDateImpl(*a.m_impl));
876  return *this;
877 }
878 GncDate&
879 GncDate::operator=(GncDate&&) = default;
880 
881 void
883 {
884  m_impl->today();
885 }
886 
887 std::string
888 GncDate::format(const char* format)
889 {
890  return m_impl->format(format);
891 }
892 
893 gnc_ymd
895 {
896  return m_impl->year_month_day();
897 }
898 
899 bool operator<(const GncDate& a, const GncDate& b) { return *(a.m_impl) < *(b.m_impl); }
900 bool operator>(const GncDate& a, const GncDate& b) { return *(a.m_impl) > *(b.m_impl); }
901 bool operator==(const GncDate& a, const GncDate& b) { return *(a.m_impl) == *(b.m_impl); }
902 bool operator<=(const GncDate& a, const GncDate& b) { return *(a.m_impl) <= *(b.m_impl); }
903 bool operator>=(const GncDate& a, const GncDate& b) { return *(a.m_impl) >= *(b.m_impl); }
904 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.