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