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 <unicode/smpdtfmt.h> 33 #include <unicode/locid.h> 34 #include <unicode/udat.h> 35 #include <unicode/parsepos.h> 36 #include <unicode/calendar.h> 50 #include <gnc-locale-utils.hpp> 51 #include "gnc-timezone.hpp" 52 #include "gnc-datetime.hpp" 54 #define N_(string) string //So that xgettext will find it 56 using PTZ = boost::local_time::posix_time_zone;
57 using Date = boost::gregorian::date;
58 using Month = boost::gregorian::greg_month;
59 using PTime = boost::posix_time::ptime;
60 using LDT = boost::local_time::local_date_time;
61 using Duration = boost::posix_time::time_duration;
62 using LDTBase = boost::local_time::local_date_time_base<PTime, boost::date_time::time_zone_base<PTime, char>>;
63 using boost::date_time::not_a_date_time;
70 static const PTime unix_epoch (Date(1970, boost::gregorian::Jan, 1),
71 boost::posix_time::seconds(0));
72 static const TZ_Ptr utc_zone(
new boost::local_time::posix_time_zone(
"UTC-0"));
78 static Date gregorian_date_from_locale_string (
const std::string& str);
81 #ifndef BOOST_DATE_TIME_HAS_NANOSECONDS 82 static constexpr
auto ticks_per_second = INT64_C(1000000);
84 static constexpr
auto ticks_per_second = INT64_C(1000000000);
96 boost::gregorian::from_string,
98 "(?<YEAR>[0-9]+)[-/.' ]+" 99 "(?<MONTH>[0-9]+)[-/.' ]+" 109 boost::gregorian::from_uk_string,
111 "(?<DAY>[0-9]+)[-/.' ]+" 112 "(?<MONTH>[0-9]+)[-/.' ]+" 122 boost::gregorian::from_us_string,
124 "(?<MONTH>[0-9]+)[-/.' ]+" 125 "(?<DAY>[0-9]+)[-/.' ]+" 138 "(?<DAY>[0-9]+)[-/.' ]+" 139 "(?<MONTH>[0-9]+)(?:[-/.' ]+" 150 "(?<MONTH>[0-9]+)[-/.' ]+" 151 "(?<DAY>[0-9]+)(?:[-/.' ]+" 159 GncDateFormat { N_(
"Locale"), gregorian_date_from_locale_string },
165 LDT_from_unix_local(
const time64 time)
169 PTime temp(unix_epoch.date(),
170 boost::posix_time::hours(time / 3600) +
171 boost::posix_time::seconds(time % 3600));
172 auto tz = tzp->get(temp.date().year());
173 return LDT(temp, tz);
175 catch(boost::gregorian::bad_year&)
177 throw(std::invalid_argument(
"Time value is outside the supported year range."));
189 LDT_with_pushup(
const Date& tdate,
const Duration& tdur,
const TZ_Ptr tz,
192 static const boost::posix_time::hours pushup{1};
193 LDT ldt{tdate, tdur + pushup, tz, LDTBase::NOT_DATE_TIME_ON_ERROR};
194 if (ldt.is_special())
196 std::string error{
"Couldn't create a valid datetime at "};
197 error += to_simple_string(tdate) +
" ";
198 error += to_simple_string(tdur) +
" TZ ";
199 error += tz->std_zone_abbrev();
200 throw(std::invalid_argument{error});
208 LDT_from_date_time(
const Date& tdate,
const Duration& tdur,
const TZ_Ptr tz)
213 LDT ldt(tdate, tdur, tz, LDTBase::EXCEPTION_ON_ERROR);
216 catch (
const boost::local_time::time_label_invalid& err)
218 return LDT_with_pushup(tdate, tdur, tz,
false);
221 catch (
const boost::local_time::ambiguous_result& err)
223 return LDT_with_pushup(tdate, tdur, tz,
true);
226 catch(boost::gregorian::bad_year&)
228 throw(std::invalid_argument(
"Time value is outside the supported year range."));
234 LDT_from_date_daypart(
const Date& date, DayPart part,
const TZ_Ptr tz)
236 using hours = boost::posix_time::hours;
238 static const Duration day_begin{0, 0, 0};
239 static const Duration day_neutral{10, 59, 0};
240 static const Duration day_end{23, 59, 59};
246 return LDT_from_date_time(date, day_begin, tz);
248 return LDT_from_date_time(date, day_end, tz);
250 case DayPart::neutral:
251 PTime pt{date, day_neutral};
253 auto offset = lt.local_time() - lt.utc_time();
254 if (offset < hours(-10))
255 lt -= hours(offset.hours() + 10);
256 if (offset > hours(13))
257 lt += hours(13 - offset.hours());
263 LDT_from_struct_tm(
const struct tm tm)
267 Date tdate{boost::gregorian::date_from_tm(tm)};
268 Duration tdur{boost::posix_time::time_duration(tm.tm_hour, tm.tm_min,
270 TZ_Ptr tz{tzp->get(tdate.year())};
271 return LDT_from_date_time(tdate, tdur, tz);
273 catch(
const boost::gregorian::bad_year&)
275 throw(std::invalid_argument{
"Time value is outside the supported year range."});
294 GncDateTimeImpl() : m_time(boost::local_time::local_sec_clock::local_time(tzp->get(boost::gregorian::day_clock::local_day().year()))) {}
296 GncDateTimeImpl(
const struct tm tm) : m_time(LDT_from_struct_tm(tm)) {}
300 GncDateTimeImpl(PTime&& pt) : m_time(pt, tzp->get(pt.date().year())) {}
304 operator struct tm() const;
305 void now() { m_time = boost::local_time::local_sec_clock::local_time(tzp->get(boost::gregorian::day_clock::local_day().year())); }
307 struct tm utc_tm() const {
return to_tm(m_time.utc_time()); }
308 std::unique_ptr<GncDateImpl> date()
const;
309 std::string format(
const char* format)
const;
310 std::string format_zulu(
const char* format)
const;
311 std::string format_iso8601()
const;
312 static std::string timestamp();
322 GncDateImpl(): m_greg(boost::gregorian::day_clock::local_day()) {}
323 GncDateImpl(
const int year,
const int month,
const int day) :
324 m_greg(year, static_cast<Month>(month), day) {}
326 GncDateImpl(
const std::string str,
const std::string fmt);
328 void today() { m_greg = boost::gregorian::day_clock::local_day(); }
329 gnc_ymd year_month_day()
const;
330 std::string format(
const char* format)
const;
331 std::string format_zulu(
const char* format)
const {
332 return this->format(format);
337 friend GncDateTimeImpl::GncDateTimeImpl(
const GncDateImpl&, DayPart);
349 GncDateTimeImpl::GncDateTimeImpl(
const GncDateImpl& date, DayPart part) :
350 m_time{LDT_from_date_daypart(date.m_greg, part,
351 tzp->get(date.m_greg.year()))} {}
357 parse_chars_into_num (
const char* ptr,
const char *end_ptr, int32_t& rv) noexcept
359 auto result = std::from_chars (ptr, end_ptr, rv);
360 return (result.ec == std::errc() && result.ptr == end_ptr);
363 static std::optional<PTime>
364 fast_iso8601_utc_parse (
const char* str)
366 int32_t year, month, mday, hour, min, sec;
369 if (!str || !parse_chars_into_num (str, str + 4, year))
374 parse_chars_into_num (str + 5, str + 7, month) && str[ 7] ==
'-' &&
375 parse_chars_into_num (str + 8, str + 10, mday) && str[10] ==
' ' &&
376 parse_chars_into_num (str + 11, str + 13, hour) && str[13] ==
':' &&
377 parse_chars_into_num (str + 14, str + 16, min) && str[16] ==
':' &&
378 parse_chars_into_num (str + 17, str + 19, sec) && str[19] ==
' ' &&
379 !strcmp (str + 20,
"+0000"))
381 return PTime (boost::gregorian::date (year, month, mday),
382 boost::posix_time::time_duration (hour, min, sec));
386 if (parse_chars_into_num (str + 4, str + 6, month) &&
387 parse_chars_into_num (str + 6, str + 8, mday) &&
388 parse_chars_into_num (str + 8, str + 10, hour) &&
389 parse_chars_into_num (str + 10, str + 12, min) &&
390 parse_chars_into_num (str + 12, str + 14, sec) &&
393 return PTime (boost::gregorian::date (year, month, mday),
394 boost::posix_time::time_duration (hour, min, sec));
401 tz_from_string(std::string str)
403 if (str.empty())
return utc_zone;
404 std::string tzstr =
"XXX" + str;
405 if (tzstr.length() > 6 && tzstr[6] !=
':')
406 tzstr.insert(6,
":");
407 if (tzstr.length() > 9 && tzstr[9] !=
':')
409 tzstr.insert(9,
":");
411 return TZ_Ptr(
new PTZ(tzstr));
414 GncDateTimeImpl::GncDateTimeImpl(
const char* str) :
415 m_time(unix_epoch, utc_zone)
417 if (!str || !str[0])
return;
421 if (
auto res = fast_iso8601_utc_parse (str))
423 m_time = LDT_from_date_time(res->date(), res->time_of_day(), utc_zone);
426 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})?)?$");
427 static const boost::regex non_delim(
"^(\\d{14}(?:\\.\\d{0,9})?)\\s*([+-]\\d{2}\\s*(:?\\d{2})?)?$");
430 if (regex_match(str, sm, non_delim))
432 std::string time_str(sm[1]);
433 time_str.insert(8,
"T");
434 pdt = boost::posix_time::from_iso_string(time_str);
436 else if (regex_match(str, sm, delim_iso))
438 pdt = boost::posix_time::time_from_string(sm[1]);
442 throw(std::invalid_argument(
"The date string was not formatted in a way that GncDateTime(const char*) knows how to parse."));
444 std::string tzstr(
"");
447 tzptr = tz_from_string(tzstr);
448 m_time = LDT_from_date_time(pdt.date(), pdt.time_of_day(), tzptr);
450 catch(boost::gregorian::bad_year&)
452 throw(std::invalid_argument(
"The date string was outside of the supported year range."));
460 auto offset = tzptr->base_utc_offset().seconds();
461 if (offset != 0 && std::abs(offset) < 3600)
462 m_time = m_time.local_time_in(utc_zone);
465 GncDateTimeImpl::operator
time64()
const 467 auto duration = m_time.utc_time() - unix_epoch;
468 auto secs = duration.ticks();
469 secs /= ticks_per_second;
473 GncDateTimeImpl::operator
struct tm() const
475 struct tm time = to_tm(m_time);
476 #if HAVE_STRUCT_TM_GMTOFF 477 time.tm_gmtoff = offset();
483 GncDateTimeImpl::offset()
const 485 auto offset = m_time.local_time() - m_time.utc_time();
486 return offset.total_seconds();
489 std::unique_ptr<GncDateImpl>
490 GncDateTimeImpl::date()
const 492 return std::unique_ptr<GncDateImpl>(
new GncDateImpl(m_time.local_time().date()));
498 static inline std::string
499 normalize_format (
const std::string& format)
502 std::string normalized;
504 format.begin(), format.end(), back_inserter(normalized),
506 bool r = (is_pct && (e ==
'E' || e ==
'O' || e ==
'-'));
513 constexpr
size_t DATEBUFLEN = 100;
515 win_date_format(std::string format,
struct tm tm)
517 wchar_t buf[DATEBUFLEN];
518 memset(buf, 0, DATEBUFLEN);
519 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,
wchar_t> conv;
520 auto numchars = wcsftime(buf, DATEBUFLEN - 1, conv.from_bytes(format).c_str(), &tm);
521 return conv.to_bytes(buf);
529 win_format_tz_abbrev (std::string format, TZ_Ptr tz,
bool is_dst)
531 size_t pos = format.find(
"%z");
532 if (pos != std::string::npos)
534 auto tzabbr = tz->has_dst() && is_dst ? tz->dst_zone_abbrev() :
535 tz->std_zone_abbrev();
536 format.replace(pos, 2, tzabbr);
542 win_format_tz_name (std::string format, TZ_Ptr tz,
bool is_dst)
544 size_t pos = format.find(
"%Z");
545 if (pos != std::string::npos)
547 auto tzname = tz->has_dst() && is_dst ? tz->dst_zone_name() :
549 format.replace(pos, 2, tzname);
555 win_format_tz_posix (std::string format, TZ_Ptr tz)
557 size_t pos = format.find(
"%ZP");
558 if (pos != std::string::npos)
559 format.replace(pos, 2, tz->to_posix_string());
565 GncDateTimeImpl::format(
const char* format)
const 568 auto tz = m_time.zone();
569 auto tm =
static_cast<struct tm
>(*this);
570 auto sformat = win_format_tz_abbrev(format, tz, tm.tm_isdst);
571 sformat = win_format_tz_name(sformat, tz, tm.tm_isdst);
572 sformat = win_format_tz_posix(sformat, tz);
573 return win_date_format(sformat, tm);
575 using Facet = boost::local_time::local_time_facet;
576 auto output_facet(
new Facet(normalize_format(format).c_str()));
577 std::stringstream ss;
578 ss.imbue(std::locale(gnc_get_locale(), output_facet));
585 GncDateTimeImpl::format_zulu(
const char* format)
const 588 auto tz = m_time.zone();
589 auto tm =
static_cast<struct tm
>(*this);
590 auto sformat = win_format_tz_abbrev(format, tz, tm.tm_isdst);
591 sformat = win_format_tz_name(sformat, tz, tm.tm_isdst);
592 sformat = win_format_tz_posix(sformat, tz);
593 return win_date_format(sformat, utc_tm());
595 using Facet = boost::local_time::local_time_facet;
596 auto zulu_time = LDT{m_time.utc_time(), utc_zone};
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_iso8601()
const 608 auto str = boost::posix_time::to_iso_extended_string(m_time.utc_time());
610 return str.substr(0, 19);
614 GncDateTimeImpl::timestamp()
617 auto str = boost::posix_time::to_iso_string(gdt.m_time.local_time());
618 return str.substr(0, 8) + str.substr(9, 15);
623 std::unique_ptr<icu::DateFormat> formatter;
624 std::unique_ptr<icu::Calendar> calendar;
635 if (
auto lc_time_locale = setlocale (LC_TIME,
nullptr))
637 std::string localeStr(lc_time_locale);
638 if (
size_t dotPos = localeStr.find(
'.'); dotPos != std::string::npos)
639 localeStr = localeStr.substr(0, dotPos);
641 locale = icu::Locale::createCanonical (localeStr.c_str());
644 rv.formatter.reset(icu::DateFormat::createDateInstance(icu::DateFormat::kDefault, locale));
646 throw std::invalid_argument(
"Cannot create date formatter.");
648 UErrorCode status = U_ZERO_ERROR;
649 rv.calendar.reset(icu::Calendar::createInstance(locale, status));
650 if (U_FAILURE(status))
651 throw std::invalid_argument(
"Cannot create calendar instance.");
653 rv.calendar->setLenient(
false);
660 gregorian_date_from_locale_string (
const std::string& str)
664 icu::UnicodeString input = icu::UnicodeString::fromUTF8(str);
665 icu::ParsePosition parsePos;
666 UDate date = resources.formatter->parse(input, parsePos);
667 if (parsePos.getErrorIndex() != -1 || parsePos.getIndex() != input.length())
668 throw std::invalid_argument (
"Cannot parse string");
670 UErrorCode status = U_ZERO_ERROR;
671 resources.calendar->setTime(date, status);
672 if (U_FAILURE(status))
673 throw std::invalid_argument (
"Cannot set calendar time");
675 return Date (resources.calendar->get(UCAL_YEAR, status),
676 resources.calendar->get(UCAL_MONTH, status) + 1,
677 resources.calendar->get(UCAL_DATE, status));
682 GncDateImpl::GncDateImpl(
const std::string str,
const std::string fmt) :
683 m_greg(boost::gregorian::day_clock::local_day())
686 [&fmt](
const GncDateFormat& v){
return (v.m_fmt == fmt); } );
688 throw std::invalid_argument(N_(
"Unknown date format specifier passed as argument."));
690 if (iter->m_str_to_date)
694 m_greg = (*iter->m_str_to_date)(str);
700 if (iter->m_re.empty())
701 throw std::invalid_argument (
"No regex pattern available");
703 boost::regex r(iter->m_re);
705 if(!boost::regex_search(str, what, r))
706 throw std::invalid_argument (N_(
"Value can't be parsed into a date using the selected date format."));
709 auto fmt_has_year = (fmt.find(
'y') != std::string::npos);
710 if (!fmt_has_year && (what.length(
"YEAR") != 0))
711 throw std::invalid_argument (N_(
"Value appears to contain a year while the selected format forbids this."));
717 year = std::stoi (what.str(
"YEAR"));
726 year = m_greg.year();
729 static_cast<Month>(std::stoi (what.str(
"MONTH"))),
730 std::stoi (what.str(
"DAY")));
734 GncDateImpl::year_month_day()
const 736 auto boost_ymd = m_greg.year_month_day();
737 return {boost_ymd.year, boost_ymd.month.as_number(), boost_ymd.day};
741 GncDateImpl::format(
const char* format)
const 744 return win_date_format(format, to_tm(m_greg));
746 using Facet = boost::gregorian::date_facet;
747 std::stringstream ss;
749 auto output_facet(
new Facet(normalize_format(format).c_str()));
750 ss.imbue(std::locale(gnc_get_locale(), output_facet));
775 GncDateTime::~GncDateTime() =
default;
788 return m_impl->operator
time64();
791 GncDateTime::operator
struct tm() const
793 return m_impl->operator
struct tm();
799 return m_impl->offset();
811 return GncDate(m_impl->date());
817 return m_impl->format(
format);
823 return m_impl->format_zulu(
format);
829 return m_impl->format_iso8601();
835 return GncDateTimeImpl::timestamp();
845 m_impl(
std::move(impl)) {}
869 return m_impl->format(
format);
875 return m_impl->year_month_day();
878 bool operator<(
const GncDate& a,
const GncDate& b) {
return *(a.m_impl) < *(b.m_impl); }
879 bool operator>(
const GncDate& a,
const GncDate& b) {
return *(a.m_impl) > *(b.m_impl); }
880 bool operator==(
const GncDate& a,
const GncDate& b) {
return *(a.m_impl) == *(b.m_impl); }
881 bool operator<=(
const GncDate& a,
const GncDate& b) {
return *(a.m_impl) <= *(b.m_impl); }
882 bool operator>=(
const GncDate& a,
const GncDate& b) {
return *(a.m_impl) >= *(b.m_impl); }
883 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.