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> 46 #include <gnc-locale-utils.hpp> 47 #include "gnc-timezone.hpp" 48 #include "gnc-datetime.hpp" 51 static const char* log_module =
"gnc.engine";
53 #define N_(string) string //So that xgettext will find it 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;
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"));
78 #ifndef BOOST_DATE_TIME_HAS_NANOSECONDS 79 static constexpr
auto ticks_per_second = INT64_C(1000000);
81 static constexpr
auto ticks_per_second = INT64_C(1000000000);
94 "(?<YEAR>[0-9]+)[-/.' ]+" 95 "(?<MONTH>[0-9]+)[-/.' ]+" 106 "(?<DAY>[0-9]+)[-/.' ]+" 107 "(?<MONTH>[0-9]+)[-/.' ]+" 118 "(?<MONTH>[0-9]+)[-/.' ]+" 119 "(?<DAY>[0-9]+)[-/.' ]+" 132 "(?<DAY>[0-9]+)[-/.' ]+" 133 "(?<MONTH>[0-9]+)(?:[-/.' ]+" 144 "(?<MONTH>[0-9]+)[-/.' ]+" 145 "(?<DAY>[0-9]+)(?:[-/.' ]+" 158 LDT_from_unix_local(
const time64 time)
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);
168 catch(boost::gregorian::bad_year&)
170 throw(std::invalid_argument(
"Time value is outside the supported year range."));
182 LDT_with_pushup(
const Date& tdate,
const Duration& tdur,
const TZ_Ptr tz,
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())
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});
201 LDT_from_date_time(
const Date& tdate,
const Duration& tdur,
const TZ_Ptr tz)
206 LDT ldt(tdate, tdur, tz, LDTBase::EXCEPTION_ON_ERROR);
209 catch (
const boost::local_time::time_label_invalid& err)
211 return LDT_with_pushup(tdate, tdur, tz,
false);
214 catch (
const boost::local_time::ambiguous_result& err)
216 return LDT_with_pushup(tdate, tdur, tz,
true);
219 catch(boost::gregorian::bad_year&)
221 throw(std::invalid_argument(
"Time value is outside the supported year range."));
227 LDT_from_date_daypart(
const Date& date, DayPart part,
const TZ_Ptr tz)
229 using hours = boost::posix_time::hours;
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};
239 return LDT_from_date_time(date, day_begin, tz);
241 return LDT_from_date_time(date, day_end, tz);
243 case DayPart::neutral:
244 PTime pt{date, day_neutral};
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());
256 LDT_from_struct_tm(
const struct tm tm)
260 Date tdate{boost::gregorian::date_from_tm(tm)};
261 Duration tdur{boost::posix_time::time_duration(tm.tm_hour, tm.tm_min,
263 TZ_Ptr tz{tzp->get(tdate.year())};
264 return LDT_from_date_time(tdate, tdur, tz);
266 catch(
const boost::gregorian::bad_year&)
268 throw(std::invalid_argument{
"Time value is outside the supported year range."});
287 GncDateTimeImpl() : m_time(boost::local_time::local_sec_clock::local_time(tzp->get(boost::gregorian::day_clock::local_day().year()))) {}
289 GncDateTimeImpl(
const struct tm tm) : m_time(LDT_from_struct_tm(tm)) {}
292 GncDateTimeImpl(PTime&& pt) : m_time(pt, tzp->get(pt.date().year())) {}
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())); }
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();
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) {}
318 GncDateImpl(
const std::string str,
const std::string fmt);
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);
329 friend GncDateTimeImpl::GncDateTimeImpl(
const GncDateImpl&, DayPart);
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()))} {}
349 tz_from_string(std::string str)
351 if (str.empty())
return utc_zone;
352 std::string tzstr =
"XXX" + str;
353 if (tzstr.length() > 6 && tzstr[6] !=
':')
354 tzstr.insert(6,
":");
355 if (tzstr.length() > 9 && tzstr[9] !=
':')
357 tzstr.insert(9,
":");
359 return TZ_Ptr(
new PTZ(tzstr));
362 GncDateTimeImpl::GncDateTimeImpl(std::string str) :
363 m_time(unix_epoch, utc_zone)
365 if (str.empty())
return;
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})?)?$");
373 if (regex_match(str, sm, non_delim))
375 std::string time_str(sm[1]);
376 time_str.insert(8,
"T");
377 pdt = boost::posix_time::from_iso_string(time_str);
379 else if (regex_match(str, sm, delim_iso))
381 pdt = boost::posix_time::time_from_string(sm[1]);
385 throw(std::invalid_argument(
"The date string was not formatted in a way that GncDateTime(std::string) knows how to parse."));
387 std::string tzstr(
"");
390 tzptr = tz_from_string(tzstr);
391 m_time = LDT_from_date_time(pdt.date(), pdt.time_of_day(), tzptr);
393 catch(boost::gregorian::bad_year&)
395 throw(std::invalid_argument(
"The date string was outside of the supported year range."));
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);
408 GncDateTimeImpl::operator
time64()
const 410 auto duration = m_time.utc_time() - unix_epoch;
411 auto secs = duration.ticks();
412 secs /= ticks_per_second;
416 GncDateTimeImpl::operator
struct tm() const
418 struct tm time = to_tm(m_time);
419 #if HAVE_STRUCT_TM_GMTOFF 420 time.tm_gmtoff = offset();
426 GncDateTimeImpl::offset()
const 428 auto offset = m_time.local_time() - m_time.utc_time();
429 return offset.total_seconds();
432 std::unique_ptr<GncDateImpl>
433 GncDateTimeImpl::date()
const 435 return std::unique_ptr<GncDateImpl>(
new GncDateImpl(m_time.local_time().date()));
441 static inline std::string
442 normalize_format (
const std::string& format)
445 std::string normalized;
447 format.begin(), format.end(), back_inserter(normalized),
449 bool r = (is_pct && (e ==
'E' || e ==
'O' || e ==
'-'));
456 constexpr
size_t DATEBUFLEN = 100;
458 win_date_format(std::string format,
struct tm tm)
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);
472 win_format_tz_abbrev (std::string format, TZ_Ptr tz,
bool is_dst)
474 size_t pos = format.find(
"%z");
475 if (pos != std::string::npos)
477 auto tzabbr = tz->has_dst() && is_dst ? tz->dst_zone_abbrev() :
478 tz->std_zone_abbrev();
479 format.replace(pos, 2, tzabbr);
485 win_format_tz_name (std::string format, TZ_Ptr tz,
bool is_dst)
487 size_t pos = format.find(
"%Z");
488 if (pos != std::string::npos)
490 auto tzname = tz->has_dst() && is_dst ? tz->dst_zone_name() :
492 format.replace(pos, 2, tzname);
498 win_format_tz_posix (std::string format, TZ_Ptr tz)
500 size_t pos = format.find(
"%ZP");
501 if (pos != std::string::npos)
502 format.replace(pos, 2, tz->to_posix_string());
508 GncDateTimeImpl::format(
const char* format)
const 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);
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));
528 GncDateTimeImpl::format_zulu(
const char* format)
const 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());
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));
549 GncDateTimeImpl::format_iso8601()
const 551 auto str = boost::posix_time::to_iso_extended_string(m_time.utc_time());
553 return str.substr(0, 19);
557 GncDateTimeImpl::timestamp()
560 auto str = boost::posix_time::to_iso_string(gdt.m_time.local_time());
561 return str.substr(0, 8) + str.substr(9, 15);
566 GncDateImpl::GncDateImpl(
const std::string str,
const std::string fmt) :
567 m_greg(boost::gregorian::day_clock::local_day())
570 [&fmt](
const GncDateFormat& v){
return (v.m_fmt == fmt); } );
572 throw std::invalid_argument(N_(
"Unknown date format specifier passed as argument."));
574 boost::regex r(iter->m_re);
576 if(!boost::regex_search(str, what, r))
577 throw std::invalid_argument (N_(
"Value can't be parsed into a date using the selected date format."));
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."));
588 year = std::stoi (what.str(
"YEAR"));
597 year = m_greg.year();
600 static_cast<Month>(std::stoi (what.str(
"MONTH"))),
601 std::stoi (what.str(
"DAY")));
605 GncDateImpl::year_month_day()
const 607 auto boost_ymd = m_greg.year_month_day();
608 return {boost_ymd.year, boost_ymd.month.as_number(), boost_ymd.day};
612 GncDateImpl::format(
const char* format)
const 615 return win_date_format(format, to_tm(m_greg));
617 using Facet = boost::gregorian::date_facet;
618 std::stringstream ss;
620 auto output_facet(
new Facet(normalize_format(format).c_str()));
621 ss.imbue(std::locale(gnc_get_locale(), output_facet));
644 GncDateTime::~GncDateTime() =
default;
657 return m_impl->operator
time64();
660 GncDateTime::operator
struct tm() const
662 return m_impl->operator
struct tm();
668 return m_impl->offset();
680 return GncDate(m_impl->date());
686 return m_impl->format(
format);
692 return m_impl->format_zulu(
format);
698 return m_impl->format_iso8601();
704 return GncDateTimeImpl::timestamp();
714 m_impl(
std::move(impl)) {}
738 return m_impl->format(
format);
744 return m_impl->year_month_day();
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.
void today()
Set the date object to the computer clock's current day.
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...
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.
void now()
Set the GncDateTime to the date and time indicated in the computer's clock.