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  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 
418 static TZ_Ptr
419 tz_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 
432 GncDateTimeImpl::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 
487 GncDateTimeImpl::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 
495 GncDateTimeImpl::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 
504 long
505 GncDateTimeImpl::offset() const
506 {
507  auto offset = m_time.local_time() - m_time.utc_time();
508  return offset.total_seconds();
509 }
510 
511 std::unique_ptr<GncDateImpl>
512 GncDateTimeImpl::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  */
520 static inline std::string
521 normalize_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__
535 constexpr size_t DATEBUFLEN = 100;
536 static std::string
537 win_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  */
553 inline std::string
554 win_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 
566 inline std::string
567 win_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 
579 inline std::string
580 win_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
589 std::string
590 GncDateTimeImpl::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 
609 std::string
610 GncDateTimeImpl::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 
630 std::string
631 GncDateTimeImpl::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 
638 std::string
639 GncDateTimeImpl::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 
652 static ICUResources&
653 get_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 
684 static Date
685 gregorian_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  */
707 GncDateImpl::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::regex r(iter->m_re);
729  boost::smatch what;
730  if(!boost::regex_search(str, what, r)) // regex didn't find a match
731  throw std::invalid_argument (N_("Value can't be parsed into a date using the selected date format."));
732 
733  // Bail out if a year was found with a yearless format specifier
734  auto fmt_has_year = (fmt.find('y') != std::string::npos);
735  if (!fmt_has_year && (what.length("YEAR") != 0))
736  throw std::invalid_argument (N_("Value appears to contain a year while the selected format forbids this."));
737 
738  int year;
739  if (fmt_has_year)
740  {
741  /* The input dates have a year, so use that one */
742  year = std::stoi (what.str("YEAR"));
743 
744  /* We assume two-digit years to be in the range 1969 - 2068. */
745  if (year < 69)
746  year += 2000;
747  else if (year < 100)
748  year += 1900;
749  }
750  else /* The input dates have no year, so use current year */
751  year = m_greg.year(); // Can use m_greg here as it was already initialized in the initializer list earlier
752 
753  m_greg = Date(year,
754  static_cast<Month>(std::stoi (what.str("MONTH"))),
755  std::stoi (what.str("DAY")));
756 }
757 
758 gnc_ymd
759 GncDateImpl::year_month_day() const
760 {
761  auto boost_ymd = m_greg.year_month_day();
762  return {boost_ymd.year, boost_ymd.month.as_number(), boost_ymd.day};
763 }
764 
765 std::string
766 GncDateImpl::format(const char* format) const
767 {
768 #ifdef __MINGW32__
769  return win_date_format(format, to_tm(m_greg));
770 #else
771  using Facet = boost::gregorian::date_facet;
772  std::stringstream ss;
773  //The stream destructor frees the facet, so it must be heap-allocated.
774  auto output_facet(new Facet(normalize_format(format).c_str()));
775  ss.imbue(std::locale(gnc_get_locale(), output_facet));
776  ss << m_greg;
777  return ss.str();
778 #endif
779 }
780 
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 bool operator==(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg == b.m_greg; }
784 bool operator<=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg <= b.m_greg; }
785 bool operator>=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg >= b.m_greg; }
786 bool operator!=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg != b.m_greg; }
787 
788 /* =================== Presentation-class Implementations ====================*/
789 /* GncDateTime */
790 
793  m_impl(new GncDateTimeImpl(time)) {}
794 GncDateTime::GncDateTime(const struct tm tm) :
795  m_impl(new GncDateTimeImpl(tm)) {}
796 GncDateTime::GncDateTime(const std::string& str) :
797  m_impl(new GncDateTimeImpl(str)) {}
798 GncDateTime::GncDateTime(const char* str) :
799  m_impl(new GncDateTimeImpl(str)) {}
800 GncDateTime::~GncDateTime() = default;
801 
802 GncDateTime::GncDateTime(const GncDate& date, DayPart part) :
803  m_impl(new GncDateTimeImpl(*(date.m_impl), part)) {}
804 
805 void
807 {
808  m_impl->now();
809 }
810 
811 GncDateTime::operator time64() const
812 {
813  return m_impl->operator time64();
814 }
815 
816 GncDateTime::operator struct tm() const
817 {
818  return m_impl->operator struct tm();
819 }
820 
821 long
823 {
824  return m_impl->offset();
825 }
826 
827 struct tm
828 GncDateTime::utc_tm() const
829 {
830  return m_impl->utc_tm();
831 }
832 
833 GncDate
835 {
836  return GncDate(m_impl->date());
837 }
838 
839 std::string
840 GncDateTime::format(const char* format) const
841 {
842  return m_impl->format(format);
843 }
844 
845 std::string
846 GncDateTime::format_zulu(const char* format) const
847 {
848  return m_impl->format_zulu(format);
849 }
850 
851 std::string
853 {
854  return m_impl->format_iso8601();
855 }
856 
857 std::string
859 {
860  return GncDateTimeImpl::timestamp();
861 }
862 
863 /* GncDate */
864 GncDate::GncDate() : m_impl{new GncDateImpl} {}
865 GncDate::GncDate(int year, int month, int day) :
866 m_impl(new GncDateImpl(year, month, day)) {}
867 GncDate::GncDate(const std::string str, const std::string fmt) :
868 m_impl(new GncDateImpl(str, fmt)) {}
869 GncDate::GncDate(std::unique_ptr<GncDateImpl> impl) :
870 m_impl(std::move(impl)) {}
872 m_impl(new GncDateImpl(*a.m_impl)) {}
873 GncDate::GncDate(GncDate&&) = default;
874 GncDate::~GncDate() = default;
875 
876 GncDate&
878 {
879  m_impl.reset(new GncDateImpl(*a.m_impl));
880  return *this;
881 }
882 GncDate&
883 GncDate::operator=(GncDate&&) = default;
884 
885 void
887 {
888  m_impl->today();
889 }
890 
891 std::string
892 GncDate::format(const char* format)
893 {
894  return m_impl->format(format);
895 }
896 
897 gnc_ymd
899 {
900  return m_impl->year_month_day();
901 }
902 
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); }
905 bool operator==(const GncDate& a, const GncDate& b) { return *(a.m_impl) == *(b.m_impl); }
906 bool operator<=(const GncDate& a, const GncDate& b) { return *(a.m_impl) <= *(b.m_impl); }
907 bool operator>=(const GncDate& a, const GncDate& b) { return *(a.m_impl) >= *(b.m_impl); }
908 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.