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