GnuCash  4.8a-134-g214de30c7+
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 extern "C"
26 {
27 #include <config.h>
28 #include "platform.h"
29 }
30 #include <boost/date_time/gregorian/gregorian.hpp>
31 #include <boost/date_time/posix_time/posix_time.hpp>
32 #include <boost/date_time/local_time/local_time.hpp>
33 #include <boost/locale.hpp>
34 #include <boost/regex.hpp>
35 #include <libintl.h>
36 #include <locale.h>
37 #include <map>
38 #include <memory>
39 #include <iostream>
40 #include <sstream>
41 #include <string>
42 #include <vector>
43 #ifdef __MINGW32__
44 #include <codecvt>
45 #endif
46 #include <gnc-locale-utils.hpp>
47 #include "gnc-timezone.hpp"
48 #include "gnc-datetime.hpp"
49 #include "qoflog.h"
50 
51 static const char* log_module = "gnc.engine";
52 
53 #define N_(string) string //So that xgettext will find it
54 
55 using PTZ = boost::local_time::posix_time_zone;
56 using Date = boost::gregorian::date;
57 using Month = boost::gregorian::greg_month;
58 using PTime = boost::posix_time::ptime;
59 using LDT = boost::local_time::local_date_time;
60 using Duration = boost::posix_time::time_duration;
61 using LDTBase = boost::local_time::local_date_time_base<PTime, boost::date_time::time_zone_base<PTime, char>>;
62 using boost::date_time::not_a_date_time;
63 using time64 = int64_t;
64 
65 static const TimeZoneProvider ltzp;
66 static const TimeZoneProvider* tzp = &ltzp;
67 
68 // For converting to/from POSIX time.
69 static const PTime unix_epoch (Date(1970, boost::gregorian::Jan, 1),
70  boost::posix_time::seconds(0));
71 static const TZ_Ptr utc_zone(new boost::local_time::posix_time_zone("UTC-0"));
72 
73 /* Backdoor to enable unittests to temporarily override the timezone: */
74 void _set_tzp(TimeZoneProvider& tz);
75 void _reset_tzp();
76 
77 /* 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. */
78 #ifndef BOOST_DATE_TIME_HAS_NANOSECONDS
79 static constexpr auto ticks_per_second = INT64_C(1000000);
80 #else
81 static constexpr auto ticks_per_second = INT64_C(1000000000);
82 #endif
83 
84 /* Vector of date formats understood by gnucash and corresponding regex
85  * to parse each from an external source
86  * Note: while the format names are using a "-" as separator, the
87  * regexes will accept any of "-/.' " and will also work for dates
88  * without separators.
89  */
90 const std::vector<GncDateFormat> GncDate::c_formats ({
92  N_("y-m-d"),
93  "(?:" // either y-m-d
94  "(?<YEAR>[0-9]+)[-/.' ]+"
95  "(?<MONTH>[0-9]+)[-/.' ]+"
96  "(?<DAY>[0-9]+)"
97  "|" // or CCYYMMDD
98  "(?<YEAR>[0-9]{4})"
99  "(?<MONTH>[0-9]{2})"
100  "(?<DAY>[0-9]{2})"
101  ")"
102  },
103  GncDateFormat {
104  N_("d-m-y"),
105  "(?:" // either d-m-y
106  "(?<DAY>[0-9]+)[-/.' ]+"
107  "(?<MONTH>[0-9]+)[-/.' ]+"
108  "(?<YEAR>[0-9]+)"
109  "|" // or DDMMCCYY
110  "(?<DAY>[0-9]{2})"
111  "(?<MONTH>[0-9]{2})"
112  "(?<YEAR>[0-9]{4})"
113  ")"
114  },
115  GncDateFormat {
116  N_("m-d-y"),
117  "(?:" // either m-d-y
118  "(?<MONTH>[0-9]+)[-/.' ]+"
119  "(?<DAY>[0-9]+)[-/.' ]+"
120  "(?<YEAR>[0-9]+)"
121  "|" // or MMDDCCYY
122  "(?<MONTH>[0-9]{2})"
123  "(?<DAY>[0-9]{2})"
124  "(?<YEAR>[0-9]{4})"
125  ")"
126  },
127  // Note year is still checked for in the regexes below
128  // This is to be able to raise an error if one is found for a yearless date format
129  GncDateFormat {
130  (N_("d-m")),
131  "(?:" // either d-m(-y)
132  "(?<DAY>[0-9]+)[-/.' ]+"
133  "(?<MONTH>[0-9]+)(?:[-/.' ]+"
134  "(?<YEAR>[0-9]+))?"
135  "|" // or DDMM(CCYY)
136  "(?<DAY>[0-9]{2})"
137  "(?<MONTH>[0-9]{2})"
138  "(?<YEAR>[0-9]+)?"
139  ")"
140  },
141  GncDateFormat {
142  (N_("m-d")),
143  "(?:" // either m-d(-y)
144  "(?<MONTH>[0-9]+)[-/.' ]+"
145  "(?<DAY>[0-9]+)(?:[-/.' ]+"
146  "(?<YEAR>[0-9]+))?"
147  "|" // or MMDD(CCYY)
148  "(?<MONTH>[0-9]{2})"
149  "(?<DAY>[0-9]{2})"
150  "(?<YEAR>[0-9]+)?"
151  ")"
152  }
153 });
154 
157 static LDT
158 LDT_from_unix_local(const time64 time)
159 {
160  try
161  {
162  PTime temp(unix_epoch.date(),
163  boost::posix_time::hours(time / 3600) +
164  boost::posix_time::seconds(time % 3600));
165  auto tz = tzp->get(temp.date().year());
166  return LDT(temp, tz);
167  }
168  catch(boost::gregorian::bad_year&)
169  {
170  throw(std::invalid_argument("Time value is outside the supported year range."));
171  }
172 }
173 /* If a date-time falls in a DST transition the LDT constructor will
174  * fail because either the date-time doesn't exist (when starting DST
175  * because the transition skips an hour) or is ambiguous (when ending
176  * because the transition hour is repeated). We try again an hour
177  * later to be outside the DST transition. When starting DST that's
178  * now the correct time but at the end of DST we need to set the
179  * returned time back an hour.
180  */
181 static LDT
182 LDT_with_pushup(const Date& tdate, const Duration& tdur, const TZ_Ptr tz,
183  bool putback)
184 {
185  static const boost::posix_time::hours pushup{1};
186  LDT ldt{tdate, tdur + pushup, tz, LDTBase::NOT_DATE_TIME_ON_ERROR};
187  if (ldt.is_special())
188  {
189  std::string error{"Couldn't create a valid datetime at "};
190  error += to_simple_string(tdate) + " ";
191  error += to_simple_string(tdur) + " TZ ";
192  error += tz->std_zone_abbrev();
193  throw(std::invalid_argument{error});
194  }
195  if (putback)
196  ldt -= pushup;
197  return ldt;
198 }
199 
200 static LDT
201 LDT_from_date_time(const Date& tdate, const Duration& tdur, const TZ_Ptr tz)
202 {
203 
204  try
205  {
206  LDT ldt(tdate, tdur, tz, LDTBase::EXCEPTION_ON_ERROR);
207  return ldt;
208  }
209  catch (const boost::local_time::time_label_invalid& err)
210  {
211  return LDT_with_pushup(tdate, tdur, tz, false);
212  }
213 
214  catch (const boost::local_time::ambiguous_result& err)
215  {
216  return LDT_with_pushup(tdate, tdur, tz, true);
217  }
218 
219  catch(boost::gregorian::bad_year&)
220  {
221  throw(std::invalid_argument("Time value is outside the supported year range."));
222  }
223 
224 }
225 
226 static LDT
227 LDT_from_date_daypart(const Date& date, DayPart part, const TZ_Ptr tz)
228 {
229  using hours = boost::posix_time::hours;
230 
231  static const Duration day_begin{0, 0, 0};
232  static const Duration day_neutral{10, 59, 0};
233  static const Duration day_end{23, 59, 59};
234 
235 
236  switch (part)
237  {
238  case DayPart::start:
239  return LDT_from_date_time(date, day_begin, tz);
240  case DayPart::end:
241  return LDT_from_date_time(date, day_end, tz);
242  default: // To stop gcc from emitting a control reaches end of non-void function.
243  case DayPart::neutral:
244  PTime pt{date, day_neutral};
245  LDT lt{pt, tz};
246  auto offset = lt.local_time() - lt.utc_time();
247  if (offset < hours(-10))
248  lt -= hours(offset.hours() + 10);
249  if (offset > hours(13))
250  lt += hours(13 - offset.hours());
251  return lt;
252  }
253 }
254 
255 static LDT
256 LDT_from_struct_tm(const struct tm tm)
257 {
258  try
259  {
260  Date tdate{boost::gregorian::date_from_tm(tm)};
261  Duration tdur{boost::posix_time::time_duration(tm.tm_hour, tm.tm_min,
262  tm.tm_sec, 0)};
263  TZ_Ptr tz{tzp->get(tdate.year())};
264  return LDT_from_date_time(tdate, tdur, tz);
265  }
266  catch(const boost::gregorian::bad_year&)
267  {
268  throw(std::invalid_argument{"Time value is outside the supported year range."});
269  }
270 }
271 
272 void
273 _set_tzp(TimeZoneProvider& new_tzp)
274 {
275  tzp = &new_tzp;
276 }
277 
278 void
279 _reset_tzp()
280 {
281  tzp = &ltzp;
282 }
283 
285 {
286 public:
287  GncDateTimeImpl() : m_time(boost::local_time::local_sec_clock::local_time(tzp->get(boost::gregorian::day_clock::local_day().year()))) {}
288  GncDateTimeImpl(const time64 time) : m_time(LDT_from_unix_local(time)) {}
289  GncDateTimeImpl(const struct tm tm) : m_time(LDT_from_struct_tm(tm)) {}
290  GncDateTimeImpl(const GncDateImpl& date, DayPart part = DayPart::neutral);
291  GncDateTimeImpl(std::string str);
292  GncDateTimeImpl(PTime&& pt) : m_time(pt, tzp->get(pt.date().year())) {}
293  GncDateTimeImpl(LDT&& ldt) : m_time(ldt) {}
294 
295  operator time64() const;
296  operator struct tm() const;
297  void now() { m_time = boost::local_time::local_sec_clock::local_time(tzp->get(boost::gregorian::day_clock::local_day().year())); }
298  long offset() const;
299  struct tm utc_tm() const { return to_tm(m_time.utc_time()); }
300  std::unique_ptr<GncDateImpl> date() const;
301  std::string format(const char* format) const;
302  std::string format_zulu(const char* format) const;
303  std::string format_iso8601() const;
304  static std::string timestamp();
305 private:
306  LDT m_time;
307 };
308 
312 {
313 public:
314  GncDateImpl(): m_greg(boost::gregorian::day_clock::local_day()) {}
315  GncDateImpl(const int year, const int month, const int day) :
316  m_greg(year, static_cast<Month>(month), day) {}
317  GncDateImpl(Date d) : m_greg(d) {}
318  GncDateImpl(const std::string str, const std::string fmt);
319 
320  void today() { m_greg = boost::gregorian::day_clock::local_day(); }
321  ymd year_month_day() const;
322  std::string format(const char* format) const;
323  std::string format_zulu(const char* format) const {
324  return this->format(format);
325  }
326 private:
327  Date m_greg;
328 
329  friend GncDateTimeImpl::GncDateTimeImpl(const GncDateImpl&, DayPart);
330  friend bool operator<(const GncDateImpl&, const GncDateImpl&);
331  friend bool operator>(const GncDateImpl&, const GncDateImpl&);
332  friend bool operator==(const GncDateImpl&, const GncDateImpl&);
333  friend bool operator<=(const GncDateImpl&, const GncDateImpl&);
334  friend bool operator>=(const GncDateImpl&, const GncDateImpl&);
335  friend bool operator!=(const GncDateImpl&, const GncDateImpl&);
336 };
337 
338 /* Needs to be separately defined so that the friend decl can grant
339  * access to date.m_greg.
340  */
341 GncDateTimeImpl::GncDateTimeImpl(const GncDateImpl& date, DayPart part) :
342  m_time{LDT_from_date_daypart(date.m_greg, part,
343  tzp->get(date.m_greg.year()))} {}
344 
345 /* Member function definitions for GncDateTimeImpl.
346  */
347 
348 static TZ_Ptr
349 tz_from_string(std::string str)
350 {
351  if (str.empty()) return utc_zone;
352  std::string tzstr = "XXX" + str;
353  if (tzstr.length() > 6 && tzstr[6] != ':') //6 for XXXsHH, s is + or -
354  tzstr.insert(6, ":");
355  if (tzstr.length() > 9 && tzstr[9] != ':') //9 for XXXsHH:MM
356  {
357  tzstr.insert(9, ":");
358  }
359  return TZ_Ptr(new PTZ(tzstr));
360 }
361 
362 GncDateTimeImpl::GncDateTimeImpl(std::string str) :
363  m_time(unix_epoch, utc_zone)
364 {
365  if (str.empty()) return;
366  TZ_Ptr tzptr;
367  try
368  {
369  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})?)?$");
370  static const boost::regex non_delim("^(\\d{14}(?:\\.\\d{0,9})?)\\s*([+-]\\d{2}\\s*(:?\\d{2})?)?$");
371  PTime pdt;
372  boost::smatch sm;
373  if (regex_match(str, sm, non_delim))
374  {
375  std::string time_str(sm[1]);
376  time_str.insert(8, "T");
377  pdt = boost::posix_time::from_iso_string(time_str);
378  }
379  else if (regex_match(str, sm, delim_iso))
380  {
381  pdt = boost::posix_time::time_from_string(sm[1]);
382  }
383  else
384  {
385  throw(std::invalid_argument("The date string was not formatted in a way that GncDateTime(std::string) knows how to parse."));
386  }
387  std::string tzstr("");
388  if (sm[2].matched)
389  tzstr += sm[2];
390  tzptr = tz_from_string(tzstr);
391  m_time = LDT_from_date_time(pdt.date(), pdt.time_of_day(), tzptr);
392  }
393  catch(boost::gregorian::bad_year&)
394  {
395  throw(std::invalid_argument("The date string was outside of the supported year range."));
396  }
397  /* Bug 767824: A GLib bug in parsing the UTC timezone on Windows may have
398  * created a bogus timezone of a random number of minutes. Since there are
399  * no fractional-hour timezones around the prime meridian we can safely
400  * check for this in files by resetting to UTC if there's a
401  * less-than-an-hour offset.
402  */
403  auto offset = tzptr->base_utc_offset().seconds();
404  if (offset != 0 && std::abs(offset) < 3600)
405  m_time = m_time.local_time_in(utc_zone);
406 }
407 
408 GncDateTimeImpl::operator time64() const
409 {
410  auto duration = m_time.utc_time() - unix_epoch;
411  auto secs = duration.ticks();
412  secs /= ticks_per_second;
413  return secs;
414 }
415 
416 GncDateTimeImpl::operator struct tm() const
417 {
418  struct tm time = to_tm(m_time);
419 #if HAVE_STRUCT_TM_GMTOFF
420  time.tm_gmtoff = offset();
421 #endif
422  return time;
423 }
424 
425 long
426 GncDateTimeImpl::offset() const
427 {
428  auto offset = m_time.local_time() - m_time.utc_time();
429  return offset.total_seconds();
430 }
431 
432 std::unique_ptr<GncDateImpl>
433 GncDateTimeImpl::date() const
434 {
435  return std::unique_ptr<GncDateImpl>(new GncDateImpl(m_time.local_time().date()));
436 }
437 
438 /* The 'O', 'E', and '-' format modifiers are not supported by
439  * boost's output facets. Remove them.
440  */
441 static inline std::string
442 normalize_format (const std::string& format)
443 {
444  bool is_pct = false;
445  std::string normalized;
446  std::remove_copy_if(
447  format.begin(), format.end(), back_inserter(normalized),
448  [&is_pct](char e){
449  bool r = (is_pct && (e == 'E' || e == 'O' || e == '-'));
450  is_pct = e == '%';
451  return r;
452  });
453  return normalized;
454 }
455 #ifdef __MINGW32__
456 constexpr size_t DATEBUFLEN = 100;
457 static std::string
458 win_date_format(std::string format, struct tm tm)
459 {
460  wchar_t buf[DATEBUFLEN];
461  memset(buf, 0, DATEBUFLEN);
462  std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> conv;
463  auto numchars = wcsftime(buf, DATEBUFLEN - 1, conv.from_bytes(format).c_str(), &tm);
464  return conv.to_bytes(buf);
465 }
466 
467 /* Microsoft's strftime uses the time zone flags differently from
468  * boost::date_time so we need to handle any before passing the
469  * format string to strftime.
470  */
471 inline std::string
472 win_format_tz_abbrev (std::string format, TZ_Ptr tz, bool is_dst)
473 {
474  size_t pos = format.find("%z");
475  if (pos != std::string::npos)
476  {
477  auto tzabbr = tz->has_dst() && is_dst ? tz->dst_zone_abbrev() :
478  tz->std_zone_abbrev();
479  format.replace(pos, 2, tzabbr);
480  }
481  return format;
482 }
483 
484 inline std::string
485 win_format_tz_name (std::string format, TZ_Ptr tz, bool is_dst)
486 {
487  size_t pos = format.find("%Z");
488  if (pos != std::string::npos)
489  {
490  auto tzname = tz->has_dst() && is_dst ? tz->dst_zone_name() :
491  tz->std_zone_name();
492  format.replace(pos, 2, tzname);
493  }
494  return format;
495 }
496 
497 inline std::string
498 win_format_tz_posix (std::string format, TZ_Ptr tz)
499 {
500  size_t pos = format.find("%ZP");
501  if (pos != std::string::npos)
502  format.replace(pos, 2, tz->to_posix_string());
503  return format;
504 }
505 
506 #endif
507 std::string
508 GncDateTimeImpl::format(const char* format) const
509 {
510 #ifdef __MINGW32__
511  auto tz = m_time.zone();
512  auto tm = static_cast<struct tm>(*this);
513  auto sformat = win_format_tz_abbrev(format, tz, tm.tm_isdst);
514  sformat = win_format_tz_name(sformat, tz, tm.tm_isdst);
515  sformat = win_format_tz_posix(sformat, tz);
516  return win_date_format(sformat, tm);
517 #else
518  using Facet = boost::local_time::local_time_facet;
519  auto output_facet(new Facet(normalize_format(format).c_str()));
520  std::stringstream ss;
521  ss.imbue(std::locale(gnc_get_locale(), output_facet));
522  ss << m_time;
523  return ss.str();
524 #endif
525 }
526 
527 std::string
528 GncDateTimeImpl::format_zulu(const char* format) const
529 {
530 #ifdef __MINGW32__
531  auto tz = m_time.zone();
532  auto tm = static_cast<struct tm>(*this);
533  auto sformat = win_format_tz_abbrev(format, tz, tm.tm_isdst);
534  sformat = win_format_tz_name(sformat, tz, tm.tm_isdst);
535  sformat = win_format_tz_posix(sformat, tz);
536  return win_date_format(sformat, utc_tm());
537 #else
538  using Facet = boost::local_time::local_time_facet;
539  auto zulu_time = LDT{m_time.utc_time(), utc_zone};
540  auto output_facet(new Facet(normalize_format(format).c_str()));
541  std::stringstream ss;
542  ss.imbue(std::locale(gnc_get_locale(), output_facet));
543  ss << zulu_time;
544  return ss.str();
545 #endif
546 }
547 
548 std::string
549 GncDateTimeImpl::format_iso8601() const
550 {
551  auto str = boost::posix_time::to_iso_extended_string(m_time.utc_time());
552  str[10] = ' ';
553  return str.substr(0, 19);
554 }
555 
556 std::string
557 GncDateTimeImpl::timestamp()
558 {
559  GncDateTimeImpl gdt;
560  auto str = boost::posix_time::to_iso_string(gdt.m_time.local_time());
561  return str.substr(0, 8) + str.substr(9, 15);
562 }
563 
564 /* Member function definitions for GncDateImpl.
565  */
566 GncDateImpl::GncDateImpl(const std::string str, const std::string fmt) :
567  m_greg(boost::gregorian::day_clock::local_day()) /* Temporarily initialized to today, will be used and adjusted in the code below */
568 {
569  auto iter = std::find_if(GncDate::c_formats.cbegin(), GncDate::c_formats.cend(),
570  [&fmt](const GncDateFormat& v){ return (v.m_fmt == fmt); } );
571  if (iter == GncDate::c_formats.cend())
572  throw std::invalid_argument(N_("Unknown date format specifier passed as argument."));
573 
574  boost::regex r(iter->m_re);
575  boost::smatch what;
576  if(!boost::regex_search(str, what, r)) // regex didn't find a match
577  throw std::invalid_argument (N_("Value can't be parsed into a date using the selected date format."));
578 
579  // Bail out if a year was found with a yearless format specifier
580  auto fmt_has_year = (fmt.find('y') != std::string::npos);
581  if (!fmt_has_year && (what.length("YEAR") != 0))
582  throw std::invalid_argument (N_("Value appears to contain a year while the selected format forbids this."));
583 
584  int year;
585  if (fmt_has_year)
586  {
587  /* The input dates have a year, so use that one */
588  year = std::stoi (what.str("YEAR"));
589 
590  /* We assume two-digit years to be in the range 1969 - 2068. */
591  if (year < 69)
592  year += 2000;
593  else if (year < 100)
594  year += 1900;
595  }
596  else /* The input dates have no year, so use current year */
597  year = m_greg.year(); // Can use m_greg here as it was already initialized in the initializer list earlier
598 
599  m_greg = Date(year,
600  static_cast<Month>(std::stoi (what.str("MONTH"))),
601  std::stoi (what.str("DAY")));
602 }
603 
604 ymd
605 GncDateImpl::year_month_day() const
606 {
607  auto boost_ymd = m_greg.year_month_day();
608  return {boost_ymd.year, boost_ymd.month.as_number(), boost_ymd.day};
609 }
610 
611 std::string
612 GncDateImpl::format(const char* format) const
613 {
614 #ifdef __MINGW32__
615  return win_date_format(format, to_tm(m_greg));
616 #else
617  using Facet = boost::gregorian::date_facet;
618  std::stringstream ss;
619  //The stream destructor frees the facet, so it must be heap-allocated.
620  auto output_facet(new Facet(normalize_format(format).c_str()));
621  ss.imbue(std::locale(gnc_get_locale(), output_facet));
622  ss << m_greg;
623  return ss.str();
624 #endif
625 }
626 
627 bool operator<(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg < b.m_greg; }
628 bool operator>(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg > b.m_greg; }
629 bool operator==(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg == b.m_greg; }
630 bool operator<=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg <= b.m_greg; }
631 bool operator>=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg >= b.m_greg; }
632 bool operator!=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg != b.m_greg; }
633 
634 /* =================== Presentation-class Implementations ====================*/
635 /* GncDateTime */
636 
639  m_impl(new GncDateTimeImpl(time)) {}
640 GncDateTime::GncDateTime(const struct tm tm) :
641  m_impl(new GncDateTimeImpl(tm)) {}
642 GncDateTime::GncDateTime(std::string str) :
643  m_impl(new GncDateTimeImpl(str)) {}
644 GncDateTime::~GncDateTime() = default;
645 
646 GncDateTime::GncDateTime(const GncDate& date, DayPart part) :
647  m_impl(new GncDateTimeImpl(*(date.m_impl), part)) {}
648 
649 void
651 {
652  m_impl->now();
653 }
654 
655 GncDateTime::operator time64() const
656 {
657  return m_impl->operator time64();
658 }
659 
660 GncDateTime::operator struct tm() const
661 {
662  return m_impl->operator struct tm();
663 }
664 
665 long
667 {
668  return m_impl->offset();
669 }
670 
671 struct tm
672 GncDateTime::utc_tm() const
673 {
674  return m_impl->utc_tm();
675 }
676 
677 GncDate
679 {
680  return GncDate(m_impl->date());
681 }
682 
683 std::string
684 GncDateTime::format(const char* format) const
685 {
686  return m_impl->format(format);
687 }
688 
689 std::string
690 GncDateTime::format_zulu(const char* format) const
691 {
692  return m_impl->format_zulu(format);
693 }
694 
695 std::string
697 {
698  return m_impl->format_iso8601();
699 }
700 
701 std::string
703 {
704  return GncDateTimeImpl::timestamp();
705 }
706 
707 /* GncDate */
708 GncDate::GncDate() : m_impl{new GncDateImpl} {}
709 GncDate::GncDate(int year, int month, int day) :
710 m_impl(new GncDateImpl(year, month, day)) {}
711 GncDate::GncDate(const std::string str, const std::string fmt) :
712 m_impl(new GncDateImpl(str, fmt)) {}
713 GncDate::GncDate(std::unique_ptr<GncDateImpl> impl) :
714 m_impl(std::move(impl)) {}
716 m_impl(new GncDateImpl(*a.m_impl)) {}
717 GncDate::GncDate(GncDate&&) = default;
718 GncDate::~GncDate() = default;
719 
720 GncDate&
722 {
723  m_impl.reset(new GncDateImpl(*a.m_impl));
724  return *this;
725 }
726 GncDate&
727 GncDate::operator=(GncDate&&) = default;
728 
729 void
731 {
732  m_impl->today();
733 }
734 
735 std::string
736 GncDate::format(const char* format)
737 {
738  return m_impl->format(format);
739 }
740 
741 ymd
743 {
744  return m_impl->year_month_day();
745 }
746 
747 bool operator<(const GncDate& a, const GncDate& b) { return *(a.m_impl) < *(b.m_impl); }
748 bool operator>(const GncDate& a, const GncDate& b) { return *(a.m_impl) > *(b.m_impl); }
749 bool operator==(const GncDate& a, const GncDate& b) { return *(a.m_impl) == *(b.m_impl); }
750 bool operator<=(const GncDate& a, const GncDate& b) { return *(a.m_impl) <= *(b.m_impl); }
751 bool operator>=(const GncDate& a, const GncDate& b) { return *(a.m_impl) >= *(b.m_impl); }
752 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.
GnuCash DateFormat 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.
~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.
ymd year_month_day() const
Get the year, month, and day from the date as a ymd.
gint64 time64
Many systems, including Microsoft Windows and BSD-derived Unixes like Darwin, are retaining the int-3...
Definition: gnc-date.h:93
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.