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