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> 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> 52 #include <gnc-locale-utils.hpp> 53 #include "gnc-timezone.hpp" 54 #include "gnc-datetime.hpp" 56 #define N_(string) string //So that xgettext will find it 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;
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"));
80 static Date gregorian_date_from_locale_string (
const std::string& str);
83 #ifndef BOOST_DATE_TIME_HAS_NANOSECONDS 84 static constexpr
auto ticks_per_second = INT64_C(1000000);
86 static constexpr
auto ticks_per_second = INT64_C(1000000000);
98 boost::gregorian::from_string,
100 "(?<YEAR>[0-9]+)[-/.' ]+" 101 "(?<MONTH>[0-9]+)[-/.' ]+" 111 boost::gregorian::from_uk_string,
113 "(?<DAY>[0-9]+)[-/.' ]+" 114 "(?<MONTH>[0-9]+)[-/.' ]+" 124 boost::gregorian::from_us_string,
126 "(?<MONTH>[0-9]+)[-/.' ]+" 127 "(?<DAY>[0-9]+)[-/.' ]+" 140 "(?<DAY>[0-9]+)[-/.' ]+" 141 "(?<MONTH>[0-9]+)(?:[-/.' ]+" 152 "(?<MONTH>[0-9]+)[-/.' ]+" 153 "(?<DAY>[0-9]+)(?:[-/.' ]+" 161 GncDateFormat { N_(
"Locale"), gregorian_date_from_locale_string },
167 LDT_from_unix_local(
const time64 time)
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);
177 catch(boost::gregorian::bad_year&)
179 throw(std::invalid_argument(
"Time value is outside the supported year range."));
181 catch(std::out_of_range&)
183 throw(std::invalid_argument(
"Time value is outside the supported year range."));
195 LDT_with_pushup(
const Date& tdate,
const Duration& tdur,
const TZ_Ptr tz,
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())
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});
214 LDT_from_date_time(
const Date& tdate,
const Duration& tdur,
const TZ_Ptr tz)
219 LDT ldt(tdate, tdur, tz, LDTBase::EXCEPTION_ON_ERROR);
222 catch (
const boost::local_time::time_label_invalid& err)
224 return LDT_with_pushup(tdate, tdur, tz,
false);
227 catch (
const boost::local_time::ambiguous_result& err)
229 return LDT_with_pushup(tdate, tdur, tz,
true);
232 catch(boost::gregorian::bad_year&)
234 throw(std::invalid_argument(
"Time value is outside the supported year range."));
236 catch(std::out_of_range&)
238 throw(std::invalid_argument(
"Time value is outside the supported year range."));
244 LDT_from_date_daypart(
const Date& date, DayPart part,
const TZ_Ptr tz)
246 using hours = boost::posix_time::hours;
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};
256 return LDT_from_date_time(date, day_begin, tz);
258 return LDT_from_date_time(date, day_end, tz);
260 case DayPart::neutral:
261 PTime pt{date, day_neutral};
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());
273 LDT_from_struct_tm(
const struct tm tm)
277 Date tdate{boost::gregorian::date_from_tm(tm)};
278 Duration tdur{boost::posix_time::time_duration(tm.tm_hour, tm.tm_min,
280 TZ_Ptr tz{tzp->get(tdate.year())};
281 return LDT_from_date_time(tdate, tdur, tz);
283 catch(
const boost::gregorian::bad_year&)
285 throw(std::invalid_argument{
"Time value is outside the supported year range."});
287 catch(std::out_of_range&)
289 throw(std::invalid_argument(
"Time value is outside the supported year range."));
311 GncDateTimeImpl() : m_time(LDT_from_unix_local(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count())) {}
313 GncDateTimeImpl(
const struct tm tm) : m_time(LDT_from_struct_tm(tm)) {}
317 GncDateTimeImpl(PTime&& pt) : m_time(pt, tzp->get(pt.date().year())) {}
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())); }
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();
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) {}
343 GncDateImpl(
const std::string str,
const std::string fmt);
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);
354 friend GncDateTimeImpl::GncDateTimeImpl(
const GncDateImpl&, DayPart);
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()))} {}
374 parse_chars_into_num (
const char* ptr,
const char *end_ptr, int32_t& rv) noexcept
376 auto result = std::from_chars (ptr, end_ptr, rv);
377 return (result.ec == std::errc() && result.ptr == end_ptr);
380 static std::optional<PTime>
381 fast_iso8601_utc_parse (
const char* str)
383 int32_t year, month, mday, hour, min, sec;
386 if (!str || !parse_chars_into_num (str, str + 4, year))
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"))
398 return PTime (boost::gregorian::date (year, month, mday),
399 boost::posix_time::time_duration (hour, min, sec));
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) &&
410 return PTime (boost::gregorian::date (year, month, mday),
411 boost::posix_time::time_duration (hour, min, sec));
418 tz_from_string(std::string str)
420 if (str.empty())
return utc_zone;
421 std::string tzstr =
"XXX" + str;
422 if (tzstr.length() > 6 && tzstr[6] !=
':')
423 tzstr.insert(6,
":");
424 if (tzstr.length() > 9 && tzstr[9] !=
':')
426 tzstr.insert(9,
":");
428 return TZ_Ptr(
new PTZ(tzstr));
431 GncDateTimeImpl::GncDateTimeImpl(
const char* str) :
432 m_time(unix_epoch, utc_zone)
434 if (!str || !str[0])
return;
438 if (
auto res = fast_iso8601_utc_parse (str))
440 m_time = LDT_from_date_time(res->date(), res->time_of_day(), utc_zone);
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})?)?$");
447 if (regex_match(str, sm, non_delim))
449 std::string time_str(sm[1]);
450 time_str.insert(8,
"T");
451 pdt = boost::posix_time::from_iso_string(time_str);
453 else if (regex_match(str, sm, delim_iso))
455 pdt = boost::posix_time::time_from_string(sm[1]);
459 throw(std::invalid_argument(
"The date string was not formatted in a way that GncDateTime(const char*) knows how to parse."));
461 std::string tzstr(
"");
464 tzptr = tz_from_string(tzstr);
465 m_time = LDT_from_date_time(pdt.date(), pdt.time_of_day(), tzptr);
467 catch(boost::gregorian::bad_year&)
469 throw(std::invalid_argument(
"The date string was outside of the supported year range."));
471 catch(std::out_of_range&)
473 throw(std::invalid_argument(
"The date string was outside of the supported year range."));
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);
486 GncDateTimeImpl::operator
time64()
const 488 auto duration = m_time.utc_time() - unix_epoch;
489 auto secs = duration.ticks();
490 secs /= ticks_per_second;
494 GncDateTimeImpl::operator
struct tm() const
496 struct tm time = to_tm(m_time);
497 #if HAVE_STRUCT_TM_GMTOFF 498 time.tm_gmtoff = offset();
504 GncDateTimeImpl::offset()
const 506 auto offset = m_time.local_time() - m_time.utc_time();
507 return offset.total_seconds();
510 std::unique_ptr<GncDateImpl>
511 GncDateTimeImpl::date()
const 513 return std::unique_ptr<GncDateImpl>(
new GncDateImpl(m_time.local_time().date()));
519 static inline std::string
520 normalize_format (
const std::string& format)
523 std::string normalized;
525 format.begin(), format.end(), back_inserter(normalized),
527 bool r = (is_pct && (e ==
'E' || e ==
'O' || e ==
'-'));
534 constexpr
size_t DATEBUFLEN = 100;
536 win_date_format(std::string format,
struct tm tm)
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);
550 win_format_tz_abbrev (std::string format, TZ_Ptr tz,
bool is_dst)
552 size_t pos = format.find(
"%z");
553 if (pos != std::string::npos)
555 auto tzabbr = tz->has_dst() && is_dst ? tz->dst_zone_abbrev() :
556 tz->std_zone_abbrev();
557 format.replace(pos, 2, tzabbr);
563 win_format_tz_name (std::string format, TZ_Ptr tz,
bool is_dst)
565 size_t pos = format.find(
"%Z");
566 if (pos != std::string::npos)
568 auto tzname = tz->has_dst() && is_dst ? tz->dst_zone_name() :
570 format.replace(pos, 2, tzname);
576 win_format_tz_posix (std::string format, TZ_Ptr tz)
578 size_t pos = format.find(
"%ZP");
579 if (pos != std::string::npos)
580 format.replace(pos, 2, tz->to_posix_string());
586 GncDateTimeImpl::format(
const char* format)
const 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);
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));
606 GncDateTimeImpl::format_zulu(
const char* format)
const 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());
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));
627 GncDateTimeImpl::format_iso8601()
const 629 auto str = boost::posix_time::to_iso_extended_string(m_time.utc_time());
631 return str.substr(0, 19);
635 GncDateTimeImpl::timestamp()
638 auto str = boost::posix_time::to_iso_string(gdt.m_time.local_time());
639 return str.substr(0, 8) + str.substr(9, 15);
644 std::unique_ptr<icu::DateFormat> formatter;
645 std::unique_ptr<icu::Calendar> calendar;
656 if (
auto lc_time_locale = setlocale (LC_TIME,
nullptr))
658 std::string localeStr(lc_time_locale);
659 if (
size_t dotPos = localeStr.find(
'.'); dotPos != std::string::npos)
660 localeStr = localeStr.substr(0, dotPos);
662 locale = icu::Locale::createCanonical (localeStr.c_str());
665 rv.formatter.reset(icu::DateFormat::createDateInstance(icu::DateFormat::kDefault, locale));
667 throw std::invalid_argument(
"Cannot create date formatter.");
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.");
674 rv.calendar->setLenient(
false);
681 gregorian_date_from_locale_string (
const std::string& str)
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");
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");
696 return Date (resources.calendar->get(UCAL_YEAR, status),
697 resources.calendar->get(UCAL_MONTH, status) + 1,
698 resources.calendar->get(UCAL_DATE, status));
703 GncDateImpl::GncDateImpl(
const std::string str,
const std::string fmt) :
704 m_greg(boost::gregorian::day_clock::local_day())
707 [&fmt](
const GncDateFormat& v){
return (v.m_fmt == fmt); } );
709 throw std::invalid_argument(N_(
"Unknown date format specifier passed as argument."));
711 if (iter->m_str_to_date)
715 m_greg = (*iter->m_str_to_date)(str);
721 if (iter->m_re.empty())
722 throw std::invalid_argument (
"No regex pattern available");
724 boost::regex r(iter->m_re);
726 if(!boost::regex_search(str, what, r))
727 throw std::invalid_argument (N_(
"Value can't be parsed into a date using the selected date format."));
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."));
738 year = std::stoi (what.str(
"YEAR"));
747 year = m_greg.year();
750 static_cast<Month>(std::stoi (what.str(
"MONTH"))),
751 std::stoi (what.str(
"DAY")));
755 GncDateImpl::year_month_day()
const 757 auto boost_ymd = m_greg.year_month_day();
758 return {boost_ymd.year, boost_ymd.month.as_number(), boost_ymd.day};
762 GncDateImpl::format(
const char* format)
const 765 return win_date_format(format, to_tm(m_greg));
767 using Facet = boost::gregorian::date_facet;
768 std::stringstream ss;
770 auto output_facet(
new Facet(normalize_format(format).c_str()));
771 ss.imbue(std::locale(gnc_get_locale(), output_facet));
796 GncDateTime::~GncDateTime() =
default;
809 return m_impl->operator
time64();
812 GncDateTime::operator
struct tm() const
814 return m_impl->operator
struct tm();
820 return m_impl->offset();
832 return GncDate(m_impl->date());
838 return m_impl->format(
format);
844 return m_impl->format_zulu(
format);
850 return m_impl->format_iso8601();
856 return GncDateTimeImpl::timestamp();
866 m_impl(
std::move(impl)) {}
890 return m_impl->format(
format);
896 return m_impl->year_month_day();
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.
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.
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...
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.