LCOV - code coverage report
Current view: top level - libgnucash/engine - gnc-date.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 69.9 % 762 533
Test Date: 2026-11-02 18:19:43 Functions: 80.2 % 91 73
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /********************************************************************\
       2              :  * gnc-date.cpp -- C interface for date and time                    *
       3              :  *                                                                  *
       4              :  * Copyright 1997 Robin D. Clark <rclark@cs.hmc.edu>                *
       5              :  * Copyright 1998-2000, 2003 Linas Vepstas <linas@linas.org>        *
       6              :  * Copyright (C) 2005 David Hampton <hampton@employees.org>         *
       7              :  * Copyright 2011-2015 John Ralls <jralls@ceridwen.us               *
       8              :  *                                                                  *
       9              :  * This program is free software; you can redistribute it and/or    *
      10              :  * modify it under the terms of the GNU General Public License as   *
      11              :  * published by the Free Software Foundation; either version 2 of   *
      12              :  * the License, or (at your option) any later version.              *
      13              :  *                                                                  *
      14              :  * This program is distributed in the hope that it will be useful,  *
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
      17              :  * GNU General Public License for more details.                     *
      18              :  *                                                                  *
      19              :  * You should have received a copy of the GNU General Public License*
      20              :  * along with this program; if not, contact:                        *
      21              :  *                                                                  *
      22              :  * Free Software Foundation           Voice:  +1-617-542-5942       *
      23              :  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
      24              :  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
      25              :  *                                                                  *
      26              : \********************************************************************/
      27              : 
      28              : #include <glib.h>
      29              : 
      30              : #include <config.h>
      31              : #include <libintl.h>
      32              : #include <stdlib.h>
      33              : #include "platform.h"
      34              : #include "qof.h"
      35              : 
      36              : #ifdef HAVE_LANGINFO_D_FMT
      37              : # include <langinfo.h>
      38              : #endif
      39              : #ifndef HAVE_STRPTIME
      40              : #include <strptime.h>
      41              : #endif
      42              : #ifdef G_OS_WIN32
      43              : #  include <windows.h>
      44              : #endif
      45              : 
      46              : #include <cinttypes>
      47              : #include <unicode/calendar.h>
      48              : #include "unicode/dtitvfmt.h"
      49              : #include "unicode/smpdtfmt.h"
      50              : 
      51              : #include "gnc-date.h"
      52              : #include "gnc-date-p.h"
      53              : #include "gnc-datetime.hpp"
      54              : #include "gnc-timezone.hpp"
      55              : #define BOOST_ERROR_CODE_HEADER_ONLY
      56              : #include <boost/date_time/local_time/local_time.hpp>
      57              : 
      58              : #define N_(string) string //So that xgettext will find it
      59              : 
      60              : #ifdef HAVE_LANGINFO_D_FMT
      61              : #  define GNC_D_FMT (nl_langinfo (D_FMT))
      62              : #  define GNC_D_T_FMT (nl_langinfo (D_T_FMT))
      63              : #  define GNC_T_FMT (nl_langinfo (T_FMT))
      64              : #elif defined(G_OS_WIN32)
      65              : #  define GNC_D_FMT (qof_win32_get_time_format(QOF_WIN32_PICTURE_DATE))
      66              : #  define GNC_T_FMT (qof_win32_get_time_format(QOF_WIN32_PICTURE_TIME))
      67              : #  define GNC_D_T_FMT (qof_win32_get_time_format(QOF_WIN32_PICTURE_DATETIME))
      68              : #else
      69              : #  define GNC_D_FMT "%Y-%m-%d"
      70              : #  define GNC_D_T_FMT "%Y-%m-%d %r"
      71              : #  define GNC_T_FMT "%r"
      72              : #endif
      73              : 
      74              : const char *gnc_default_strftime_date_format =
      75              : #ifdef G_OS_WIN32
      76              :     /* The default date format for use with strftime in Win32. */
      77              :     N_("%B %#d, %Y")
      78              : #else
      79              :     /* The default date format for use with strftime in other OS. */
      80              :     /* Translators: call "man strftime" for possible values. */
      81              :     N_("%B %e, %Y")
      82              : #endif
      83              :     ;
      84              : 
      85              : /* This is now user configured through the gnome options system() */
      86              : static QofDateFormat dateFormat = QOF_DATE_FORMAT_LOCALE;
      87              : static QofDateFormat prevQofDateFormat = QOF_DATE_FORMAT_LOCALE;
      88              : 
      89              : static QofDateCompletion dateCompletion = QOF_DATE_COMPLETION_THISYEAR;
      90              : static int dateCompletionBackMonths = 6;
      91              : 
      92              : /* This static indicates the debugging module that this .o belongs to. */
      93              : static QofLogModule log_module = QOF_MOD_ENGINE;
      94              : 
      95              : /****************** Posix Replacement Functions ***************************/
      96              : void
      97        19595 : gnc_tm_free (struct tm* time)
      98              : {
      99        19595 :     free(time);
     100        19595 : }
     101              : 
     102              : struct tm*
     103        19590 : gnc_localtime (const time64 *secs)
     104              : {
     105        19590 :     auto time = static_cast<struct tm*>(calloc(1, sizeof(struct tm)));
     106        19590 :     if (gnc_localtime_r (secs, time) == nullptr)
     107              :     {
     108            1 :         gnc_tm_free (time);
     109            1 :         return nullptr;
     110              :     }
     111        19589 :     return time;
     112              : }
     113              : 
     114              : struct tm*
     115        26460 : gnc_localtime_r (const time64 *secs, struct tm* time)
     116              : {
     117              :     try
     118              :     {
     119        26460 :         *time = static_cast<struct tm>(GncDateTime(*secs));
     120        26459 :         return time;
     121              :     }
     122            1 :     catch(std::invalid_argument&)
     123              :     {
     124            1 :         return nullptr;
     125            1 :     }
     126              : }
     127              : 
     128              : static void
     129        42680 : normalize_time_component (int *inner, int *outer, int divisor)
     130              : {
     131        42753 :      while (*inner < 0)
     132              :      {
     133           73 :           --(*outer);
     134           73 :           *inner += divisor;
     135              :      }
     136        51102 :      while (*inner >= divisor)
     137              :      {
     138         8422 :           ++(*outer);
     139         8422 :           *inner -= divisor;
     140              :      }
     141        42680 : }
     142              : 
     143              : static void
     144        10572 : normalize_struct_tm (struct tm* time)
     145              : {
     146        10572 :      gint year = time->tm_year + 1900;
     147              :      gint last_day;
     148              : 
     149              :      /* Gregorian_date throws if it gets an out-of-range year
     150              :       * so clamp year into gregorian_date's range.
     151              :       */
     152        10572 :      if (year < 1400) year += 1400;
     153        10572 :      if (year > 9999) year %= 10000;
     154              : 
     155        10572 :      normalize_time_component (&(time->tm_sec), &(time->tm_min), 60);
     156        10572 :      normalize_time_component (&(time->tm_min), &(time->tm_hour), 60);
     157        10572 :      normalize_time_component (&(time->tm_hour), &(time->tm_mday), 24);
     158        10572 :      normalize_time_component (&(time->tm_mon), &year, 12);
     159              : 
     160              :      // auto month_in_range = []int (int m){ return (m + 12) % 12; }
     161        10663 :      while (time->tm_mday < 1)
     162              :      {
     163           91 :          normalize_time_component (&(--time->tm_mon), &year, 12);
     164           91 :          last_day = gnc_date_get_last_mday (time->tm_mon, year);
     165           91 :          time->tm_mday += last_day;
     166              :      }
     167        10572 :      last_day = gnc_date_get_last_mday (time->tm_mon, year);
     168        10873 :      while (time->tm_mday > last_day)
     169              :      {
     170          301 :           time->tm_mday -= last_day;
     171          301 :           normalize_time_component(&(++time->tm_mon), &year, 12);
     172          301 :           last_day = gnc_date_get_last_mday (time->tm_mon, year);
     173              :      }
     174        10572 :      time->tm_year = year - 1900;
     175        10572 : }
     176              : 
     177              : struct tm*
     178           16 : gnc_gmtime (const time64 *secs)
     179              : {
     180              :     try
     181              :     {
     182           16 :         GncDateTime gncdt(*secs);
     183           15 :         auto time = static_cast<struct tm*>(calloc(1, sizeof(struct tm)));
     184           15 :         *time = gncdt.utc_tm();
     185           15 :         return time;
     186           15 :     }
     187            1 :     catch(std::invalid_argument&)
     188              :     {
     189            1 :         return nullptr;
     190            1 :     }
     191              : 
     192              : }
     193              : 
     194              : gint
     195           43 : gnc_start_of_week (void)
     196              : {
     197              :     /* icu's day of week is 1 based. Using 0 here to mean unset or error while setting */
     198              :     static int cached_result = 0;
     199              : 
     200           43 :     if (!cached_result)
     201              :     {
     202           43 :         UErrorCode err = U_ZERO_ERROR;
     203           43 :         auto cal = icu::Calendar::createInstance (err);
     204           43 :         if (!cal)
     205              :         {
     206            0 :             PERR("ICU error: %s\n", u_errorName (err));
     207            0 :             return 0;
     208              :         }
     209              : 
     210              :         /* 1 for sunday, 2 for monday, etc. */
     211           43 :         cached_result = cal->getFirstDayOfWeek (err);
     212           43 :         delete cal;
     213              :     }
     214              : 
     215           43 :     return cached_result;
     216              : }
     217              : 
     218              : time64
     219        10523 : gnc_mktime (struct tm* time)
     220              : {
     221              :     try
     222              :     {
     223        10523 :         normalize_struct_tm (time);
     224        10523 :         GncDateTime gncdt(*time);
     225        10523 :         *time = static_cast<struct tm>(gncdt);
     226        10523 :         return static_cast<time64>(gncdt);
     227        10523 :     }
     228            0 :     catch(std::invalid_argument&)
     229              :     {
     230            0 :         return 0;
     231            0 :     }
     232              : }
     233              : 
     234              : time64
     235           18 : gnc_timegm (struct tm* time)
     236              : {
     237              :     try
     238              :     {
     239           18 :         normalize_struct_tm(time);
     240           18 :         GncDateTime gncdt(*time);
     241           18 :         *time = static_cast<struct tm>(gncdt);
     242           18 :         time->tm_sec -= gncdt.offset();
     243           18 :         normalize_struct_tm(time);
     244              : #ifdef HAVE_STRUCT_TM_GMTOFF
     245           18 :         time->tm_gmtoff = 0;
     246              : #endif
     247           18 :         return static_cast<time64>(gncdt) - gncdt.offset();
     248           18 :     }
     249            0 :     catch(std::invalid_argument&)
     250              :     {
     251            0 :         return 0;
     252            0 :     }
     253              : }
     254              : 
     255              : char*
     256            6 : gnc_ctime (const time64 *secs)
     257              : {
     258            6 :     return gnc_print_time64(*secs, "%a %b %d %H:%M:%S %Y");
     259              : }
     260              : 
     261              : time64
     262         9792 : gnc_time (time64 *tbuf)
     263              : {
     264         9792 :     GncDateTime gncdt;
     265         9792 :     auto time = static_cast<time64>(gncdt);
     266         9792 :     if (tbuf != nullptr)
     267           14 :         *tbuf = time;
     268         9792 :     return time;
     269         9792 : }
     270              : 
     271              : gdouble
     272            0 : gnc_difftime (const time64 secs1, const time64 secs2)
     273              : {
     274            0 :     PWARN ("gnc_difftime is deprecated");
     275            0 :     return (double)secs1 - (double)secs2;
     276              : }
     277              : 
     278              : /****************************************************************************/
     279              : 
     280              : const char*
     281            8 : gnc_date_dateformat_to_string(QofDateFormat format)
     282              : {
     283            8 :     switch (format)
     284              :     {
     285            1 :     case QOF_DATE_FORMAT_US:
     286            1 :         return "us";
     287            1 :     case QOF_DATE_FORMAT_UK:
     288            1 :         return "uk";
     289            1 :     case QOF_DATE_FORMAT_CE:
     290            1 :         return "ce";
     291            1 :     case QOF_DATE_FORMAT_ISO:
     292            1 :         return "iso";
     293            1 :     case QOF_DATE_FORMAT_UTC:
     294            1 :         return "utc";
     295            1 :     case QOF_DATE_FORMAT_LOCALE:
     296            1 :         return "locale";
     297            1 :     case QOF_DATE_FORMAT_CUSTOM:
     298            1 :         return "custom";
     299            1 :     case QOF_DATE_FORMAT_UNSET:
     300            1 :         return "unset";
     301            0 :     default:
     302            0 :         return nullptr;
     303              :     }
     304              : }
     305              : 
     306              : gboolean
     307           11 : gnc_date_string_to_dateformat(const char* fmt_str, QofDateFormat *format)
     308              : {
     309           11 :     if (!fmt_str)
     310            1 :         return TRUE;
     311              : 
     312           10 :     if (!strcmp(fmt_str, "us"))
     313            1 :         *format = QOF_DATE_FORMAT_US;
     314            9 :     else if (!strcmp(fmt_str, "uk"))
     315            1 :         *format = QOF_DATE_FORMAT_UK;
     316            8 :     else if (!strcmp(fmt_str, "ce"))
     317            1 :         *format = QOF_DATE_FORMAT_CE;
     318            7 :     else if (!strcmp(fmt_str, "utc"))
     319            1 :         *format = QOF_DATE_FORMAT_UTC;
     320            6 :     else if (!strcmp(fmt_str, "iso"))
     321            1 :         *format = QOF_DATE_FORMAT_ISO;
     322            5 :     else if (!strcmp(fmt_str, "locale"))
     323            1 :         *format = QOF_DATE_FORMAT_LOCALE;
     324            4 :     else if (!strcmp(fmt_str, "custom"))
     325            1 :         *format = QOF_DATE_FORMAT_CUSTOM;
     326            3 :     else if (!strcmp(fmt_str, "unset"))
     327            1 :         *format = QOF_DATE_FORMAT_UNSET;
     328              :     else
     329            2 :         return TRUE;
     330              : 
     331            8 :     return FALSE;
     332              : }
     333              : 
     334              : const char*
     335            4 : gnc_date_monthformat_to_string(GNCDateMonthFormat format)
     336              : {
     337              :  //avoid UB if format is out of range
     338            4 :     switch (static_cast<uint8_t>(format))
     339              :     {
     340            1 :     case GNCDATE_MONTH_NUMBER:
     341            1 :         return "number";
     342            1 :     case GNCDATE_MONTH_ABBREV:
     343            1 :         return "abbrev";
     344            1 :     case GNCDATE_MONTH_NAME:
     345            1 :         return "name";
     346            1 :     default:
     347            1 :         return nullptr;
     348              :     }
     349              : }
     350              : 
     351              : gboolean
     352            6 : gnc_date_string_to_monthformat(const char *fmt_str, GNCDateMonthFormat *format)
     353              : {
     354            6 :     if (!fmt_str)
     355            1 :         return TRUE;
     356              : 
     357            5 :     if (!strcmp(fmt_str, "number"))
     358            1 :         *format = GNCDATE_MONTH_NUMBER;
     359            4 :     else if (!strcmp(fmt_str, "abbrev"))
     360            1 :         *format = GNCDATE_MONTH_ABBREV;
     361            3 :     else if (!strcmp(fmt_str, "name"))
     362            1 :         *format = GNCDATE_MONTH_NAME;
     363              :     else
     364            2 :         return TRUE;
     365              : 
     366            3 :     return FALSE;
     367              : }
     368              : 
     369              : char*
     370          798 : gnc_print_time64(time64 time, const char* format)
     371              : {
     372              :     try
     373              :     {
     374          798 :         GncDateTime gncdt(time);
     375          798 :         auto sstr = gncdt.format(format);
     376              :         //ugly C allocation so that the ptr can be freed at the other end
     377          798 :         char* cstr = static_cast<char*>(malloc(sstr.length() + 1));
     378          798 :         memset(cstr, 0, sstr.length() + 1);
     379          798 :         strncpy(cstr, sstr.c_str(), sstr.length());
     380          798 :         return cstr;
     381          798 :     }
     382            0 :     catch(std::runtime_error& err)
     383              :     {
     384            0 :         PWARN("Error processing time64 %" PRId64 ": %s", time, err.what());
     385            0 :         return nullptr;
     386            0 :     }
     387            0 :     catch(std::logic_error& err)
     388              :     {
     389            0 :         PWARN("Error processing time64 %" PRId64 ": %s", time, err.what());
     390            0 :         return nullptr;
     391            0 :     }
     392              : }
     393              : 
     394              : /********************************************************************\
     395              : \********************************************************************/
     396              : 
     397              : 
     398              : /* Converts any time on a day to midday that day.
     399              : 
     400              :  * given a timepair contains any time on a certain day (local time)
     401              :  * converts it to be midday that day.
     402              :  */
     403              : time64
     404         4777 : time64CanonicalDayTime (time64 t)
     405              : {
     406              :     struct tm tm;
     407         4777 :     gnc_localtime_r(&t, &tm);
     408         4777 :     gnc_tm_set_day_middle(&tm);
     409         9554 :     return gnc_mktime (&tm);
     410              : }
     411              : 
     412              : /* NB: month is 0-11, year is 0001 - 9999. */
     413        11238 : int gnc_date_get_last_mday (int month, int year)
     414              : {
     415              :     static int last_day_of_month[12] =
     416              :         {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
     417              :   
     418        11238 :     g_assert(month >= 0 && month < 12);
     419              : 
     420              :     // To be a leap year, the year number must be divisible by four,
     421              :     // except for end-of-century years, which must be divisible by 400.
     422              : 
     423        11238 :     return last_day_of_month[month] +
     424        11238 :         (month == 1 && year % 4 == 0 && !(year % 100 == 0 && year % 400 != 0) ?
     425        11238 :         1 : 0);
     426              : }
     427              : 
     428          233 : QofDateFormat qof_date_format_get (void)
     429              : {
     430          233 :     return dateFormat;
     431              : }
     432              : 
     433          117 : void qof_date_format_set(QofDateFormat df)
     434              : {
     435              : //avoid UB if df is out of range
     436          117 :     auto dfi{static_cast<uint8_t>(df)};
     437          117 :     if (dfi >= DATE_FORMAT_FIRST && dfi <= DATE_FORMAT_LAST)
     438              :     {
     439          116 :         prevQofDateFormat = dateFormat;
     440          116 :         dateFormat = df;
     441              :     }
     442              :     else
     443              :     {
     444              :         /* hack alert - Use a neutral default. */
     445            1 :         PERR("non-existent date format set attempted. Setting ISO default");
     446            1 :         prevQofDateFormat = dateFormat;
     447            1 :         dateFormat = QOF_DATE_FORMAT_ISO;
     448              :     }
     449              : 
     450          117 :     return;
     451              : }
     452              : 
     453              : /* set date completion method
     454              : 
     455              : set dateCompletion to one of QOF_DATE_COMPLETION_THISYEAR (for
     456              : completing the year to the current calendar year) or
     457              : QOF_DATE_COMPLETION_SLIDING (for using a sliding 12-month window). The
     458              : sliding window starts 'backmonth' months before the current month (0-11).
     459              : checks to make sure it's a legal value
     460              : 
     461              : param QofDateCompletion: indicates preferred completion method
     462              : param int: the number of months to go back in time (0-11)
     463              : 
     464              : return void
     465              : 
     466              : Globals: dateCompletion dateCompletionBackMonths
     467              : */
     468            1 : void qof_date_completion_set(QofDateCompletion dc, int backmonths)
     469              : {
     470            1 :     if (dc == QOF_DATE_COMPLETION_THISYEAR ||
     471              :             dc == QOF_DATE_COMPLETION_SLIDING)
     472              :     {
     473            1 :         dateCompletion = dc;
     474              :     }
     475              :     else
     476              :     {
     477              :         /* hack alert - Use a neutral default. */
     478            0 :         PERR("non-existent date completion set attempted. Setting current year completion as default");
     479            0 :         dateCompletion = QOF_DATE_COMPLETION_THISYEAR;
     480              :     }
     481              : 
     482            1 :     if (backmonths < 0)
     483              :     {
     484            0 :         backmonths = 0;
     485              :     }
     486            1 :     else if (backmonths > 11)
     487              :     {
     488            0 :         backmonths = 11;
     489              :     }
     490            1 :     dateCompletionBackMonths = backmonths;
     491              : 
     492            1 :     return;
     493              : }
     494              : 
     495              : /*
     496              :  qof_date_format_get_string
     497              :  get the date format string for the current format
     498              :  returns: string
     499              : 
     500              :  Globals: dateFormat
     501              : */
     502         2674 : const gchar *qof_date_format_get_string(QofDateFormat df)
     503              : {
     504         2674 :     switch (df)
     505              :     {
     506           12 :     case QOF_DATE_FORMAT_US:
     507           12 :         return "%m/%d/%Y";
     508           12 :     case QOF_DATE_FORMAT_UK:
     509           12 :         return "%d/%m/%Y";
     510           12 :     case QOF_DATE_FORMAT_CE:
     511           12 :         return "%d.%m.%Y";
     512            0 :     case QOF_DATE_FORMAT_UTC:
     513            0 :         return "%Y-%m-%dT%H:%M:%SZ";
     514         1502 :     case QOF_DATE_FORMAT_ISO:
     515         1502 :         return "%Y-%m-%d";
     516            0 :     case QOF_DATE_FORMAT_UNSET: // use global
     517            0 :         return qof_date_format_get_string (dateFormat);
     518         1136 :     case QOF_DATE_FORMAT_LOCALE:
     519              :     default:
     520         1136 :         break;
     521              :     };
     522         1136 :     return GNC_D_FMT;
     523              : }
     524              : 
     525            0 : const gchar *qof_date_text_format_get_string(QofDateFormat df)
     526              : {
     527            0 :     switch (df)
     528              :     {
     529            0 :     case QOF_DATE_FORMAT_US:
     530            0 :         return "%b %d, %Y";
     531            0 :     case QOF_DATE_FORMAT_UK:
     532              :     case QOF_DATE_FORMAT_CE:
     533            0 :         return "%d %b %Y";
     534            0 :     case QOF_DATE_FORMAT_UTC:
     535            0 :         return "%Y-%m-%dT%H:%M:%SZ";
     536            0 :     case QOF_DATE_FORMAT_ISO:
     537            0 :         return "%Y-%b-%d";
     538            0 :     case QOF_DATE_FORMAT_UNSET: // use global
     539            0 :         return qof_date_text_format_get_string (dateFormat);
     540            0 :     case QOF_DATE_FORMAT_LOCALE:
     541              :     default:
     542            0 :         break;
     543              :     };
     544            0 :     return GNC_D_FMT;
     545              : }
     546              : 
     547              : size_t
     548           76 : qof_print_date_dmy_buff (char * buff, const size_t len, int day, int month, int year)
     549              : {
     550           76 :     if (!buff) return 0;
     551              : 
     552              :     try
     553              :     {
     554           76 :         GncDate date(year, month, day);
     555           76 :         std::string str = date.format(qof_date_format_get_string(dateFormat));
     556           76 :         strncpy(buff, str.c_str(), len);
     557           76 :         if (str.length() >= len)
     558            0 :             buff[len - 1] = '\0';
     559           76 :     }
     560            0 :     catch(std::logic_error& err)
     561              :     {
     562            0 :         PWARN("Error processing year-month-day %d-%d-%d: %s",
     563              :               year, month, day, err.what());
     564            0 :     }
     565            0 :     catch(std::runtime_error& err)
     566              :     {
     567            0 :         PWARN("Error processing year-month-day %d-%d-%d: %s",
     568              :               year, month, day, err.what());
     569            0 :     }
     570           76 :     return strlen(buff);
     571              : }
     572              : 
     573              : size_t
     574         2535 : qof_print_date_buff (char * buff, const size_t len, time64 t)
     575              : {
     576         2535 :     if (!buff) return 0;
     577              : 
     578              :     try
     579              :     {
     580         2535 :         GncDateTime gncdt(t);
     581         2535 :         std::string str = gncdt.format(qof_date_format_get_string(dateFormat));
     582         2535 :         strncpy(buff, str.c_str(), len);
     583         2535 :         if (str.length() >= len)
     584            0 :             buff[len - 1] = '\0';
     585         2535 :     }
     586            0 :     catch(std::logic_error& err)
     587              :     {
     588            0 :         PWARN("Error processing time64 %" PRId64 ": %s", t, err.what());
     589            0 :     }
     590            0 :     catch(std::runtime_error& err)
     591              :     {
     592            0 :         PWARN("Error processing time64 %" PRId64 ": %s", t, err.what());
     593            0 :     }
     594         2535 :     return strlen(buff);
     595              : }
     596              : 
     597              : size_t
     598           55 : qof_print_gdate( char *buf, size_t len, const GDate *gd )
     599              : {
     600              :     GDate date;
     601           55 :     g_date_clear (&date, 1);
     602           55 :     date = *gd;
     603          220 :     return qof_print_date_dmy_buff( buf, len,
     604           55 :                                     g_date_get_day(&date),
     605           55 :                                     g_date_get_month(&date),
     606          165 :                                     g_date_get_year(&date) );
     607              : }
     608              : 
     609              : char *
     610         2224 : qof_print_date (time64 t)
     611              : {
     612              :     char buff[MAX_DATE_LENGTH + 1];
     613         2224 :     memset (buff, 0, sizeof (buff));
     614         2224 :     qof_print_date_buff (buff, MAX_DATE_LENGTH, t);
     615         4448 :     return g_strdup (buff);
     616              : }
     617              : 
     618              : static icu::DateIntervalFormat*
     619           67 : get_icu_date_interval_formatter ()
     620              : {
     621           67 :     static std::unique_ptr<icu::DateIntervalFormat> difmt;
     622           67 :     if (!difmt)
     623              :     {
     624            8 :         auto locale = icu::Locale::getDefault();
     625            8 :         UErrorCode status = U_ZERO_ERROR;
     626            8 :         difmt.reset(icu::DateIntervalFormat::createInstance(UDAT_YEAR_NUM_MONTH_DAY, locale, status));
     627            8 :         if (U_FAILURE(status))
     628              :         {
     629            0 :             PWARN ("icu::DateIntervalFormat::createInstance error %d", status);
     630            0 :             return nullptr;
     631              :         }
     632            8 :     }
     633           67 :     return difmt.get();
     634              : }
     635              : 
     636              : static gchar*
     637           67 : icu_date_interval_format (time64 from_date, time64 to_date)
     638              : {
     639           67 :     auto difmt = get_icu_date_interval_formatter();
     640           67 :     if (!difmt)
     641            0 :         return nullptr;
     642              : 
     643           67 :     if (from_date > to_date)
     644            0 :         std::swap(from_date, to_date);
     645              : 
     646           67 :     icu::DateInterval interval (from_date * 1000.0, to_date * 1000.0);
     647           67 :     icu::UnicodeString result;
     648           67 :     icu::FieldPosition fp;
     649           67 :     UErrorCode status = U_ZERO_ERROR;
     650           67 :     difmt->format (&interval, result, fp, status);
     651           67 :     if (U_FAILURE(status))
     652              :     {
     653            0 :         PWARN("Error formatting interval: %d", status);
     654            0 :         return nullptr;
     655              :     }
     656              : 
     657           67 :     std::string interval_string;
     658           67 :     result.toUTF8String(interval_string);
     659              : 
     660          134 :     return g_strdup (interval_string.c_str());
     661           67 : }
     662              : 
     663              : gchar*
     664          168 : gnc_date_interval_format (time64 from_date, time64 to_date)
     665              : {
     666          168 :     gchar* rv = nullptr;
     667              : 
     668          168 :     if (qof_date_format_get() == QOF_DATE_FORMAT_LOCALE)
     669           67 :         rv = icu_date_interval_format (from_date, to_date);
     670              : 
     671              :     // not using locale, or icu failure
     672          168 :     if (!rv)
     673              :     {
     674              :         gchar from_buff[MAX_DATE_LENGTH+1], to_buff[MAX_DATE_LENGTH+1];
     675          101 :         qof_print_date_buff (from_buff, MAX_DATE_LENGTH, from_date);
     676          101 :         qof_print_date_buff (to_buff, MAX_DATE_LENGTH, to_date);
     677          101 :         rv = g_strdup_printf (gettext("%s to %s"), from_buff, to_buff);
     678              :     }
     679              : 
     680          168 :     return rv;
     681              : }
     682              : 
     683              : /* ============================================================== */
     684              : 
     685              : /* return the greatest integer <= a/b; works for b > 0 and positive or
     686              :    negative a. */
     687              : static int
     688            0 : floordiv(int a, int b)
     689              : {
     690            0 :     if (a >= 0)
     691              :     {
     692            0 :         return a / b;
     693              :     }
     694              :     else
     695              :     {
     696            0 :         return - ((-a - 1) / b) - 1;
     697              :     }
     698              : }
     699              : 
     700              : /* Normalize the localized date format to avoid date scanning issues.
     701              :  *
     702              :  *   The 'O' and 'E' format modifiers are for localized input/output
     703              :  *   characters. Remove them as we are always using Arabic numbers.
     704              :  */
     705              : static inline std::string
     706            3 : normalize_format (const std::string& format)
     707              : {
     708            3 :     bool is_pct = false;
     709            3 :     std::string normalized;
     710            3 :     std::remove_copy_if(
     711              :         format.begin(), format.end(), back_inserter(normalized),
     712           24 :         [&is_pct](char e){
     713           24 :             bool r = (is_pct && (e == 'E' || e == 'O' || e == '-'));
     714           24 :             is_pct = e == '%';
     715           24 :             return r;
     716              :         });
     717            3 :     return normalized;
     718            0 : }
     719              : 
     720              : /* Convert a string into  day, month and year integers
     721              : 
     722              :     Convert a string into  day / month / year integers according to
     723              :     the current dateFormat value.
     724              : 
     725              :     This function will always parse a single number as the day of
     726              :     the month, regardless of the ordering of the dateFormat value.
     727              :     Two numbers will always be parsed as the day and the month, in
     728              :     the same order that they appear in the dateFormat value.  Three
     729              :     numbers are parsed exactly as specified in the dateFormat field.
     730              : 
     731              :     Fully formatted UTC timestamp strings are converted separately.
     732              : 
     733              :     param   buff - pointer to date string
     734              :     param     day -  will store day of the month as 1 ... 31
     735              :     param     month - will store month of the year as 1 ... 12
     736              :     param     year - will store the year (4-digit)
     737              : 
     738              :     return TRUE if date appeared to be valid.
     739              : 
     740              :     Globals: global dateFormat value
     741              : */
     742              : static gboolean
     743           18 : qof_scan_date_internal (const char *buff, int *day, int *month, int *year,
     744              :                         QofDateFormat which_format)
     745              : {
     746              :     char *dupe, *tmp, *first_field, *second_field, *third_field;
     747              :     int iday, imonth, iyear;
     748              :     int now_day, now_month, now_year;
     749              :     struct tm *now, utc;
     750              :     time64 secs;
     751              : 
     752           18 :     if (!buff) return(FALSE);
     753              : 
     754           17 :     if (which_format == QOF_DATE_FORMAT_UTC)
     755              :     {
     756            4 :         if (strptime(buff, QOF_UTC_DATE_FORMAT, &utc)
     757            4 :             || strptime (buff, "%Y-%m-%d", &utc))
     758              :         {
     759            3 :             *day = utc.tm_mday;
     760            3 :             *month = utc.tm_mon + 1;
     761            3 :             *year = utc.tm_year + 1900;
     762            3 :             return TRUE;
     763              :         }
     764              :         else
     765              :         {
     766            1 :             return FALSE;
     767              :         }
     768              :     }
     769           13 :     dupe = g_strdup (buff);
     770              : 
     771           13 :     tmp = dupe;
     772           13 :     first_field = nullptr;
     773           13 :     second_field = nullptr;
     774           13 :     third_field = nullptr;
     775              : 
     776              :     /* Use strtok to find delimiters */
     777           13 :     if (tmp)
     778              :     {
     779              :         static const char *delims = ".,-+/\\()년월年月 ";
     780              : 
     781           13 :         first_field = strtok (tmp, delims);
     782           13 :         if (first_field)
     783              :         {
     784           13 :             second_field = strtok (nullptr, delims);
     785           13 :             if (second_field)
     786              :             {
     787           13 :                 third_field = strtok (nullptr, delims);
     788              :             }
     789              :         }
     790              :     }
     791              : 
     792              :     /* today's date */
     793           13 :     gnc_time (&secs);
     794           13 :     now = gnc_localtime (&secs);
     795           13 :     if (!now)
     796            0 :         return FALSE;
     797           13 :     now_day = now->tm_mday;
     798           13 :     now_month = now->tm_mon + 1;
     799           13 :     now_year = now->tm_year + 1900;
     800           13 :     gnc_tm_free (now);
     801              : 
     802              :     /* set defaults: if day or month appear to be blank, use today's date */
     803           13 :     iday = now_day;
     804           13 :     imonth = now_month;
     805           13 :     iyear = -1;
     806              : 
     807              :     /* get numeric values */
     808           13 :     switch (which_format)
     809              :     {
     810            3 :     case QOF_DATE_FORMAT_LOCALE:
     811            3 :         if (buff[0] != '\0')
     812              :         {
     813              :             struct tm thetime;
     814              :             /* Parse time string. */
     815            3 :             memset(&thetime, -1, sizeof(struct tm));
     816            3 :             char *strv = strptime (buff, normalize_format(GNC_D_FMT).c_str(),
     817              :                                    &thetime);
     818              : 
     819            3 :             if (third_field)
     820              :             {
     821            3 :                 if (!strv) // Parse failed, continuing gives the wrong result.
     822            0 :                     return FALSE;
     823              : 
     824              :                 /* Easy.  All three values were parsed. */
     825            3 :                 iyear = thetime.tm_year + 1900;
     826            3 :                 iday = thetime.tm_mday;
     827            3 :                 imonth = thetime.tm_mon + 1;
     828              :             }
     829            0 :             else if (second_field)
     830              :             {
     831              :                 /* Hard. Two values parsed.  Figure out the ordering. */
     832            0 :                 if (thetime.tm_year == -1)
     833              :                 {
     834              :                     /* %m-%d or %d-%m. Don't care. Already parsed correctly. */
     835            0 :                     iday = thetime.tm_mday;
     836            0 :                     imonth = thetime.tm_mon + 1;
     837              :                 }
     838            0 :                 else if (thetime.tm_mon != -1)
     839              :                 {
     840              :                     /* Must be %Y-%m-%d. Reparse as %m-%d.*/
     841            0 :                     imonth = atoi(first_field);
     842            0 :                     iday = atoi(second_field);
     843              :                 }
     844              :                 else
     845              :                 {
     846              :                     /* Must be %Y-%d-%m. Reparse as %d-%m. */
     847            0 :                     iday = atoi(first_field);
     848            0 :                     imonth = atoi(second_field);
     849              :                 }
     850              :             }
     851            0 :             else if (first_field)
     852              :             {
     853            0 :                 iday = atoi(first_field);
     854              :             }
     855              :         }
     856            3 :         break;
     857            3 :     case QOF_DATE_FORMAT_UK:
     858              :     case QOF_DATE_FORMAT_CE:
     859            3 :         if (third_field)
     860              :         {
     861            3 :             iday = atoi(first_field);
     862            3 :             imonth = atoi(second_field);
     863            3 :             iyear = atoi(third_field);
     864              :         }
     865            0 :         else if (second_field)
     866              :         {
     867            0 :             iday = atoi(first_field);
     868            0 :             imonth = atoi(second_field);
     869              :         }
     870            0 :         else if (first_field)
     871              :         {
     872            0 :             iday = atoi(first_field);
     873              :         }
     874            3 :         break;
     875            0 :     case QOF_DATE_FORMAT_ISO:
     876            0 :         if (third_field)
     877              :         {
     878            0 :             iyear = atoi(first_field);
     879            0 :             imonth = atoi(second_field);
     880            0 :             iday = atoi(third_field);
     881              :         }
     882            0 :         else if (second_field)
     883              :         {
     884            0 :             imonth = atoi(first_field);
     885            0 :             iday = atoi(second_field);
     886              :         }
     887            0 :         else if (first_field)
     888              :         {
     889            0 :             iday = atoi(first_field);
     890              :         }
     891            0 :         break;
     892            7 :     case QOF_DATE_FORMAT_US:
     893              :     default:
     894            7 :         if (third_field)
     895              :         {
     896            5 :             imonth = atoi(first_field);
     897            5 :             iday = atoi(second_field);
     898            5 :             iyear = atoi(third_field);
     899              :         }
     900            2 :         else if (second_field)
     901              :         {
     902            2 :             imonth = atoi(first_field);
     903            2 :             iday = atoi(second_field);
     904              :         }
     905            0 :         else if (first_field)
     906              :         {
     907            0 :             iday = atoi(first_field);
     908              :         }
     909            7 :         break;
     910              :     }
     911              : 
     912           13 :     g_free (dupe);
     913              : 
     914           13 :     if ((imonth == 0) || (iday == 0))
     915            0 :         return FALSE;
     916              : 
     917           13 :     if ((12 < imonth) || (31 < iday))
     918              :     {
     919              :         /*
     920              :          * Ack! Thppfft!  Someone just fed this routine a string in the
     921              :          * wrong date format.  This is known to happen if a register
     922              :          * window is open when changing the date format.  Try the
     923              :          * previous date format.  If that doesn't work, see if we can
     924              :          * exchange month and day. If that still doesn't work,
     925              :          * bail and give the caller what they asked for (garbage)
     926              :          * parsed in the new format.
     927              :          *
     928              :          * Note: This test cannot detect any format change that only
     929              :          * swaps month and day field, if the day is 12 or less.  This is
     930              :          * deemed acceptable given the obscurity of this bug.
     931              :          */
     932            2 :         if ((which_format != prevQofDateFormat) &&
     933            1 :                 qof_scan_date_internal(buff, day, month, year, prevQofDateFormat))
     934              :         {
     935            0 :             return(TRUE);
     936              :         }
     937            1 :         if ((12 < imonth) && (12 >= iday))
     938              :         {
     939            1 :             int tmp = imonth;
     940            1 :             imonth = iday;
     941            1 :             iday = tmp;
     942            1 :         }
     943              :         else
     944              :         {
     945            0 :             return FALSE;
     946              :         }
     947              :     }
     948              : 
     949              :     /* if no year was entered, choose a year according to the
     950              :        dateCompletion preference. If it is
     951              :        QOF_DATE_COMPLETION_THISYEAR, use the current year, else if it
     952              :        is QOF_DATE_COMPLETION_SLIDING, use a sliding window that
     953              :        starts dateCompletionBackMonths before the current month.
     954              : 
     955              :        We go by whole months, rather than days, because presumably
     956              :        this is less confusing.
     957              :     */
     958              : 
     959           13 :     if (iyear == -1)
     960              :     {
     961            2 :         if (dateCompletion == QOF_DATE_COMPLETION_THISYEAR)
     962              :         {
     963            2 :             iyear = now_year;  /* use the current year */
     964              :         }
     965              :         else
     966              :         {
     967            0 :             iyear = now_year - floordiv(imonth - now_month +
     968              :                                         dateCompletionBackMonths, 12);
     969              :         }
     970              :     }
     971              : 
     972              :     /* If the year entered is smaller than 100, assume we mean the current
     973              :        century (and are not revising some roman emperor's books) */
     974           13 :     if (iyear < 100)
     975            0 :         iyear += ((int) ((now_year + 50 - iyear) / 100)) * 100;
     976              : 
     977              :     /* Fix up any goofy dates */
     978           13 :     struct tm tm{};
     979           13 :     tm.tm_year = iyear - 1900;
     980           13 :     tm.tm_mon = imonth - 1;
     981           13 :     tm.tm_mday = iday;
     982           13 :     normalize_struct_tm(&tm);
     983              : 
     984           13 :     if (year) *year = tm.tm_year + 1900;
     985           13 :     if (month) *month = tm.tm_mon + 1;
     986           13 :     if (day) *day = tm.tm_mday;
     987           13 :     return(TRUE);
     988              : }
     989              : 
     990              : gboolean
     991           17 : qof_scan_date (const char *buff, int *day, int *month, int *year)
     992              : {
     993           17 :     return qof_scan_date_internal(buff, day, month, year, dateFormat);
     994              : }
     995              : 
     996              : /* Return the field separator for the current date format
     997              : return date character
     998              : */
     999            0 : char dateSeparator (void)
    1000              : {
    1001              :     static char locale_separator = '\0';
    1002              : 
    1003            0 :     switch (dateFormat)
    1004              :     {
    1005            0 :     case QOF_DATE_FORMAT_CE:
    1006            0 :         return '.';
    1007            0 :     case QOF_DATE_FORMAT_ISO:
    1008              :     case QOF_DATE_FORMAT_UTC:
    1009            0 :         return '-';
    1010            0 :     case QOF_DATE_FORMAT_US:
    1011              :     case QOF_DATE_FORMAT_UK:
    1012              :     default:
    1013            0 :         return '/';
    1014            0 :     case QOF_DATE_FORMAT_LOCALE:
    1015            0 :         if (locale_separator != '\0')
    1016            0 :             return locale_separator;
    1017              :         else
    1018              :         {
    1019              :             /* Make a guess */
    1020              :             gchar string[256];
    1021              :             struct tm tm;
    1022              :             time64 secs;
    1023              :             gchar *s;
    1024              : 
    1025            0 :             secs = gnc_time (nullptr);
    1026            0 :             gnc_localtime_r(&secs, &tm);
    1027              :             auto normalized_fmt =
    1028            0 :                 normalize_format(qof_date_format_get_string(dateFormat));
    1029            0 :             qof_strftime(string, sizeof(string), normalized_fmt.c_str(), &tm);
    1030              : 
    1031            0 :             for (s = string; *s != '\0'; s++)
    1032            0 :                 if (!isdigit(*s))
    1033            0 :                     return (locale_separator = *s);
    1034            0 :         }
    1035            0 :         break;
    1036              :     }
    1037            0 :     return '\0';
    1038              : }
    1039              : 
    1040              : /* The following functions have Win32 forms in qof-win32.c */
    1041              : #ifndef G_OS_WIN32
    1042              : gchar *
    1043         1779 : qof_time_format_from_utf8(const gchar *utf8_format)
    1044              : {
    1045              :     gchar *retval;
    1046         1779 :     GError *error = nullptr;
    1047              : 
    1048         1779 :     retval = g_locale_from_utf8(utf8_format, -1, nullptr, nullptr, &error);
    1049              : 
    1050         1779 :     if (!retval)
    1051              :     {
    1052            0 :         g_warning("Could not convert format '%s' from UTF-8: %s", utf8_format,
    1053              :                   error->message);
    1054            0 :         g_error_free(error);
    1055              :     }
    1056         1779 :     return retval;
    1057              : }
    1058              : 
    1059              : gchar *
    1060         1779 : qof_formatted_time_to_utf8(const gchar *locale_string)
    1061              : {
    1062              :     gchar *retval;
    1063         1779 :     GError *error = nullptr;
    1064              : 
    1065         1779 :     retval = g_locale_to_utf8(locale_string, -1, nullptr, nullptr, &error);
    1066              : 
    1067         1779 :     if (!retval)
    1068              :     {
    1069            0 :         g_warning("Could not convert '%s' to UTF-8: %s", locale_string,
    1070              :                   error->message);
    1071            0 :         g_error_free(error);
    1072              :     }
    1073         1779 :     return retval;
    1074              : }
    1075              : #endif /* G_OS_WIN32 */
    1076              : 
    1077              : static gchar *
    1078         1779 : qof_format_time(const gchar *format, const struct tm *tm)
    1079              : {
    1080              :     gchar *locale_format, *tmpbuf, *retval;
    1081              :     gsize tmplen, tmpbufsize;
    1082              : 
    1083         1779 :     g_return_val_if_fail(format, 0);
    1084         1779 :     g_return_val_if_fail(tm, 0);
    1085              : 
    1086         1779 :     locale_format = qof_time_format_from_utf8(format);
    1087         1779 :     if (!locale_format)
    1088            0 :         return nullptr;
    1089              : 
    1090         1779 :     tmpbufsize = MAX(128, strlen(locale_format) * 2);
    1091              :     while (TRUE)
    1092              :     {
    1093            0 :         tmpbuf = static_cast<gchar*>(g_malloc(tmpbufsize));
    1094              : 
    1095              :         /* Set the first byte to something other than '\0', to be able to
    1096              :          * recognize whether strftime actually failed or just returned "".
    1097              :          */
    1098         1779 :         tmpbuf[0] = '\1';
    1099         1779 :         tmplen = strftime(tmpbuf, tmpbufsize, locale_format, tm);
    1100              : 
    1101         1779 :         if (tmplen == 0 && tmpbuf[0] != '\0')
    1102              :         {
    1103            0 :             g_free(tmpbuf);
    1104            0 :             tmpbufsize *= 2;
    1105              : 
    1106            0 :             if (tmpbufsize > 65536)
    1107              :             {
    1108            0 :                 g_warning("Maximum buffer size for qof_format_time "
    1109              :                           "exceeded: giving up");
    1110            0 :                 g_free(locale_format);
    1111              : 
    1112            0 :                 return nullptr;
    1113              :             }
    1114              :         }
    1115              :         else
    1116              :         {
    1117              :             break;
    1118              :         }
    1119              :     }
    1120         1779 :     g_free(locale_format);
    1121              : 
    1122         1779 :     retval = qof_formatted_time_to_utf8(tmpbuf);
    1123         1779 :     g_free(tmpbuf);
    1124              : 
    1125         1779 :     return retval;
    1126              : }
    1127              : 
    1128              : gsize
    1129         1779 : qof_strftime(gchar *buf, gsize max, const gchar *format, const struct tm *tm)
    1130              : {
    1131              :     gsize convlen, retval;
    1132              :     gchar *convbuf;
    1133              : 
    1134         1779 :     g_return_val_if_fail(buf, 0);
    1135         1779 :     g_return_val_if_fail(max > 0, 0);
    1136         1779 :     g_return_val_if_fail(format, 0);
    1137         1779 :     g_return_val_if_fail(tm, 0);
    1138              : 
    1139         1779 :     convbuf = qof_format_time(format, tm);
    1140         1779 :     if (!convbuf)
    1141              :     {
    1142            0 :         buf[0] = '\0';
    1143            0 :         return 0;
    1144              :     }
    1145              : 
    1146         1779 :     convlen = strlen(convbuf);
    1147              : 
    1148         1779 :     if (max <= convlen)
    1149              :     {
    1150              :         /* Ensure only whole characters are copied into the buffer. */
    1151            0 :         gchar *end = g_utf8_find_prev_char(convbuf, convbuf + max);
    1152            0 :         g_assert(end != nullptr);
    1153            0 :         convlen = end - convbuf;
    1154              : 
    1155              :         /* Return 0 because the buffer isn't large enough. */
    1156            0 :         retval = 0;
    1157              :     }
    1158              :     else
    1159              :     {
    1160         1779 :         retval = convlen;
    1161              :     }
    1162              : 
    1163         1779 :     memcpy(buf, convbuf, convlen);
    1164         1779 :     buf[convlen] = '\0';
    1165         1779 :     g_free(convbuf);
    1166              : 
    1167         1779 :     return retval;
    1168              : }
    1169              : 
    1170              : /********************************************************************\
    1171              : \********************************************************************/
    1172              : 
    1173              : gchar *
    1174           50 : gnc_date_timestamp (void)
    1175              : {
    1176           50 :     auto timestamp = GncDateTime::timestamp();
    1177          150 :     return g_strdup(timestamp.c_str());
    1178           50 : }
    1179              : 
    1180              : /********************************************************************\
    1181              :  * iso 8601 datetimes should look like 1998-07-02 11:00:00.68-05
    1182              : \********************************************************************/
    1183              : /* Unfortunately, not all strptime or struct tm implementations
    1184              :  * support timezones, so we have to do this with sscanf.
    1185              :  */
    1186              : 
    1187              : #define ISO_DATE_FORMAT "%d-%d-%d %d:%d:%lf%s"
    1188              : time64
    1189         2745 : gnc_iso8601_to_time64_gmt(const char *cstr)
    1190              : {
    1191         2745 :     if (!cstr) return INT64_MAX;
    1192              :     try
    1193              :     {
    1194         2744 :         GncDateTime gncdt(cstr);
    1195         2744 :         return static_cast<time64>(gncdt);
    1196         2744 :     }
    1197            0 :     catch(std::logic_error& err)
    1198              :     {
    1199            0 :         PWARN("Error processing %s: %s", cstr, err.what());
    1200            0 :         return INT64_MAX;
    1201            0 :     }
    1202            0 :     catch(std::runtime_error& err)
    1203              :     {
    1204            0 :         PWARN("Error processing time64 %s: %s", cstr, err.what());
    1205            0 :         return INT64_MAX;
    1206            0 :     }
    1207              : }
    1208              : 
    1209              : /********************************************************************\
    1210              : \********************************************************************/
    1211              : 
    1212              : char *
    1213        19891 : gnc_time64_to_iso8601_buff (time64 time, char * buff)
    1214              : {
    1215        19891 :     if (!buff) return nullptr;
    1216              :     try
    1217              :     {
    1218        19890 :         GncDateTime gncdt(time);
    1219        19890 :         auto sstr = gncdt.format_iso8601();
    1220              : 
    1221        19890 :         memset(buff, 0, sstr.length() + 1);
    1222        19890 :         strncpy(buff, sstr.c_str(), sstr.length());
    1223        19890 :         return buff + sstr.length();
    1224        19890 :     }
    1225            0 :     catch(std::logic_error& err)
    1226              :     {
    1227            0 :         PWARN("Error processing time64 %" PRId64 ": %s", time, err.what());
    1228            0 :         return buff;
    1229            0 :     }
    1230            0 :     catch(std::runtime_error& err)
    1231              :     {
    1232            0 :         PWARN("Error processing time64 %" PRId64 ": %s", time, err.what());
    1233            0 :         return buff;
    1234            0 :     }
    1235              : }
    1236              : 
    1237              : #define THIRTY_TWO_YEARS 0x3c30fc00LL
    1238              : 
    1239              : static time64
    1240         6065 : gnc_dmy2time64_internal (int day, int month, int year, DayPart day_part)
    1241              : {
    1242              :     try
    1243              :     {
    1244         6065 :         auto date = GncDate(year, month, day);
    1245         6048 :         return static_cast<time64>(GncDateTime (date, day_part));
    1246         6048 :     }
    1247           17 :     catch(const std::logic_error& err)
    1248              :     {
    1249           17 :         PWARN("Date computation error from Y-M-D %d-%d-%d: %s",
    1250              :               year, month, day, err.what());
    1251           17 :         return INT64_MAX;
    1252           17 :     }
    1253            0 :     catch(const std::runtime_error& err)
    1254              :     {
    1255            0 :         PWARN("Date computation error from Y-M-D %d-%d-%d: %s",
    1256              :               year, month, day, err.what());
    1257            0 :         return INT64_MAX;
    1258            0 :     }
    1259              : }
    1260              : 
    1261              : time64
    1262         2848 : gnc_dmy2time64 (int day, int month, int year)
    1263              : {
    1264         2848 :     return gnc_dmy2time64_internal (day, month, year, DayPart::start);
    1265              : }
    1266              : 
    1267              : time64
    1268          860 : gnc_dmy2time64_end (int day, int month, int year)
    1269              : {
    1270          860 :     return gnc_dmy2time64_internal (day, month, year, DayPart::end);
    1271              : }
    1272              : 
    1273              : time64
    1274         2357 : gnc_dmy2time64_neutral (int day, int month, int year)
    1275              : {
    1276         2357 :     return gnc_dmy2time64_internal (day, month, year, DayPart::neutral);
    1277              : }
    1278              : 
    1279              : 
    1280              : /* The GDate setter functions all in the end use g_date_set_time_t,
    1281              :  * which in turn relies on localtime and is therefore subject to the
    1282              :  * 2038 bug.
    1283              :  */
    1284          207 : GDate time64_to_gdate (time64 t)
    1285              : {
    1286              :     GDate result;
    1287              : 
    1288          207 :     g_date_clear (&result, 1);
    1289          207 :     GncDateTime time(t);
    1290          207 :     auto date = time.date().year_month_day();
    1291          207 :     g_date_set_dmy (&result, date.day, static_cast<GDateMonth>(date.month),
    1292          207 :                     date.year);
    1293          207 :     g_assert(g_date_valid (&result));
    1294              : 
    1295          207 :     return result;
    1296          207 : }
    1297              : 
    1298           17 : GDate* gnc_g_date_new_today ()
    1299              : {
    1300           17 :     GDate* rv = g_date_new ();
    1301           17 :     gnc_gdate_set_today (rv);
    1302           17 :     return rv;
    1303              : }
    1304              : 
    1305              : void
    1306          287 : gnc_gdate_set_today (GDate* gd)
    1307              : {
    1308          287 :     g_return_if_fail (gd != nullptr);
    1309          287 :     auto ymd = GncDate().year_month_day();
    1310          287 :     g_date_set_dmy (gd, ymd.day, static_cast<GDateMonth>(ymd.month), ymd.year);
    1311              : }
    1312              : 
    1313              : void
    1314          217 : gnc_gdate_set_time64 (GDate* gd, time64 time)
    1315              : {
    1316              :     struct tm tm;
    1317          217 :     gnc_localtime_r(&time, &tm);
    1318          217 :     g_date_set_dmy (gd, tm.tm_mday,
    1319          217 :                     static_cast<GDateMonth>(tm.tm_mon + 1),
    1320          217 :                     tm.tm_year + 1900);
    1321          217 : }
    1322              : 
    1323         2313 : time64 gdate_to_time64 (GDate d)
    1324              : {
    1325         2313 :     return gnc_dmy2time64_neutral (g_date_get_day(&d),
    1326         2313 :                                      g_date_get_month(&d),
    1327         4626 :                                      g_date_get_year(&d));
    1328              : }
    1329              : 
    1330              : static void
    1331           30 : gnc_tm_get_day_start (struct tm *tm, time64 time_val)
    1332              : {
    1333              :     /* Get the equivalent time structure */
    1334           30 :     if (!gnc_localtime_r(&time_val, tm))
    1335            0 :         return;
    1336           30 :     gnc_tm_set_day_start(tm);
    1337              : }
    1338              : 
    1339              : void
    1340            0 : gnc_tm_set_day_neutral (struct tm *tm)
    1341              : {
    1342            0 :     auto time_val{gnc_dmy2time64_internal(tm->tm_mday, tm->tm_mon + 1,
    1343            0 :                                           tm->tm_year + 1900, DayPart::neutral)};
    1344            0 :     gnc_localtime_r(&time_val, tm);
    1345            0 : }
    1346              : 
    1347              : static void
    1348            0 : gnc_tm_get_day_neutral (struct tm *tm, time64 time_val)
    1349              : {
    1350              :     /* Get the equivalent time structure */
    1351            0 :     if (!gnc_localtime_r(&time_val, tm))
    1352            0 :         return;
    1353            0 :     gnc_tm_set_day_neutral(tm);
    1354              : }
    1355              : 
    1356              : static void
    1357           38 : gnc_tm_get_day_end (struct tm *tm, time64 time_val)
    1358              : {
    1359              :     /* Get the equivalent time structure */
    1360           38 :     if (!gnc_localtime_r(&time_val, tm))
    1361            0 :         return;
    1362           38 :     gnc_tm_set_day_end(tm);
    1363              : }
    1364              : 
    1365              : time64
    1366           30 : gnc_time64_get_day_start (time64 time_val)
    1367              : {
    1368              :     struct tm tm;
    1369              :     time64 new_time;
    1370              : 
    1371           30 :     gnc_tm_get_day_start(&tm, time_val);
    1372           30 :     new_time = gnc_mktime(&tm);
    1373           30 :     return new_time;
    1374              : }
    1375              : 
    1376              : time64
    1377            0 : gnc_time64_get_day_neutral (time64 time_val)
    1378              : {
    1379              :     struct tm tm;
    1380            0 :     gnc_localtime_r(&time_val, &tm);
    1381            0 :     return gnc_dmy2time64_internal(tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900,
    1382            0 :                                    DayPart::neutral);
    1383              : }
    1384              : 
    1385              : time64
    1386           35 : gnc_time64_get_day_end (time64 time_val)
    1387              : {
    1388              :     struct tm tm;
    1389              :     time64 new_time;
    1390              : 
    1391           35 :     gnc_tm_get_day_end(&tm, time_val);
    1392           35 :     new_time = gnc_mktime(&tm);
    1393           35 :     return new_time;
    1394              : }
    1395              : 
    1396              : /* ======================================================== */
    1397              : 
    1398              : void
    1399            0 : gnc_tm_get_today_start (struct tm *tm)
    1400              : {
    1401            0 :     gnc_tm_get_day_start(tm, gnc_time(nullptr));
    1402            0 : }
    1403              : 
    1404              : void
    1405            0 : gnc_tm_get_today_neutral (struct tm *tm)
    1406              : {
    1407            0 :     gnc_tm_get_day_neutral(tm, gnc_time(nullptr));
    1408            0 : }
    1409              : 
    1410              : void
    1411            0 : gnc_tm_get_today_end (struct tm *tm)
    1412              : {
    1413            0 :     gnc_tm_get_day_end(tm, gnc_time(nullptr));
    1414            0 : }
    1415              : 
    1416              : time64
    1417            0 : gnc_time64_get_today_start (void)
    1418              : {
    1419              :     struct tm tm;
    1420              : 
    1421            0 :     gnc_tm_get_day_start(&tm, gnc_time(nullptr));
    1422            0 :     return gnc_mktime(&tm);
    1423              : }
    1424              : 
    1425              : time64
    1426            3 : gnc_time64_get_today_end (void)
    1427              : {
    1428              :     struct tm tm;
    1429              : 
    1430            3 :     gnc_tm_get_day_end(&tm, gnc_time(nullptr));
    1431            6 :     return gnc_mktime(&tm);
    1432              : }
    1433              : 
    1434              : void
    1435            0 : gnc_dow_abbrev(gchar *buf, int buf_len, int dow)
    1436              : {
    1437              :     struct tm my_tm;
    1438              :     int i;
    1439              : 
    1440            0 :     memset(buf, 0, buf_len);
    1441            0 :     memset(&my_tm, 0, sizeof(struct tm));
    1442            0 :     my_tm.tm_wday = dow;
    1443            0 :     i = qof_strftime(buf, buf_len, "%a", &my_tm);
    1444            0 :     buf[i] = 0;
    1445            0 : }
    1446              : 
    1447              : /* *******************************************************************
    1448              :  *  GValue handling
    1449              :  ********************************************************************/
    1450              : 
    1451              : static Time64*
    1452          100 : time64_boxed_copy_func (Time64 *in_time64)
    1453              : {
    1454          100 :     Time64* newvalue = g_new (Time64, 1);
    1455          100 :     *newvalue = *in_time64;
    1456              : 
    1457          100 :     return newvalue;
    1458              : }
    1459              : 
    1460              : static void
    1461           50 : time64_boxed_free_func (Time64 *in_time64)
    1462              : {
    1463           50 :     g_free (in_time64);
    1464           50 : }
    1465              : 
    1466          274 : G_DEFINE_BOXED_TYPE (Time64, time64, time64_boxed_copy_func, time64_boxed_free_func)
    1467              : 
    1468              : /* ================================================= */
    1469              : 
    1470              : gboolean
    1471            0 : gnc_gdate_equal(gconstpointer gda, gconstpointer gdb)
    1472              : {
    1473            0 :     return (g_date_compare( (GDate*)gda, (GDate*)gdb ) == 0 ? TRUE : FALSE);
    1474              : }
    1475              : 
    1476              : guint
    1477            0 : gnc_gdate_hash( gconstpointer gd )
    1478              : {
    1479            0 :     gint val = (g_date_get_year( (GDate*)gd ) * 10000)
    1480            0 :                + (g_date_get_month( (GDate*)gd ) * 100)
    1481            0 :                + g_date_get_day( (GDate*)gd );
    1482            0 :     return g_int_hash( &val );
    1483              : }
    1484              : 
    1485              : /* ================================================= */
    1486              : 
    1487              : time64
    1488          202 : gnc_time64_get_day_start_gdate (const GDate *date)
    1489              : {
    1490              :     struct tm stm;
    1491              :     time64 secs;
    1492              : 
    1493              :     /* First convert to a 'struct tm' */
    1494          202 :     g_date_to_struct_tm (date, &stm);
    1495              : 
    1496              :     /* Then convert to number of seconds */
    1497          202 :     secs = gnc_mktime (&stm);
    1498          202 :     return secs;
    1499              : }
    1500              : 
    1501              : time64
    1502           41 : gnc_time64_get_day_end_gdate (const GDate *date)
    1503              : {
    1504              :     struct tm stm;
    1505              :     time64 secs;
    1506              : 
    1507              :     /* First convert to a 'struct tm' */
    1508           41 :     g_date_to_struct_tm(date, &stm);
    1509              : 
    1510              :     /* Force to th last second of the day */
    1511           41 :     stm.tm_hour = 23;
    1512           41 :     stm.tm_min = 59;
    1513           41 :     stm.tm_sec = 59;
    1514           41 :     stm.tm_isdst = -1;
    1515              : 
    1516              :     /* Then convert to number of seconds */
    1517           41 :     secs = gnc_mktime (&stm);
    1518           41 :     return secs;
    1519              : }
    1520              : 
    1521              : /* ================================================= */
    1522              : 
    1523              : void
    1524            3 : gnc_gdate_set_month_start (GDate *date)
    1525              : {
    1526            3 :     g_date_set_day(date, 1);
    1527            3 : }
    1528              : 
    1529              : /** Convert a GDate to the last day of the month.  This routine has no
    1530              :  *  knowledge of how many days are in a month, whether its a leap
    1531              :  *  year, etc.  All that information is contained in the glib date
    1532              :  *  functions.
    1533              :  *
    1534              :  *  @param date The GDate to modify.
    1535              :  */
    1536              : void
    1537            2 : gnc_gdate_set_month_end (GDate *date)
    1538              : {
    1539              :     /* First set the start of next month. */
    1540            2 :     g_date_set_day(date, 1);
    1541            2 :     g_date_add_months(date, 1);
    1542              : 
    1543              :     /* Then back up one day */
    1544            2 :     g_date_subtract_days(date, 1);
    1545            2 : }
    1546              : 
    1547              : /** Convert a GDate to the first day of the prebvious month.  This
    1548              :  *  routine has no knowledge of how many days are in a month, whether
    1549              :  *  its a leap year, etc.  All that information is contained in the
    1550              :  *  glib date functions.
    1551              :  *
    1552              :  *  @param date The GDate to modify.
    1553              :  */
    1554              : void
    1555            2 : gnc_gdate_set_prev_month_start (GDate *date)
    1556              : {
    1557            2 :     g_date_set_day(date, 1);
    1558            2 :     g_date_subtract_months(date, 1);
    1559            2 : }
    1560              : 
    1561              : /** Convert a GDate to the last day of the prebvious month.  This
    1562              :  *  routine has no knowledge of how many days are in a month, whether
    1563              :  *  its a leap year, etc.  All that information is contained in the
    1564              :  *  glib date functions.
    1565              :  *
    1566              :  *  @param date The GDate to modify.
    1567              :  */
    1568              : void
    1569            2 : gnc_gdate_set_prev_month_end (GDate *date)
    1570              : {
    1571              :     /* This will correctly handle the varying month lengths */
    1572            2 :     g_date_set_day(date, 1);
    1573            2 :     g_date_subtract_days(date, 1);
    1574            2 : }
    1575              : 
    1576              : /* ================================================= */
    1577              : 
    1578              : void
    1579            4 : gnc_gdate_set_quarter_start (GDate *date)
    1580              : {
    1581              :     gint months;
    1582              : 
    1583              :     /* Set the date to the first day of the specified month. */
    1584            4 :     g_date_set_day(date, 1);
    1585              : 
    1586              :     /* Back up 0-2 months */
    1587            4 :     months = (g_date_get_month(date) - G_DATE_JANUARY) % 3;
    1588            4 :     g_date_subtract_months(date, months);
    1589            4 : }
    1590              : 
    1591              : void
    1592            4 : gnc_gdate_set_quarter_end (GDate *date)
    1593              : {
    1594            4 :      const GDateMonth months[] = {G_DATE_MARCH, G_DATE_JUNE,
    1595              :                                   G_DATE_SEPTEMBER, G_DATE_DECEMBER};
    1596            4 :      const GDateDay days[] = {31, 30, 30, 31};
    1597            4 :      int quarter = (g_date_get_month (date) - 1) / 3;
    1598              : 
    1599            4 :      g_date_set_month (date, months[quarter]);
    1600            4 :      g_date_set_day (date, days[quarter]);
    1601            4 : }
    1602              : 
    1603              : void
    1604            2 : gnc_gdate_set_prev_quarter_start (GDate *date)
    1605              : {
    1606            2 :     g_date_subtract_months(date, 3);
    1607            2 :     gnc_gdate_set_quarter_start(date);
    1608            2 : }
    1609              : 
    1610              : void
    1611            2 : gnc_gdate_set_prev_quarter_end (GDate *date)
    1612              : {
    1613            2 :     g_date_subtract_months(date, 3);
    1614            2 :     gnc_gdate_set_quarter_end(date);
    1615            2 : }
    1616              : 
    1617              : /* ================================================= */
    1618              : 
    1619              : void
    1620           31 : gnc_gdate_set_year_start (GDate *date)
    1621              : {
    1622           31 :     g_date_set_month(date, G_DATE_JANUARY);
    1623           31 :     g_date_set_day(date, 1);
    1624           31 : }
    1625              : 
    1626              : void
    1627            4 : gnc_gdate_set_year_end (GDate *date)
    1628              : {
    1629            4 :     g_date_set_month(date, G_DATE_DECEMBER);
    1630            4 :     g_date_set_day(date, 31);
    1631            4 : }
    1632              : 
    1633              : void
    1634            5 : gnc_gdate_set_prev_year_start (GDate *date)
    1635              : {
    1636            5 :     gnc_gdate_set_year_start(date);
    1637            5 :     g_date_subtract_years(date, 1);
    1638            5 : }
    1639              : 
    1640              : void
    1641            2 : gnc_gdate_set_prev_year_end (GDate *date)
    1642              : {
    1643            2 :     gnc_gdate_set_year_end(date);
    1644            2 :     g_date_subtract_years(date, 1);
    1645            2 : }
    1646              : 
    1647              : /* ================================================= */
    1648              : 
    1649              : void
    1650            0 : gnc_gdate_set_fiscal_year_start (GDate *date,
    1651              :                                  const GDate *fy_end)
    1652              : {
    1653              :     GDate temp;
    1654              :     gboolean new_fy;
    1655              : 
    1656            0 :     g_return_if_fail(date);
    1657            0 :     g_return_if_fail(fy_end);
    1658              : 
    1659              :     /* Compute the FY end that occurred this CY */
    1660            0 :     temp = *fy_end;
    1661            0 :     g_date_set_year(&temp, g_date_get_year(date));
    1662              : 
    1663              :     /* Has it already passed? */
    1664            0 :     new_fy = (g_date_compare(date, &temp) > 0);
    1665              : 
    1666              :     /* Set start date */
    1667            0 :     *date = temp;
    1668            0 :     g_date_add_days(date, 1);
    1669            0 :     if (!new_fy)
    1670            0 :         g_date_subtract_years(date, 1);
    1671              : }
    1672              : 
    1673              : void
    1674            6 : gnc_gdate_set_fiscal_year_end (GDate *date,
    1675              :                                const GDate *fy_end)
    1676              : {
    1677              :     GDate temp;
    1678              :     gboolean new_fy;
    1679              : 
    1680            6 :     g_return_if_fail(date);
    1681            6 :     g_return_if_fail(fy_end);
    1682              : 
    1683              :     /* Compute the FY end that occurred this CY */
    1684            6 :     temp = *fy_end;
    1685            6 :     g_date_set_year(&temp, g_date_get_year(date));
    1686              : 
    1687              :     /* Has it already passed? */
    1688            6 :     new_fy = (g_date_compare(date, &temp) > 0);
    1689              : 
    1690              :     /* Set end date */
    1691            6 :     *date = temp;
    1692            6 :     if (new_fy)
    1693            0 :         g_date_add_years(date, 1);
    1694              : }
    1695              : 
    1696              : void
    1697            0 : gnc_gdate_set_prev_fiscal_year_start (GDate *date,
    1698              :                                       const GDate *fy_end)
    1699              : {
    1700            0 :     g_return_if_fail(date);
    1701            0 :     g_return_if_fail(fy_end);
    1702              : 
    1703            0 :     gnc_gdate_set_fiscal_year_start(date, fy_end);
    1704            0 :     g_date_subtract_years(date, 1);
    1705              : }
    1706              : 
    1707              : void
    1708            0 : gnc_gdate_set_prev_fiscal_year_end (GDate *date,
    1709              :                                     const GDate *fy_end)
    1710              : {
    1711            0 :     g_return_if_fail(date);
    1712            0 :     g_return_if_fail(fy_end);
    1713              : 
    1714            0 :     gnc_gdate_set_fiscal_year_end(date, fy_end);
    1715            0 :     g_date_subtract_years(date, 1);
    1716              : }
    1717              : 
    1718              : Testfuncs*
    1719            0 : gnc_date_load_funcs (void)
    1720              : {
    1721            0 :     Testfuncs *tf = g_slice_new (Testfuncs);
    1722            0 :     return tf;
    1723              : }
        

Generated by: LCOV version 2.0-1