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   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.