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