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