LCOV - code coverage report
Current view: top level - libgnucash/engine - gnc-datetime.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 60.1 % 351 211
Test Date: 2026-11-02 18:19:43 Functions: 67.1 % 79 53
Legend: Lines:     hit not hit

            Line data    Source code
       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              : #include <config.h>
      26              : #include "platform.h"
      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 <stdexcept>
      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>
      38              : #include <libintl.h>
      39              : #include <locale.h>
      40              : #include<chrono>
      41              : #include <map>
      42              : #include <memory>
      43              : #include <iostream>
      44              : #include <sstream>
      45              : #include <string>
      46              : #include <vector>
      47              : #include <optional>
      48              : #include <charconv>
      49              : #ifdef __MINGW32__
      50              : #include <codecvt>
      51              : #endif
      52              : #include <gnc-locale-utils.hpp>
      53              : #include "gnc-timezone.hpp"
      54              : #include "gnc-datetime.hpp"
      55              : 
      56              : #define N_(string) string //So that xgettext will find it
      57              : 
      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;
      66              : using time64 = int64_t;
      67              : 
      68              : static const TimeZoneProvider ltzp;
      69              : static const TimeZoneProvider* tzp = &ltzp;
      70              : 
      71              : // For converting to/from POSIX 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"));
      75              : 
      76              : /* Backdoor to enable unittests to temporarily override the timezone: */
      77              : void _set_tzp(TimeZoneProvider& tz);
      78              : void _reset_tzp();
      79              : 
      80              : static Date gregorian_date_from_locale_string (const std::string& str);
      81              : 
      82              : /* 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. */
      83              : #ifndef BOOST_DATE_TIME_HAS_NANOSECONDS
      84              : static constexpr auto ticks_per_second = INT64_C(1000000);
      85              : #else
      86              : static constexpr auto ticks_per_second = INT64_C(1000000000);
      87              : #endif
      88              : 
      89              : /* Vector of date formats understood by gnucash and corresponding regex
      90              :  * and/or string->gregorian_date to parse each from an external source
      91              :  * Note: while the format names are using a "-" as separator, the
      92              :  * regexes will accept any of "-/.' " and will also work for dates
      93              :  * without separators.
      94              :  */
      95              : const std::vector<GncDateFormat> GncDate::c_formats ({
      96              :     GncDateFormat {
      97              :         N_("y-m-d"),
      98              :         boost::gregorian::from_string,
      99              :         "(?:"                                   // either y-m-d
     100              :         "(?<YEAR>[0-9]+)[-/.' ]+"
     101              :         "(?<MONTH>[0-9]+)[-/.' ]+"
     102              :         "(?<DAY>[0-9]+)"
     103              :         "|"                                     // or CCYYMMDD
     104              :         "(?<YEAR>[0-9]{4})"
     105              :         "(?<MONTH>[0-9]{2})"
     106              :         "(?<DAY>[0-9]{2})"
     107              :         ")"
     108              :     },
     109              :     GncDateFormat {
     110              :         N_("d-m-y"),
     111              :         boost::gregorian::from_uk_string,
     112              :         "(?:"                                   // either d-m-y
     113              :         "(?<DAY>[0-9]+)[-/.' ]+"
     114              :         "(?<MONTH>[0-9]+)[-/.' ]+"
     115              :         "(?<YEAR>[0-9]+)"
     116              :         "|"                                     // or DDMMCCYY
     117              :         "(?<DAY>[0-9]{2})"
     118              :         "(?<MONTH>[0-9]{2})"
     119              :         "(?<YEAR>[0-9]{4})"
     120              :         ")"
     121              :     },
     122              :     GncDateFormat {
     123              :         N_("m-d-y"),
     124              :         boost::gregorian::from_us_string,
     125              :         "(?:"                                   // either m-d-y
     126              :         "(?<MONTH>[0-9]+)[-/.' ]+"
     127              :         "(?<DAY>[0-9]+)[-/.' ]+"
     128              :         "(?<YEAR>[0-9]+)"
     129              :         "|"                                     // or MMDDCCYY
     130              :         "(?<MONTH>[0-9]{2})"
     131              :         "(?<DAY>[0-9]{2})"
     132              :         "(?<YEAR>[0-9]{4})"
     133              :         ")"
     134              :     },
     135              :     // Note year is still checked for in the regexes below
     136              :     // This is to be able to raise an error if one is found for a yearless date format
     137              :     GncDateFormat {
     138              :         (N_("d-m")),
     139              :         "(?:"                                   // either d-m(-y)
     140              :         "(?<DAY>[0-9]+)[-/.' ]+"
     141              :         "(?<MONTH>[0-9]+)(?:[-/.' ]+"
     142              :         "(?<YEAR>[0-9]+))?"
     143              :         "|"                                     // or DDMM(CCYY)
     144              :         "(?<DAY>[0-9]{2})"
     145              :         "(?<MONTH>[0-9]{2})"
     146              :         "(?<YEAR>[0-9]+)?"
     147              :         ")"
     148              :     },
     149              :     GncDateFormat {
     150              :         (N_("m-d")),
     151              :         "(?:"                                   // either m-d(-y)
     152              :         "(?<MONTH>[0-9]+)[-/.' ]+"
     153              :         "(?<DAY>[0-9]+)(?:[-/.' ]+"
     154              :         "(?<YEAR>[0-9]+))?"
     155              :         "|"                                     // or MMDD(CCYY)
     156              :         "(?<MONTH>[0-9]{2})"
     157              :         "(?<DAY>[0-9]{2})"
     158              :         "(?<YEAR>[0-9]+)?"
     159              :         ")"
     160              :     },
     161              :     GncDateFormat { N_("Locale"), gregorian_date_from_locale_string },
     162              : });
     163              : 
     164              : /** Private implementation of GncDateTime. See the documentation for that class.
     165              :  */
     166              : static LDT
     167        61246 : LDT_from_unix_local(const time64 time)
     168              : {
     169              :     try
     170              :     {
     171              :         PTime temp(unix_epoch.date(),
     172        61246 :                    boost::posix_time::hours(time / 3600) +
     173       122492 :                    boost::posix_time::seconds(time % 3600));
     174        61246 :         auto tz = tzp->get(temp.date().year());
     175       122488 :         return LDT(temp, tz);
     176        61244 :     }
     177            2 :     catch(boost::gregorian::bad_year&)
     178              :     {
     179            2 :         throw(std::invalid_argument("Time value is outside the supported year range."));
     180            2 :     }
     181            0 :     catch(std::out_of_range&)
     182              :     {
     183            0 :         throw(std::invalid_argument("Time value is outside the supported year range."));
     184            0 :     }
     185              : }
     186              : /* If a date-time falls in a DST transition the LDT constructor will
     187              :  * fail because either the date-time doesn't exist (when starting DST
     188              :  * because the transition skips an hour) or is ambiguous (when ending
     189              :  * because the transition hour is repeated). We try again an hour
     190              :  * later to be outside the DST transition. When starting DST that's
     191              :  * now the correct time but at the end of DST we need to set the
     192              :  * returned time back an hour.
     193              :  */
     194              : static LDT
     195            1 : LDT_with_pushup(const Date& tdate, const Duration& tdur, const TZ_Ptr tz,
     196              :                  bool putback)
     197              : {
     198            1 :     static const boost::posix_time::hours pushup{1};
     199            1 :     LDT ldt{tdate, tdur + pushup, tz, LDTBase::NOT_DATE_TIME_ON_ERROR};
     200            1 :     if (ldt.is_special())
     201              :     {
     202            0 :         std::string error{"Couldn't create a valid datetime at "};
     203            0 :         error += to_simple_string(tdate) + " ";
     204            0 :         error += to_simple_string(tdur) + " TZ ";
     205            0 :         error += tz->std_zone_abbrev();
     206            0 :         throw(std::invalid_argument{error});
     207            0 :     }
     208            1 :     if (putback)
     209            1 :         ldt -= pushup;
     210            1 :     return ldt;
     211            0 : }
     212              : 
     213              : static LDT
     214        17316 : LDT_from_date_time(const Date& tdate, const Duration& tdur, const TZ_Ptr tz)
     215              : {
     216              : 
     217              :     try
     218              :     {
     219        17317 :         LDT ldt(tdate, tdur, tz, LDTBase::EXCEPTION_ON_ERROR);
     220        17315 :         return ldt;
     221            0 :     }
     222            1 :     catch (const boost::local_time::time_label_invalid& err)
     223              :     {
     224            0 :         return LDT_with_pushup(tdate, tdur, tz, false);
     225            0 :     }
     226              : 
     227            1 :     catch (const boost::local_time::ambiguous_result& err)
     228              :     {
     229            1 :         return LDT_with_pushup(tdate, tdur, tz, true);
     230            1 :     }
     231              : 
     232            0 :     catch(boost::gregorian::bad_year&)
     233              :     {
     234            0 :         throw(std::invalid_argument("Time value is outside the supported year range."));
     235            0 :     }
     236            0 :     catch(std::out_of_range&)
     237              :     {
     238            0 :         throw(std::invalid_argument("Time value is outside the supported year range."));
     239            0 :     }
     240              : 
     241              : }
     242              : 
     243              : static LDT
     244         6110 : LDT_from_date_daypart(const Date& date, DayPart part, const TZ_Ptr tz)
     245              : {
     246              :     using hours = boost::posix_time::hours;
     247              : 
     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};
     251              : 
     252              : 
     253         6110 :     switch (part)
     254              :     {
     255         2872 :     case DayPart::start:
     256         2872 :         return LDT_from_date_time(date, day_begin, tz);
     257          880 :     case DayPart::end:
     258          880 :         return LDT_from_date_time(date, day_end, tz);
     259         2358 :     default: // To stop gcc from emitting a control reaches end of non-void function.
     260              :     case DayPart::neutral:
     261         2358 :         PTime pt{date, day_neutral};
     262         4716 :         LDT lt{pt, tz};
     263         2358 :         auto offset = lt.local_time() - lt.utc_time();
     264         2358 :         if (offset < hours(-10))
     265            0 :             lt -= hours(offset.hours() + 10);
     266         2358 :         if (offset > hours(13))
     267            0 :             lt += hours(13 - offset.hours());
     268         2358 :         return lt;
     269              :     }
     270              : }
     271              : 
     272              : static LDT
     273        10692 : LDT_from_struct_tm(const struct tm tm)
     274              : {
     275              :     try
     276              :     {
     277        10692 :         Date tdate{boost::gregorian::date_from_tm(tm)};
     278        10692 :         Duration tdur{boost::posix_time::time_duration(tm.tm_hour, tm.tm_min,
     279        10692 :                                                        tm.tm_sec, 0)};
     280        10692 :         TZ_Ptr tz{tzp->get(tdate.year())};
     281        21384 :         return LDT_from_date_time(tdate, tdur, tz);
     282        10692 :     }
     283            0 :     catch(const boost::gregorian::bad_year&)
     284              :     {
     285            0 :         throw(std::invalid_argument{"Time value is outside the supported year range."});
     286            0 :     }
     287            0 :     catch(std::out_of_range&)
     288              :     {
     289            0 :         throw(std::invalid_argument("Time value is outside the supported year range."));
     290            0 :     }
     291              : }
     292              : 
     293              : void
     294            0 : _set_tzp(TimeZoneProvider& new_tzp)
     295              : {
     296            0 :     tzp = &new_tzp;
     297            0 : }
     298              : 
     299              : void
     300            0 : _reset_tzp()
     301              : {
     302            0 :     tzp = &ltzp;
     303            0 : }
     304              : 
     305              : class GncDateTimeImpl
     306              : {
     307              : public:
     308              :   /* Boost::date_time's localtime function relies on the C library's, so it may
     309              :    * suffer from the 2038 failure. std::chrono is supposed to be immune to that.
     310              :    */
     311        10151 :     GncDateTimeImpl() : m_time(LDT_from_unix_local(std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count())) {}
     312        51095 :     GncDateTimeImpl(const time64 time) : m_time(LDT_from_unix_local(time)) {}
     313        10692 :     GncDateTimeImpl(const struct tm tm) : m_time(LDT_from_struct_tm(tm)) {}
     314              :     GncDateTimeImpl(const GncDateImpl& date, DayPart part = DayPart::neutral);
     315          124 :     GncDateTimeImpl(const std::string& str) : GncDateTimeImpl (str.c_str()) {};
     316              :     GncDateTimeImpl(const char* str);
     317              :     GncDateTimeImpl(PTime&& pt) : m_time(pt, tzp->get(pt.date().year())) {}
     318              :     GncDateTimeImpl(LDT&& ldt) : m_time(ldt) {}
     319              : 
     320              :     operator time64() const;
     321              :     operator struct tm() const;
     322            0 :     void now() { m_time = boost::local_time::local_sec_clock::local_time(tzp->get(boost::gregorian::day_clock::local_day().year())); }
     323              :     long offset() const;
     324           15 :     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();
     330              : private:
     331              :     LDT m_time;
     332              : };
     333              : 
     334              : /** Private implementation of GncDate. See the documentation for that class.
     335              :  */
     336              : class GncDateImpl
     337              : {
     338              : public:
     339          288 :     GncDateImpl(): m_greg(boost::gregorian::day_clock::local_day()) {}
     340         6198 :     GncDateImpl(const int year, const int month, const int day) :
     341         6198 :     m_greg(year, static_cast<Month>(month), day) {}
     342          207 :     GncDateImpl(Date d) : m_greg(d) {}
     343              :     GncDateImpl(const std::string str, const std::string fmt);
     344              : 
     345            0 :     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);
     350              :     }
     351              : private:
     352              :     Date m_greg;
     353              : 
     354              :     friend GncDateTimeImpl::GncDateTimeImpl(const GncDateImpl&, DayPart);
     355              :     friend bool operator<(const GncDateImpl&, const GncDateImpl&);
     356              :     friend bool operator>(const GncDateImpl&, const GncDateImpl&);
     357              :     friend bool operator==(const GncDateImpl&, const GncDateImpl&);
     358              :     friend bool operator<=(const GncDateImpl&, const GncDateImpl&);
     359              :     friend bool operator>=(const GncDateImpl&, const GncDateImpl&);
     360              :     friend bool operator!=(const GncDateImpl&, const GncDateImpl&);
     361              : };
     362              : 
     363              : /* Needs to be separately defined so that the friend decl can grant
     364              :  * access to date.m_greg.
     365              :  */
     366         6110 : GncDateTimeImpl::GncDateTimeImpl(const GncDateImpl& date, DayPart part) :
     367         6110 :     m_time{LDT_from_date_daypart(date.m_greg, part,
     368        12220 :                                  tzp->get(date.m_greg.year()))} {}
     369              : 
     370              : /* Member function definitions for GncDateTimeImpl.
     371              :  */
     372              : 
     373              : static bool
     374        15972 : parse_chars_into_num (const char* ptr, const char *end_ptr, int32_t& rv) noexcept
     375              : {
     376        15972 :     auto result = std::from_chars (ptr, end_ptr, rv);
     377        15972 :     return (result.ec == std::errc() && result.ptr == end_ptr);
     378              : }
     379              : 
     380              : static std::optional<PTime>
     381         2872 : fast_iso8601_utc_parse (const char* str)
     382              : {
     383              :     int32_t year, month, mday, hour, min, sec;
     384         2872 :     const size_t len = str ? strnlen (str, 26) : 0;
     385              : 
     386              :     // parse iso-8601 utc format "YYYY-MM-DD HH:MM:SS +0000"
     387         2872 :     constexpr size_t expanded_iso_string_len = 25;
     388         2661 :     if (len == expanded_iso_string_len &&
     389         7983 :         parse_chars_into_num (str,      str +  4, year)  && str[ 4] == '-' &&
     390         7983 :         parse_chars_into_num (str +  5, str +  7, month) && str[ 7] == '-' &&
     391         7983 :         parse_chars_into_num (str +  8, str + 10, mday)  && str[10] == ' ' &&
     392         7983 :         parse_chars_into_num (str + 11, str + 13, hour)  && str[13] == ':' &&
     393         7983 :         parse_chars_into_num (str + 14, str + 16, min)   && str[16] == ':' &&
     394        10855 :         parse_chars_into_num (str + 17, str + 19, sec)   && str[19] == ' ' &&
     395         2661 :         !strcmp (str + 20, "+0000"))
     396              :     {
     397         1562 :         return PTime (boost::gregorian::date (year, month, mday),
     398         2343 :                       boost::posix_time::time_duration (hour, min, sec));
     399              :     }
     400              : 
     401              :     // parse compressed iso-8601 format "YYYYMMDDHHMMSS"
     402         2091 :     constexpr size_t compact_iso_string_len = 14;
     403            1 :     if (len == compact_iso_string_len &&
     404            2 :         parse_chars_into_num (str,      str +  4, year)  &&
     405            2 :         parse_chars_into_num (str +  4, str +  6, month) &&
     406            2 :         parse_chars_into_num (str +  6, str +  8, mday)  &&
     407            2 :         parse_chars_into_num (str +  8, str + 10, hour)  &&
     408         2093 :         parse_chars_into_num (str + 10, str + 12, min)   &&
     409            1 :         parse_chars_into_num (str + 12, str + 14, sec))
     410              :     {
     411            2 :         return PTime (boost::gregorian::date (year, month, mday),
     412            3 :                       boost::posix_time::time_duration (hour, min, sec));
     413              :     }
     414              : 
     415         2090 :     return {};
     416              : }
     417              : 
     418              : static TZ_Ptr
     419         2090 : tz_from_string(std::string str)
     420              : {
     421         2090 :     if (str.empty()) return utc_zone;
     422         1888 :     std::string tzstr = "XXX" + str;
     423         1888 :     if (tzstr.length() > 6 && tzstr[6] != ':') //6 for XXXsHH, s is + or -
     424         1880 :         tzstr.insert(6, ":");
     425         1888 :     if (tzstr.length() > 9 && tzstr[9] != ':') //9 for XXXsHH:MM
     426              :     {
     427            0 :         tzstr.insert(9, ":");
     428              :     }
     429         1888 :     return TZ_Ptr(new PTZ(tzstr));
     430         1888 : }
     431              : 
     432         2873 : GncDateTimeImpl::GncDateTimeImpl(const char* str) :
     433         2873 :     m_time(unix_epoch, utc_zone)
     434              : {
     435         3655 :     if (!str || !str[0]) return;
     436         2872 :     TZ_Ptr tzptr;
     437              :     try
     438              :     {
     439         2872 :         if (auto res = fast_iso8601_utc_parse (str))
     440              :         {
     441          782 :             m_time = LDT_from_date_time(res->date(), res->time_of_day(), utc_zone);
     442          782 :             return;
     443              :         }
     444         2090 :         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})?)?$");
     445         2090 :         static const boost::regex non_delim("^(\\d{14}(?:\\.\\d{0,9})?)\\s*([+-]\\d{2}\\s*(:?\\d{2})?)?$");
     446         2090 :         PTime pdt;
     447         2090 :         boost::cmatch sm;
     448         2090 :         if (regex_match(str, sm, non_delim))
     449              :         {
     450            0 :             std::string time_str(sm[1]);
     451            0 :             time_str.insert(8, "T");
     452            0 :             pdt = boost::posix_time::from_iso_string(time_str);
     453            0 :         }
     454         2090 :         else if (regex_match(str, sm, delim_iso))
     455              :         {
     456         2090 :             pdt = boost::posix_time::time_from_string(sm[1]);
     457              :         }
     458              :         else
     459              :         {
     460            0 :             throw(std::invalid_argument("The date string was not formatted in a way that GncDateTime(const char*) knows how to parse."));
     461              :         }
     462         2090 :         std::string tzstr("");
     463         2090 :         if (sm[2].matched)
     464         1888 :             tzstr += sm[2];
     465         2090 :         tzptr = tz_from_string(tzstr);
     466         2090 :         m_time = LDT_from_date_time(pdt.date(), pdt.time_of_day(), tzptr);
     467         2090 :     }
     468            0 :     catch(boost::gregorian::bad_year&)
     469              :     {
     470            0 :         throw(std::invalid_argument("The date string was outside of the supported year range."));
     471            0 :     }
     472            0 :     catch(std::out_of_range&)
     473              :     {
     474            0 :         throw(std::invalid_argument("The date string was outside of the supported year range."));
     475            0 :     }
     476              :     /* Bug 767824: A GLib bug in parsing the UTC timezone on Windows may have
     477              :      * created a bogus timezone of a random number of minutes. Since there are
     478              :      * no fractional-hour timezones around the prime meridian we can safely
     479              :      * check for this in files by resetting to UTC if there's a
     480              :      * less-than-an-hour offset.
     481              :      */
     482         2090 :     auto offset = tzptr->base_utc_offset().seconds();
     483         2090 :     if (offset != 0 && std::abs(offset) < 3600)
     484            0 :         m_time = m_time.local_time_in(utc_zone);
     485         2872 : }
     486              : 
     487        29776 : GncDateTimeImpl::operator time64() const
     488              : {
     489        29776 :     auto duration = m_time.utc_time() - unix_epoch;
     490        29776 :     auto secs = duration.ticks();
     491        29776 :     secs /= ticks_per_second;
     492        29776 :     return secs;
     493              : }
     494              : 
     495        37302 : GncDateTimeImpl::operator struct tm() const
     496              : {
     497        37302 :     struct tm time = to_tm(m_time);
     498              : #if HAVE_STRUCT_TM_GMTOFF
     499        37302 :     time.tm_gmtoff = offset();
     500              : #endif
     501        37302 :     return time;
     502              : }
     503              : 
     504              : long
     505        37338 : GncDateTimeImpl::offset() const
     506              : {
     507        37338 :     auto offset = m_time.local_time() - m_time.utc_time();
     508        74676 :     return offset.total_seconds();
     509              : }
     510              : 
     511              : std::unique_ptr<GncDateImpl>
     512          207 : GncDateTimeImpl::date() const
     513              : {
     514          414 :     return std::unique_ptr<GncDateImpl>(new GncDateImpl(m_time.local_time().date()));
     515              : }
     516              : 
     517              : /* The 'O', 'E', and '-' format modifiers are not supported by
     518              :  * boost's output facets. Remove them.
     519              :  */
     520              : static inline std::string
     521         3409 : normalize_format (const std::string& format)
     522              : {
     523         3409 :     bool is_pct = false;
     524         3409 :     std::string normalized;
     525         3409 :     std::remove_copy_if(
     526              :         format.begin(), format.end(), back_inserter(normalized),
     527        24842 :         [&is_pct](char e){
     528        24842 :             bool r = (is_pct && (e == 'E' || e == 'O' || e == '-'));
     529        24842 :             is_pct = e == '%';
     530        24842 :             return r;
     531              :         });
     532         3409 :     return normalized;
     533            0 : }
     534              : #ifdef __MINGW32__
     535              : constexpr size_t DATEBUFLEN = 100;
     536              : static std::string
     537              : win_date_format(std::string format, struct tm tm)
     538              : {
     539              : #pragma GCC diagnostic push
     540              : #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
     541              :     wchar_t buf[DATEBUFLEN];
     542              :     memset(buf, 0, DATEBUFLEN * sizeof(wchar_t));
     543              :     std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> conv;
     544              :     [[maybe_unused]] auto numchars = wcsftime(buf, DATEBUFLEN - 1, conv.from_bytes(format).c_str(), &tm);
     545              :     return conv.to_bytes(buf);
     546              : #pragma GCC diagnostic pop
     547              : }
     548              : 
     549              : /* Microsoft's strftime uses the time zone flags differently from
     550              :  * boost::date_time so we need to handle any before passing the
     551              :  * format string to strftime.
     552              :  */
     553              : inline std::string
     554              : win_format_tz_abbrev (std::string format, TZ_Ptr tz, bool is_dst)
     555              : {
     556              :     size_t pos = format.find("%z");
     557              :     if (pos != std::string::npos)
     558              :     {
     559              :         auto tzabbr = tz->has_dst() && is_dst ? tz->dst_zone_abbrev() :
     560              :             tz->std_zone_abbrev();
     561              :         format.replace(pos, 2, tzabbr);
     562              :     }
     563              :     return format;
     564              : }
     565              : 
     566              : inline std::string
     567              : win_format_tz_name (std::string format, TZ_Ptr tz, bool is_dst)
     568              : {
     569              :     size_t pos = format.find("%Z");
     570              :     if (pos != std::string::npos)
     571              :     {
     572              :         auto tzname =  tz->has_dst() && is_dst ? tz->dst_zone_name() :
     573              :             tz->std_zone_name();
     574              :         format.replace(pos, 2, tzname);
     575              :     }
     576              :     return format;
     577              : }
     578              : 
     579              : inline std::string
     580              : win_format_tz_posix (std::string format, TZ_Ptr tz)
     581              : {
     582              :     size_t pos = format.find("%ZP");
     583              :     if (pos != std::string::npos)
     584              :         format.replace(pos, 2, tz->to_posix_string());
     585              :     return format;
     586              : }
     587              : 
     588              : #endif
     589              : std::string
     590         3333 : GncDateTimeImpl::format(const char* format) const
     591              : {
     592              : #ifdef __MINGW32__
     593              :     auto tz = m_time.zone();
     594              :     auto tm =  static_cast<struct tm>(*this);
     595              :     auto sformat = win_format_tz_abbrev(format, tz, tm.tm_isdst);
     596              :     sformat = win_format_tz_name(sformat, tz, tm.tm_isdst);
     597              :     sformat = win_format_tz_posix(sformat, tz);
     598              :     return win_date_format(sformat, tm);
     599              : #else
     600              :     using Facet = boost::local_time::local_time_facet;
     601         9999 :     auto output_facet(new Facet(normalize_format(format).c_str()));
     602         3333 :     std::stringstream ss;
     603         3333 :     ss.imbue(std::locale(gnc_get_locale(), output_facet));
     604         3333 :     ss << m_time;
     605         6666 :     return ss.str();
     606              : #endif
     607         3333 : }
     608              : 
     609              : std::string
     610            0 : GncDateTimeImpl::format_zulu(const char* format) const
     611              : {
     612              : #ifdef __MINGW32__
     613              :     auto tz = utc_zone;
     614              :     auto tm =  static_cast<struct tm>(*this);
     615              :     auto sformat = win_format_tz_abbrev(format, tz, tm.tm_isdst);
     616              :     sformat = win_format_tz_name(sformat, tz, tm.tm_isdst);
     617              :     sformat = win_format_tz_posix(sformat, tz);
     618              :     return win_date_format(sformat, utc_tm());
     619              : #else
     620              :     using Facet = boost::local_time::local_time_facet;
     621            0 :     auto zulu_time = LDT{m_time.utc_time(), utc_zone};
     622            0 :     auto output_facet(new Facet(normalize_format(format).c_str()));
     623            0 :     std::stringstream ss;
     624            0 :     ss.imbue(std::locale(gnc_get_locale(), output_facet));
     625            0 :     ss << zulu_time;
     626            0 :     return ss.str();
     627              : #endif
     628            0 : }
     629              : 
     630              : std::string
     631        20777 : GncDateTimeImpl::format_iso8601() const
     632              : {
     633        20777 :     auto str = boost::posix_time::to_iso_extended_string(m_time.utc_time());
     634        20777 :     str[10] = ' ';
     635        41554 :     return str.substr(0, 19);
     636        20777 : }
     637              : 
     638              : std::string
     639           50 : GncDateTimeImpl::timestamp()
     640              : {
     641           50 :     GncDateTimeImpl gdt;
     642           50 :     auto str = boost::posix_time::to_iso_string(gdt.m_time.local_time());
     643          100 :     return str.substr(0, 8) + str.substr(9, 15);
     644           50 : }
     645              : 
     646              : struct ICUResources
     647              : {
     648              :     std::unique_ptr<icu::DateFormat> formatter;
     649              :     std::unique_ptr<icu::Calendar> calendar;
     650              : };
     651              : 
     652              : static ICUResources&
     653            0 : get_icu_resources()
     654              : {
     655            0 :     static ICUResources rv;
     656              : 
     657            0 :     if (!rv.formatter)
     658              :     {
     659            0 :         icu::Locale locale;
     660            0 :         if (auto lc_time_locale = setlocale (LC_TIME, nullptr))
     661              :         {
     662            0 :             std::string localeStr(lc_time_locale);
     663            0 :             if (size_t dotPos = localeStr.find('.'); dotPos != std::string::npos)
     664            0 :                 localeStr = localeStr.substr(0, dotPos);
     665              : 
     666            0 :             locale = icu::Locale::createCanonical (localeStr.c_str());
     667            0 :         }
     668              : 
     669            0 :         rv.formatter.reset(icu::DateFormat::createDateInstance(icu::DateFormat::kDefault, locale));
     670            0 :         if (!rv.formatter)
     671            0 :             throw std::invalid_argument("Cannot create date formatter.");
     672              : 
     673            0 :         UErrorCode status = U_ZERO_ERROR;
     674            0 :         rv.calendar.reset(icu::Calendar::createInstance(locale, status));
     675            0 :         if (U_FAILURE(status))
     676            0 :             throw std::invalid_argument("Cannot create calendar instance.");
     677              : 
     678            0 :         rv.calendar->setLenient(false);
     679            0 :     }
     680              : 
     681            0 :     return rv;
     682              : }
     683              : 
     684              : static Date
     685            0 : gregorian_date_from_locale_string (const std::string& str)
     686              : {
     687            0 :     ICUResources& resources = get_icu_resources();
     688              : 
     689            0 :     icu::UnicodeString input = icu::UnicodeString::fromUTF8(str);
     690            0 :     icu::ParsePosition parsePos;
     691            0 :     UDate date = resources.formatter->parse(input, parsePos);
     692            0 :     if (parsePos.getErrorIndex() != -1 || parsePos.getIndex() != input.length())
     693            0 :         throw std::invalid_argument ("Cannot parse string");
     694              : 
     695            0 :     UErrorCode status = U_ZERO_ERROR;
     696            0 :     resources.calendar->setTime(date, status);
     697            0 :     if (U_FAILURE(status))
     698            0 :         throw std::invalid_argument ("Cannot set calendar time");
     699              : 
     700            0 :     return Date (resources.calendar->get(UCAL_YEAR, status),
     701            0 :                  resources.calendar->get(UCAL_MONTH, status) + 1,
     702            0 :                  resources.calendar->get(UCAL_DATE, status));
     703            0 : }
     704              : 
     705              : /* Member function definitions for GncDateImpl.
     706              :  */
     707            5 : GncDateImpl::GncDateImpl(const std::string str, const std::string fmt) :
     708            5 :     m_greg(boost::gregorian::day_clock::local_day()) /* Temporarily initialized to today, will be used and adjusted in the code below */
     709              : {
     710            5 :     auto iter = std::find_if(GncDate::c_formats.cbegin(), GncDate::c_formats.cend(),
     711           15 :                              [&fmt](const GncDateFormat& v){ return (v.m_fmt == fmt); } );
     712           10 :     if (iter == GncDate::c_formats.cend())
     713            0 :         throw std::invalid_argument(N_("Unknown date format specifier passed as argument."));
     714              : 
     715            5 :     if (iter->m_str_to_date)
     716              :     {
     717              :         try
     718              :         {
     719            5 :             m_greg = (*iter->m_str_to_date)(str);
     720            5 :             return;
     721              :         }
     722            0 :         catch (...) {}          // with any string->date exception, try regex
     723              :     }
     724              : 
     725            0 :     if (iter->m_re.empty())
     726            0 :         throw std::invalid_argument ("No regex pattern available");
     727              : 
     728            0 :     boost::smatch what;
     729            0 :     if(!boost::regex_search(str, what, iter->m_re))  // regex didn't find a match
     730            0 :         throw std::invalid_argument (N_("Value can't be parsed into a date using the selected date format."));
     731              : 
     732              :     // Bail out if a year was found with a yearless format specifier
     733            0 :     auto fmt_has_year = (fmt.find('y') != std::string::npos);
     734            0 :     if (!fmt_has_year && (what.length("YEAR") != 0))
     735            0 :         throw std::invalid_argument (N_("Value appears to contain a year while the selected format forbids this."));
     736              : 
     737              :     int year;
     738            0 :     if (fmt_has_year)
     739              :     {
     740              :         /* The input dates have a year, so use that one */
     741            0 :         year = std::stoi (what.str("YEAR"));
     742              : 
     743              :         /* We assume two-digit years to be in the range 1969 - 2068. */
     744            0 :         if (year < 69)
     745            0 :                 year += 2000;
     746            0 :         else if (year < 100)
     747            0 :                 year += 1900;
     748              :     }
     749              :     else /* The input dates have no year, so use current year */
     750            0 :         year = m_greg.year(); // Can use m_greg here as it was already initialized in the initializer list earlier
     751              : 
     752            0 :     m_greg = Date(year,
     753            0 :                   static_cast<Month>(std::stoi (what.str("MONTH"))),
     754            0 :                   std::stoi (what.str("DAY")));
     755            0 : }
     756              : 
     757              : gnc_ymd
     758          495 : GncDateImpl::year_month_day() const
     759              : {
     760          495 :     auto boost_ymd = m_greg.year_month_day();
     761          990 :     return {boost_ymd.year, boost_ymd.month.as_number(), boost_ymd.day};
     762              : }
     763              : 
     764              : std::string
     765           76 : GncDateImpl::format(const char* format) const
     766              : {
     767              : #ifdef __MINGW32__
     768              :     return win_date_format(format, to_tm(m_greg));
     769              : #else
     770              :     using Facet = boost::gregorian::date_facet;
     771           76 :     std::stringstream ss;
     772              :     //The stream destructor frees the facet, so it must be heap-allocated.
     773          228 :     auto output_facet(new Facet(normalize_format(format).c_str()));
     774           76 :     ss.imbue(std::locale(gnc_get_locale(), output_facet));
     775           76 :     ss << m_greg;
     776          152 :     return ss.str();
     777              : #endif
     778           76 : }
     779              : 
     780            0 : bool operator<(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg < b.m_greg; }
     781            0 : bool operator>(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg > b.m_greg; }
     782            0 : bool operator==(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg == b.m_greg; }
     783            0 : bool operator<=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg <= b.m_greg; }
     784            0 : bool operator>=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg >= b.m_greg; }
     785            0 : bool operator!=(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg != b.m_greg; }
     786              : 
     787              : /* =================== Presentation-class Implementations ====================*/
     788              : /* GncDateTime */
     789              : 
     790        10101 : GncDateTime::GncDateTime() : m_impl(new GncDateTimeImpl) {}
     791        51095 : GncDateTime::GncDateTime(const time64 time) :
     792        51095 :     m_impl(new GncDateTimeImpl(time)) {}
     793        10692 : GncDateTime::GncDateTime(const struct tm tm) :
     794        10692 :     m_impl(new GncDateTimeImpl(tm)) {}
     795          124 : GncDateTime::GncDateTime(const std::string& str) :
     796          124 :     m_impl(new GncDateTimeImpl(str)) {}
     797         2749 : GncDateTime::GncDateTime(const char* str) :
     798         2749 :     m_impl(new GncDateTimeImpl(str)) {}
     799        80869 : GncDateTime::~GncDateTime() = default;
     800              : 
     801         6110 : GncDateTime::GncDateTime(const GncDate& date, DayPart part) :
     802         6110 :     m_impl(new GncDateTimeImpl(*(date.m_impl), part)) {}
     803              : 
     804              : void
     805            0 : GncDateTime::now()
     806              : {
     807            0 :     m_impl->now();
     808            0 : }
     809              : 
     810        29776 : GncDateTime::operator time64() const
     811              : {
     812        29776 :     return m_impl->operator time64();
     813              : }
     814              : 
     815        37302 : GncDateTime::operator struct tm() const
     816              : {
     817        37302 :     return m_impl->operator struct tm();
     818              : }
     819              : 
     820              : long
     821           36 : GncDateTime::offset() const
     822              : {
     823           36 :     return m_impl->offset();
     824              : }
     825              : 
     826              : struct tm
     827           15 : GncDateTime::utc_tm() const
     828              : {
     829           15 :     return m_impl->utc_tm();
     830              : }
     831              : 
     832              : GncDate
     833          207 : GncDateTime::date() const
     834              : {
     835          207 :     return GncDate(m_impl->date());
     836              : }
     837              : 
     838              : std::string
     839         3333 : GncDateTime::format(const char* format) const
     840              : {
     841         3333 :     return m_impl->format(format);
     842              : }
     843              : 
     844              : std::string
     845            0 : GncDateTime::format_zulu(const char* format) const
     846              : {
     847            0 :     return m_impl->format_zulu(format);
     848              : }
     849              : 
     850              : std::string
     851        20777 : GncDateTime::format_iso8601() const
     852              : {
     853        20777 :     return m_impl->format_iso8601();
     854              : }
     855              : 
     856              : std::string
     857           50 : GncDateTime::timestamp()
     858              : {
     859           50 :     return GncDateTimeImpl::timestamp();
     860              : }
     861              : 
     862              : /* GncDate */
     863          288 : GncDate::GncDate() : m_impl{new GncDateImpl} {}
     864         6198 : GncDate::GncDate(int year, int month, int day) :
     865         6198 : m_impl(new GncDateImpl(year, month, day)) {}
     866            5 : GncDate::GncDate(const std::string str, const std::string fmt) :
     867            5 : m_impl(new GncDateImpl(str, fmt)) {}
     868          207 : GncDate::GncDate(std::unique_ptr<GncDateImpl> impl) :
     869          414 : m_impl(std::move(impl)) {}
     870            0 : GncDate::GncDate(const GncDate& a) :
     871            0 : m_impl(new GncDateImpl(*a.m_impl)) {}
     872            0 : GncDate::GncDate(GncDate&&) = default;
     873         6681 : GncDate::~GncDate() = default;
     874              : 
     875              : GncDate&
     876            0 : GncDate::operator=(const GncDate& a)
     877              : {
     878            0 :     m_impl.reset(new GncDateImpl(*a.m_impl));
     879            0 :     return *this;
     880              : }
     881              : GncDate&
     882            0 : GncDate::operator=(GncDate&&) = default;
     883              : 
     884              : void
     885            0 : GncDate::today()
     886              : {
     887            0 :     m_impl->today();
     888            0 : }
     889              : 
     890              : std::string
     891           76 : GncDate::format(const char* format)
     892              : {
     893           76 :     return m_impl->format(format);
     894              : }
     895              : 
     896              : gnc_ymd
     897          495 : GncDate::year_month_day() const
     898              : {
     899          495 :     return m_impl->year_month_day();
     900              : }
     901              : 
     902            0 : bool operator<(const GncDate& a, const GncDate& b) { return *(a.m_impl) < *(b.m_impl); }
     903            0 : bool operator>(const GncDate& a, const GncDate& b) { return *(a.m_impl) > *(b.m_impl); }
     904            0 : bool operator==(const GncDate& a, const GncDate& b) { return *(a.m_impl) == *(b.m_impl); }
     905            0 : bool operator<=(const GncDate& a, const GncDate& b) { return *(a.m_impl) <= *(b.m_impl); }
     906            0 : bool operator>=(const GncDate& a, const GncDate& b) { return *(a.m_impl) >= *(b.m_impl); }
     907            0 : bool operator!=(const GncDate& a, const GncDate& b) { return *(a.m_impl) != *(b.m_impl); }
        

Generated by: LCOV version 2.0-1