1 /********************************************************************
2 * qofdate.c - QofDate, 64bit UTC date handling.
3 * Rewritten from scratch for QOF 0.7.0
4 *
5 * Fri May 5 15:05:24 2006
6 * Copyright (C) 1991, 1993, 1997, 1998, 2002, 2006
7 * Free Software Foundation, Inc.
8 * This file contains routines modified from the GNU C Library.
9 ********************************************************************/
10 /*
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA
24 */
25
26 #include "config.h"
27 #include <glib.h>
28 #include <glib/gprintf.h>
29 #include <stdlib.h>
30 #include <time.h>
31 #include "qof.h"
32 #include "qofdate-p.h"
33
34 /* from gnu libc */
35 #define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
36 #define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))
37
38 static GHashTable *DateFormatTable = NULL;
39 static gboolean QofDateInit = FALSE;
40 static QofLogModule log_module = QOF_MOD_DATE;
41 static gchar locale_separator = '\0';
42 static QofDateFormat dateFormat = QOF_DATE_FORMAT_LOCALE;
43
44 /* copied from glib */
45 static const guint16 days_in_year[2][14] =
46 { /* 0, jan feb mar apr may jun jul aug sep oct nov dec */
47 { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
48 { 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
49 };
50 static const guint8 days_in_months[2][13] =
51 { /* error, jan feb mar apr may jun jul aug sep oct nov dec */
52 { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
53 { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } /* leap year */
54 };
55
56 /* A single Date Format Entry. */
57 typedef struct QofDateEntry_s
58 {
59 const gchar *format;
60 const gchar *name;
61 gchar separator;
62 QofDateFormat df;
63 gboolean locale_specific;
64 } QofDateEntry;
65
66 void
qof_date_init(void)67 qof_date_init (void)
68 {
69 if (!QofDateInit)
70 {
71 DateFormatTable = g_hash_table_new (g_direct_hash, g_direct_equal);
72 }
73 {
74 QofDateEntry *d = g_new0 (QofDateEntry, 1);
75 d->format = "%m/%d/%Y";
76 d->name = "us";
77 d->separator = '/';
78 d->df = QOF_DATE_FORMAT_US;
79 d->locale_specific = FALSE;
80 g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
81 }
82 {
83 QofDateEntry *d = g_new0 (QofDateEntry, 1);
84 d->format = "%d/%m/%Y";
85 d->name = "uk";
86 d->separator = '/';
87 d->df = QOF_DATE_FORMAT_UK;
88 d->locale_specific = FALSE;
89 g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
90 }
91 {
92 QofDateEntry *d = g_new0 (QofDateEntry, 1);
93 d->format = "%d.%m.%Y";
94 d->name = "ce";
95 d->separator = '.';
96 d->df = QOF_DATE_FORMAT_CE;
97 d->locale_specific = FALSE;
98 g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
99 }
100 {
101 QofDateEntry *d = g_new0 (QofDateEntry, 1);
102 d->format = "%F";
103 d->name = "iso";
104 d->separator = '-';
105 d->df = QOF_DATE_FORMAT_ISO;
106 d->locale_specific = FALSE;
107 g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
108 }
109 {
110 QofDateEntry *d = g_new0 (QofDateEntry, 1);
111 d->format = QOF_UTC_DATE_FORMAT;
112 d->name = "utc";
113 d->separator = '-';
114 d->df = QOF_DATE_FORMAT_UTC;
115 d->locale_specific = FALSE;
116 g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
117 }
118 {
119 QofDateEntry *d = g_new0 (QofDateEntry, 1);
120 d->format = "%x";
121 d->name = "locale";
122 d->separator = locale_separator;
123 d->df = QOF_DATE_FORMAT_LOCALE;
124 d->locale_specific = TRUE;
125 g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
126 }
127 {
128 QofDateEntry *d = g_new0 (QofDateEntry, 1);
129 d->format = "%c";
130 d->name = "custom";
131 d->separator = locale_separator;
132 d->df = QOF_DATE_FORMAT_CUSTOM;
133 d->locale_specific = TRUE;
134 g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
135 }
136 {
137 QofDateEntry *d = g_new0(QofDateEntry,1);
138 d->format = "%Y-%m-%d %H:%M:%S.%N %z";
139 d->name = "iso8601";
140 d->separator = '-';
141 d->df = QOF_DATE_FORMAT_ISO8601;
142 d->locale_specific = FALSE;
143 g_hash_table_insert (DateFormatTable, GINT_TO_POINTER(d->df), d);
144 }
145 QofDateInit = TRUE;
146 }
147
148 static void
hash_value_free(gpointer key,gpointer value,gpointer data)149 hash_value_free (gpointer key __attribute__ ((unused)), gpointer value,
150 gpointer data __attribute__ ((unused)))
151 {
152 g_free (value);
153 }
154
155 void
qof_date_close(void)156 qof_date_close (void)
157 {
158 if (QofDateInit)
159 {
160 g_hash_table_foreach (DateFormatTable, hash_value_free, NULL);
161 g_hash_table_destroy (DateFormatTable);
162 }
163 QofDateInit = FALSE;
164 }
165
166 guint16
qof_date_get_yday(gint mday,gint month,gint64 year)167 qof_date_get_yday (gint mday, gint month, gint64 year)
168 {
169 guint8 leap;
170
171 g_return_val_if_fail (mday != 0, 0);
172 g_return_val_if_fail (month != 0, 0);
173 g_return_val_if_fail (month <= 12, 0);
174 g_return_val_if_fail (month >= 1, 0);
175 g_return_val_if_fail (year != 0, 0);
176 leap = qof_date_isleap (year);
177 g_return_val_if_fail (mday <=
178 qof_date_get_mday (month, year), 0);
179 return days_in_year[leap][month] + mday;
180 }
181
182 guint8
qof_date_get_mday(gint month,gint64 year)183 qof_date_get_mday (gint month, gint64 year)
184 {
185 g_return_val_if_fail (month != 0, 0);
186 g_return_val_if_fail (month <= 12, 0);
187 g_return_val_if_fail (month >= 1, 0);
188 g_return_val_if_fail (year != 0, 0);
189 return days_in_months[qof_date_isleap (year)][month];
190 }
191
192 gboolean
qof_date_is_last_mday(const QofDate * qd)193 qof_date_is_last_mday (const QofDate *qd)
194 {
195 g_return_val_if_fail (qd, FALSE);
196 g_return_val_if_fail (qd->qd_valid, FALSE);
197 return (qd->qd_mday ==
198 qof_date_get_mday (qd->qd_mon, qd->qd_year));
199 }
200
201 gboolean
qof_date_format_add(const gchar * str,QofDateFormat * identifier)202 qof_date_format_add (const gchar * str, QofDateFormat * identifier)
203 {
204 struct tm check;
205 gint len;
206 time_t now;
207 gchar test[MAX_DATE_BUFFER];
208
209 /** \todo Move to QofDate and qofgmtime_r */
210 g_return_val_if_fail (QofDateInit, FALSE);
211 g_return_val_if_fail (str, FALSE);
212 g_return_val_if_fail (strlen (str) != 0, FALSE);
213 /* prevent really long strings being passed */
214 ENTER (" str=%s", str);
215 if (strlen (str) > MAX_DATE_LENGTH)
216 {
217 LEAVE (" '%s' is too long! Max=%d str_len=%d",
218 str, MAX_DATE_LENGTH, (gint) strlen (str));
219 return FALSE;
220 }
221 /* test the incoming string using the current time. */
222 now = time (NULL);
223 test[0] = '\1';
224 check = *gmtime_r (&now, &check);
225 /* need to allow time related formats -
226 don't use g_date_strftime here. */
227 len = strftime (test, (MAX_DATE_BUFFER - 1), str, &check);
228 if (len == 0 && test[0] != '\0')
229 {
230 LEAVE (" strftime could not understand '%s'", str);
231 return FALSE;
232 }
233 len = strlen (test);
234 if (len > MAX_DATE_LENGTH)
235 {
236 LEAVE (" %s creates a string '%s' that is too long!"
237 " Max=%d str_len=%d", str, test, MAX_DATE_LENGTH, len);
238 return FALSE;
239 }
240 *identifier = g_hash_table_size (DateFormatTable) + 1;
241 {
242 QofDateEntry *d = g_new0 (QofDateEntry, 1);
243 d->format = str;
244 d->name = str;
245 d->separator = locale_separator;
246 d->df = *identifier;
247 g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
248 }
249 LEAVE (" successful");
250 return TRUE;
251 }
252
253 const gchar *
qof_date_format_to_name(QofDateFormat format)254 qof_date_format_to_name (QofDateFormat format)
255 {
256 QofDateEntry *d;
257
258 g_return_val_if_fail (QofDateInit, NULL);
259 d = g_hash_table_lookup (DateFormatTable, GINT_TO_POINTER (format));
260 if (!d)
261 {
262 PERR (" unknown format: '%d'", format);
263 return NULL;
264 }
265 return d->name;
266 }
267
268 gboolean
qof_date_format_set_name(const gchar * name,QofDateFormat format)269 qof_date_format_set_name (const gchar * name, QofDateFormat format)
270 {
271 QofDateEntry *d;
272
273 g_return_val_if_fail (QofDateInit, FALSE);
274 if (format <= DATE_FORMAT_LAST)
275 return FALSE;
276 d = g_hash_table_lookup (DateFormatTable, GINT_TO_POINTER (format));
277 if (!d)
278 {
279 PERR (" unknown format: '%d'", format);
280 return FALSE;
281 }
282 d->name = name;
283 g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (format), d);
284 return TRUE;
285 }
286
287 QofDateFormat
qof_date_format_get_current(void)288 qof_date_format_get_current (void)
289 {
290 return dateFormat;
291 }
292
293 gboolean
qof_date_format_set_current(QofDateFormat df)294 qof_date_format_set_current (QofDateFormat df)
295 {
296 QofDateEntry *d;
297
298 g_return_val_if_fail (QofDateInit, FALSE);
299 d = g_hash_table_lookup (DateFormatTable, GINT_TO_POINTER (df));
300 if (!d)
301 {
302 PERR (" unknown format: '%d'", df);
303 return FALSE;
304 }
305 dateFormat = d->df;
306 return TRUE;
307 }
308
309 const gchar *
qof_date_format_get_format(QofDateFormat df)310 qof_date_format_get_format (QofDateFormat df)
311 {
312 QofDateEntry *d;
313
314 g_return_val_if_fail (QofDateInit, NULL);
315 d = g_hash_table_lookup (DateFormatTable, GINT_TO_POINTER (df));
316 if (!d)
317 {
318 PERR (" unknown format: '%d'", df);
319 return NULL;
320 }
321 return d->format;
322 }
323
324 gchar
qof_date_format_get_date_separator(QofDateFormat df)325 qof_date_format_get_date_separator (QofDateFormat df)
326 {
327 QofDateEntry *d;
328
329 g_return_val_if_fail (QofDateInit, locale_separator);
330 d = g_hash_table_lookup (DateFormatTable, GINT_TO_POINTER (df));
331 if (!d)
332 {
333 PERR (" unknown format: '%d'", df);
334 return locale_separator;
335 }
336 return d->separator;
337 }
338
339 gboolean
qof_date_format_set_date_separator(const gchar sep,QofDateFormat df)340 qof_date_format_set_date_separator (const gchar sep, QofDateFormat df)
341 {
342 QofDateEntry *d;
343
344 g_return_val_if_fail (QofDateInit, FALSE);
345 if (df < DATE_FORMAT_LAST)
346 {
347 DEBUG (" Prevented attempt to override a default format");
348 return FALSE;
349 }
350 if (g_ascii_isdigit (sep))
351 return FALSE;
352 d = g_hash_table_lookup (DateFormatTable, GINT_TO_POINTER (df));
353 if (!d)
354 {
355 PERR (" unknown format: '%d'", df);
356 return FALSE;
357 }
358 d->separator = sep;
359 g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (df), d);
360 return TRUE;
361 }
362
363 struct iter
364 {
365 const gchar *name;
366 QofDateFormat df;
367 };
368
369 static void
lookup_name(gpointer key,gpointer value,gpointer data)370 lookup_name (gpointer key __attribute__ ((unused)), gpointer value,
371 gpointer data)
372 {
373 struct iter *i;
374 QofDateEntry *d;
375
376 i = (struct iter *) data;
377 d = (QofDateEntry *) value;
378 if (0 == safe_strcmp (d->name, i->name))
379 {
380 i->df = d->df;
381 }
382 }
383
384 QofDateFormat
qof_date_format_from_name(const gchar * name)385 qof_date_format_from_name (const gchar * name)
386 {
387 struct iter i;
388
389 if (!name)
390 return -1;
391 if (0 == safe_strcmp (name, "us"))
392 return QOF_DATE_FORMAT_US;
393 if (0 == safe_strcmp (name, "uk"))
394 return QOF_DATE_FORMAT_UK;
395 if (0 == safe_strcmp (name, "ce"))
396 return QOF_DATE_FORMAT_CE;
397 if (0 == safe_strcmp (name, "utc"))
398 return QOF_DATE_FORMAT_UTC;
399 if (0 == safe_strcmp (name, "iso"))
400 return QOF_DATE_FORMAT_ISO;
401 if (0 == safe_strcmp (name, "locale"))
402 return QOF_DATE_FORMAT_LOCALE;
403 if (0 == safe_strcmp (name, "custom"))
404 return QOF_DATE_FORMAT_CUSTOM;
405 i.name = name;
406 i.df = -1;
407 g_hash_table_foreach (DateFormatTable, lookup_name, &i);
408 return i.df;
409 }
410
411 static QofDate*
date_normalise(QofDate * date)412 date_normalise (QofDate * date)
413 {
414 gint days, leap;
415
416 g_return_val_if_fail (date, NULL);
417
418 date->qd_sec -= date->qd_gmt_off;
419 /* if value is negative, just add */
420 if ((date->qd_nanosecs >= QOF_NSECS) ||
421 (date->qd_nanosecs <= -QOF_NSECS))
422 {
423 date->qd_sec += date->qd_nanosecs / QOF_NSECS;
424 date->qd_nanosecs = date->qd_nanosecs % QOF_NSECS;
425 if (date->qd_nanosecs < 0)
426 {
427 date->qd_nanosecs += QOF_NSECS;
428 date->qd_sec--;
429 }
430 }
431 if ((date->qd_sec >= 60) || (date->qd_sec <= -60))
432 {
433 date->qd_min += date->qd_sec / 60;
434 date->qd_sec = date->qd_sec % 60;
435 if (date->qd_sec < 0)
436 {
437 date->qd_sec += 60;
438 date->qd_min--;
439 }
440 }
441 if ((date->qd_min >= 60) || (date->qd_min <= -60))
442 {
443 date->qd_hour += date->qd_min / 60;
444 date->qd_min = date->qd_min % 60;
445 if (date->qd_min < 0)
446 {
447 date->qd_min += 60;
448 date->qd_hour--;
449 }
450 }
451 /* Year Zero does not exist, 1BC is immediately followed by 1AD. */
452 if (date->qd_year == 0)
453 date->qd_year = -1;
454 /* qd_mon starts at 1, not zero */
455 if (date->qd_mon == 0)
456 date->qd_mon = 1;
457 /* qd_mday starts at 1, not zero */
458 if (date->qd_mday == 0)
459 date->qd_mday = 1;
460 if ((date->qd_hour >= 24) || (date->qd_hour <= -24))
461 {
462 date->qd_mday += date->qd_hour / 24;
463 date->qd_hour = date->qd_hour % 24;
464 if (date->qd_hour < 0)
465 {
466 date->qd_hour += 24;
467 date->qd_mday--;
468 }
469 }
470 /* yes, [13] is correct == total at end of month_12 */
471 leap = days_in_year[qof_date_isleap(date->qd_year)][13];
472 while (date->qd_mday > leap)
473 {
474 date->qd_year++;
475 leap = days_in_year[qof_date_isleap(date->qd_year)][13];
476 date->qd_mday -= leap;
477 }
478 while (date->qd_mday < (leap*-1))
479 {
480 date->qd_year--;
481 leap = days_in_year[qof_date_isleap(date->qd_year)][13];
482 date->qd_mday += leap;
483 }
484 if ((date->qd_mon > 12) || (date->qd_mon < -12))
485 {
486 gint leap = days_in_year[qof_date_isleap(date->qd_year)][13];
487 date->qd_year += date->qd_mon / 12;
488 date->qd_mon = date->qd_mon % 12;
489 if (date->qd_mday > leap)
490 date->qd_mday -= leap;
491 if (date->qd_mday < (leap*-1))
492 date->qd_mday += leap;
493 if (date->qd_mon < 0)
494 {
495 /* -1 == Dec, -4 == Sep */
496 date->qd_mon += 12 + 1;
497 if (date->qd_year < 0)
498 {
499 date->qd_year++;
500 }
501 else
502 {
503 date->qd_year--;
504 }
505 /*date->qd_year = (date->qd_year < 0) ?
506 date->qd_year++ : date->qd_year--;*/
507 }
508 }
509 days = days_in_months[qof_date_isleap(date->qd_year)][date->qd_mon];
510 while (date->qd_mday < 0)
511 {
512 date->qd_mday += days;
513 date->qd_mon--;
514 if (date->qd_mon < 1)
515 {
516 date->qd_year -= date->qd_mon / 12;
517 date->qd_mon = date->qd_mon % 12;
518 /* if year was AD and is now zero, reset to BC. */
519 if ((date->qd_year == 0) && (date->qd_mon < 0))
520 date->qd_year = -1;
521 }
522 days = days_in_months[qof_date_isleap(date->qd_year)][date->qd_mon];
523 }
524 while (date->qd_mday > days)
525 {
526 date->qd_mday -= days;
527 date->qd_mon++;
528 if (date->qd_mon > 11)
529 {
530 date->qd_year += date->qd_mon / 12;
531 date->qd_mon = date->qd_mon % 12;
532 /* if year was BC and is now zero, reset to AD. */
533 if ((date->qd_year == 0) && (date->qd_mon > 0))
534 date->qd_year = +1;
535 }
536 days = days_in_months[qof_date_isleap(date->qd_year)][date->qd_mon];
537 }
538 /* use sensible defaults */
539 if (date->qd_mday == 0)
540 date->qd_mday = 1;
541 if (date->qd_mon == 0)
542 date->qd_mon = 1;
543 /* use days_in_year to set yday */
544 date->qd_yday = (date->qd_mday - 1) +
545 days_in_year[qof_date_isleap(date->qd_year)][date->qd_mon];
546 set_day_of_the_week (date);
547
548 /* qd_year has no realistic limits */
549 date->qd_valid = TRUE;
550 date->qd_zone = "GMT";
551 date->qd_is_dst = 0;
552 date->qd_gmt_off = 0L;
553 return date;
554 }
555
556 QofDate *
qof_date_parse(const gchar * str,QofDateFormat df)557 qof_date_parse (const gchar * str, QofDateFormat df)
558 {
559 const gchar *format;
560 QofDateError error;
561 QofDate *date;
562 gchar * G_GNUC_UNUSED check;
563
564 check = NULL;
565 error = ERR_NO_ERROR;
566 date = qof_date_new ();
567 format = qof_date_format_get_format (df);
568 check = strptime_internal (str, format, date, &error);
569 check = NULL;
570 if (error != ERR_NO_ERROR)
571 {
572 qof_date_free (date);
573 return NULL;
574 }
575
576 date = date_normalise (date);
577 return date;
578 }
579
580 gchar *
qof_date_print(const QofDate * date,QofDateFormat df)581 qof_date_print (const QofDate * date, QofDateFormat df)
582 {
583 size_t result;
584 gchar temp[MAX_DATE_BUFFER];
585 QofDateEntry *d;
586
587 g_return_val_if_fail (QofDateInit, NULL);
588 g_return_val_if_fail (date, NULL);
589 g_return_val_if_fail (date->qd_valid, NULL);
590 d = g_hash_table_lookup (DateFormatTable,
591 GINT_TO_POINTER (df));
592 g_return_val_if_fail (d, NULL);
593 temp[0] = '\1';
594 result = strftime_case (FALSE, temp, MAX_DATE_BUFFER,
595 d->format, date, 1, date->qd_nanosecs);
596 if (result == 0 && temp[0] != '\0')
597 {
598 PERR (" qof extended strftime failed");
599 return NULL;
600 }
601 return g_strndup(temp, result);
602 }
603
604 /* QofDate handlers */
605
606 QofDate *
qof_date_new(void)607 qof_date_new (void)
608 {
609 QofDate *d;
610
611 d = g_new0 (QofDate, 1);
612 return d;
613 }
614
615 QofDate *
qof_date_get_current(void)616 qof_date_get_current (void)
617 {
618 QofTime *qt;
619 QofDate *qd;
620
621 qt = qof_time_get_current ();
622 qd = qof_date_from_qtime (qt);
623 qof_time_free (qt);
624 return qd;
625 }
626
627 QofDate *
qof_date_new_dmy(gint day,gint month,gint64 year)628 qof_date_new_dmy (gint day, gint month, gint64 year)
629 {
630 QofDate *qd;
631
632 qd = g_new0 (QofDate, 1);
633 qd->qd_mday = day;
634 qd->qd_mon = month;
635 qd->qd_year = year;
636 if(!qof_date_valid (qd))
637 return NULL;
638 return qd;
639 }
640
641 void
qof_date_free(QofDate * date)642 qof_date_free (QofDate * date)
643 {
644 g_return_if_fail (date);
645 g_free (date);
646 date = NULL;
647 }
648
649 gboolean
qof_date_valid(QofDate * date)650 qof_date_valid (QofDate *date)
651 {
652 g_return_val_if_fail (date, FALSE);
653 date = date_normalise (date);
654 if (date->qd_valid == FALSE)
655 {
656 PERR (" unknown QofDate error");
657 return FALSE;
658 }
659 return TRUE;
660 }
661
662 gboolean
qof_date_equal(const QofDate * d1,const QofDate * d2)663 qof_date_equal (const QofDate *d1, const QofDate *d2)
664 {
665 if (0 == qof_date_compare (d1, d2))
666 return TRUE;
667 return FALSE;
668 }
669
670 gint
qof_date_compare(const QofDate * d1,const QofDate * d2)671 qof_date_compare (const QofDate * d1, const QofDate * d2)
672 {
673 if ((!d1) && (!d2))
674 return 0;
675 if (d1 == d2)
676 return 0;
677 if (!d1)
678 return -1;
679 if (!d2)
680 return 1;
681 if (d1->qd_year < d2->qd_year)
682 return -1;
683 if (d1->qd_year > d2->qd_year)
684 return 1;
685 if (d1->qd_mon < d2->qd_mon)
686 return -1;
687 if (d1->qd_mon > d2->qd_mon)
688 return 1;
689 if (d1->qd_mday < d2->qd_mday)
690 return -1;
691 if (d1->qd_mday > d2->qd_mday)
692 return 1;
693 if (d1->qd_hour < d2->qd_hour)
694 return -1;
695 if (d1->qd_hour > d2->qd_hour)
696 return 1;
697 if (d1->qd_min < d2->qd_min)
698 return -1;
699 if (d1->qd_min > d2->qd_min)
700 return 1;
701 if (d1->qd_sec < d2->qd_sec)
702 return -1;
703 if (d1->qd_sec > d2->qd_sec)
704 return 1;
705 if (d1->qd_nanosecs < d2->qd_nanosecs)
706 return -1;
707 if (d1->qd_nanosecs > d2->qd_nanosecs)
708 return 1;
709 return 0;
710 }
711
712 QofDate *
qof_date_from_struct_tm(const struct tm * stm)713 qof_date_from_struct_tm (const struct tm *stm)
714 {
715 QofDate *d;
716
717 g_return_val_if_fail (stm, NULL);
718 d = g_new0 (QofDate, 1);
719 d->qd_sec = stm->tm_sec;
720 d->qd_min = stm->tm_min;
721 d->qd_hour = stm->tm_hour;
722 d->qd_mday = stm->tm_mday;
723 d->qd_mon = stm->tm_mon + 1;
724 d->qd_year = stm->tm_year + 1900;
725 d->qd_wday = stm->tm_wday;
726 d->qd_yday = stm->tm_yday;
727 d->qd_is_dst = stm->tm_isdst;
728 d->qd_gmt_off = stm->tm_gmtoff;
729 d->qd_zone = stm->tm_zone;
730 d->qd_valid = TRUE;
731 d = date_normalise(d);
732 return d;
733 }
734
735 gboolean
qof_date_to_struct_tm(const QofDate * qd,struct tm * stm,glong * nanosecs)736 qof_date_to_struct_tm (const QofDate * qd, struct tm * stm,
737 glong *nanosecs)
738 {
739 g_return_val_if_fail (qd, FALSE);
740 g_return_val_if_fail (stm, FALSE);
741 g_return_val_if_fail (qd->qd_valid, FALSE);
742 if ((qd->qd_year > G_MAXINT) || (qd->qd_year < 1900))
743 {
744 PERR (" date too large for struct tm");
745 return FALSE;
746 }
747 stm->tm_sec = qd->qd_sec;
748 stm->tm_min = qd->qd_min;
749 stm->tm_hour = qd->qd_hour;
750 stm->tm_mday = qd->qd_mday;
751 stm->tm_mon = qd->qd_mon - 1;
752 stm->tm_year = qd->qd_year - 1900;
753 stm->tm_wday = qd->qd_wday;
754 stm->tm_yday = qd->qd_yday;
755 stm->tm_isdst = qd->qd_is_dst;
756 stm->tm_gmtoff = qd->qd_gmt_off;
757 stm->tm_zone = qd->qd_zone;
758 if (nanosecs != NULL)
759 *nanosecs = qd->qd_nanosecs;
760 return TRUE;
761 }
762
763 gboolean
qof_date_to_gdate(const QofDate * qd,GDate * gd)764 qof_date_to_gdate (const QofDate *qd, GDate *gd)
765 {
766 g_return_val_if_fail (qd, FALSE);
767 g_return_val_if_fail (gd, FALSE);
768 g_return_val_if_fail (qd->qd_valid, FALSE);
769 if (qd->qd_year >= G_MAXUINT16)
770 {
771 PERR (" QofDate out of range of GDate");
772 return FALSE;
773 }
774 if (!g_date_valid_dmy (qd->qd_mday, qd->qd_mon, qd->qd_year))
775 {
776 PERR (" GDate failed to allow day, month and/or year");
777 return FALSE;
778 }
779 g_date_set_dmy (gd, qd->qd_mday, qd->qd_mon, qd->qd_year);
780 return TRUE;
781 }
782
783 QofDate *
qof_date_from_gdate(const GDate * date)784 qof_date_from_gdate (const GDate *date)
785 {
786 QofDate * qd;
787
788 g_return_val_if_fail (g_date_valid (date), NULL);
789 qd = qof_date_new ();
790 qd->qd_year = g_date_get_year (date);
791 qd->qd_mon = g_date_get_month (date);
792 qd->qd_mday = g_date_get_day (date);
793 qd = date_normalise (qd);
794 return qd;
795 }
796
797 static void
qof_date_offset(const QofTime * time,glong offset,QofDate * qd)798 qof_date_offset (const QofTime *time, glong offset, QofDate *qd)
799 {
800 glong days;
801 gint64 rem, y, yg;
802 const guint16 *ip;
803 QofTimeSecs t;
804
805 g_return_if_fail (qd);
806 g_return_if_fail (time);
807 t = qof_time_get_secs ((QofTime*)time);
808 days = t / SECS_PER_DAY;
809 rem = t % SECS_PER_DAY;
810 rem += offset;
811 while (rem < 0)
812 {
813 rem += SECS_PER_DAY;
814 --days;
815 }
816 while (rem >= SECS_PER_DAY)
817 {
818 rem -= SECS_PER_DAY;
819 ++days;
820 }
821 qd->qd_hour = rem / SECS_PER_HOUR;
822 rem %= SECS_PER_HOUR;
823 qd->qd_min = rem / 60;
824 qd->qd_sec = rem % 60;
825 /* January 1, 1970 was a Thursday. */
826 qd->qd_wday = (4 + days) % 7;
827 if (qd->qd_wday < 0)
828 qd->qd_wday += 7;
829 y = 1970;
830 while (days < 0 || days >= (qof_date_isleap (y) ? 366 : 365))
831 {
832 /* Guess a corrected year, assuming 365 days per year. */
833 yg = y + days / 365 - (days % 365 < 0);
834 /* Adjust DAYS and Y to match the guessed year. */
835 days -= ((yg - y) * 365
836 + LEAPS_THRU_END_OF (yg - 1)
837 - LEAPS_THRU_END_OF (y - 1));
838 y = yg;
839 }
840 qd->qd_year = y;
841 qd->qd_yday = days;
842 ip = days_in_year[qof_date_isleap(y)];
843 for (y = 12; days < (glong) ip[y]; --y)
844 continue;
845 days -= ip[y];
846 qd->qd_mon = y;
847 qd->qd_mday = days + 1;
848 }
849
850 /* safe to use time_t here because only values
851 within the range of a time_t have any leapseconds. */
852 static gint
count_leapseconds(time_t interval)853 count_leapseconds (time_t interval)
854 {
855 time_t altered;
856 struct tm utc;
857
858 altered = interval;
859 utc = *gmtime_r (&interval, &utc);
860 altered = mktime (&utc);
861 return altered - interval;
862 }
863
864 /*static inline gint*/
865 static gint
extract_interval(const QofTime * qt)866 extract_interval (const QofTime *qt)
867 {
868 gint leap_seconds;
869 QofTimeSecs t, l;
870 const QofTime *now;
871
872 leap_seconds = 0;
873 t = qof_time_get_secs (qt);
874 now = qof_time_get_current ();
875 l = (qof_time_get_secs (now) > G_MAXINT32) ?
876 G_MAXINT32 : qof_time_get_secs (now);
877 leap_seconds = ((t > l) || (t < 0)) ?
878 count_leapseconds (l) :
879 count_leapseconds (t);
880 return leap_seconds;
881 }
882
883 QofDate *
qof_date_from_qtime(const QofTime * qt)884 qof_date_from_qtime (const QofTime *qt)
885 {
886 QofDate *qd;
887 gint leap_extra_secs;
888
889 /* may not want to create a new time or date - it
890 complicates memory management. */
891 g_return_val_if_fail (qt, NULL);
892 g_return_val_if_fail (qof_time_is_valid (qt), NULL);
893 qd = qof_date_new ();
894 leap_extra_secs = 0;
895 setenv ("TZ", "GMT", 1);
896 tzset();
897 leap_extra_secs = extract_interval (qt);
898 qof_date_offset (qt, leap_extra_secs, qd);
899 qd->qd_nanosecs = qof_time_get_nanosecs (qt);
900 qd->qd_is_dst = 0;
901 qd->qd_zone = "GMT";
902 qd->qd_gmt_off = 0L;
903 if (!qof_date_valid(qd))
904 return NULL;
905 return qd;
906 }
907
908 gint64
days_between(gint64 year1,gint64 year2)909 days_between (gint64 year1, gint64 year2)
910 {
911 gint64 i, start, end, l;
912
913 l = 0;
914 if (year1 == year2)
915 return l;
916 start = (year1 < year2) ? year1 : year2;
917 end = (year2 < year1) ? year1: year2;
918 for (i = start; i < end; i++)
919 {
920 l += (qof_date_isleap(i)) ? 366 : 365;
921 }
922 return l;
923 }
924
925 QofTime*
qof_date_to_qtime(const QofDate * qd)926 qof_date_to_qtime (const QofDate *qd)
927 {
928 QofTime *qt;
929 QofTimeSecs c;
930
931 g_return_val_if_fail (qd, NULL);
932 g_return_val_if_fail (qd->qd_valid, NULL);
933 c = 0;
934 qt = NULL;
935 if (qd->qd_year < 1970)
936 {
937 c = qd->qd_sec;
938 c += QOF_MIN_TO_SEC(qd->qd_min);
939 c += QOF_HOUR_TO_SEC(qd->qd_hour);
940 c += QOF_DAYS_TO_SEC(qd->qd_yday);
941 c -= QOF_DAYS_TO_SEC(days_between (1970, qd->qd_year));
942 c -= qd->qd_gmt_off;
943 qt = qof_time_set (c, qd->qd_nanosecs);
944 }
945 if (qd->qd_year >= 1970)
946 {
947 c = qd->qd_sec;
948 c += QOF_MIN_TO_SEC(qd->qd_min);
949 c += QOF_HOUR_TO_SEC(qd->qd_hour);
950 c += QOF_DAYS_TO_SEC(qd->qd_yday);
951 c += QOF_DAYS_TO_SEC(days_between (1970, qd->qd_year));
952 c -= qd->qd_gmt_off;
953 qt = qof_time_set (c, qd->qd_nanosecs);
954 }
955 return qt;
956 }
957
958 QofTime *
qof_date_time_difference(const QofDate * date1,const QofDate * date2)959 qof_date_time_difference (const QofDate * date1,
960 const QofDate * date2)
961 {
962 gint64 days;
963 QofTime *secs;
964
965 secs = qof_time_new ();
966 days = days_between (date1->qd_year, date2->qd_year);
967 qof_time_add_secs(secs, QOF_DAYS_TO_SEC(days));
968 if (days >= 0)
969 {
970 /* positive value, add date2 secs, subtract date1 */
971 qof_time_add_secs(secs, -1 *
972 (QOF_HOUR_TO_SEC(date1->qd_hour) -
973 QOF_MIN_TO_SEC(date1->qd_min) -
974 (date1->qd_sec)));
975 qof_time_add_secs(secs,
976 QOF_HOUR_TO_SEC(date2->qd_hour) +
977 QOF_MIN_TO_SEC(date2->qd_min) +
978 (date2->qd_sec));
979 qof_time_set_nanosecs(secs,
980 (date1->qd_nanosecs - date2->qd_nanosecs));
981 }
982 if (days < 0)
983 {
984 /* negative value*/
985 qof_time_add_secs (secs,
986 QOF_HOUR_TO_SEC(date1->qd_hour) -
987 QOF_MIN_TO_SEC(date1->qd_min) -
988 (date1->qd_sec));
989 qof_time_add_secs (secs, -1 *
990 (QOF_HOUR_TO_SEC(date2->qd_hour) +
991 QOF_MIN_TO_SEC(date2->qd_min) +
992 (date2->qd_sec)));
993 qof_time_set_nanosecs(secs,
994 (date2->qd_nanosecs - date1->qd_nanosecs));
995 }
996 return secs;
997 }
998
999 gboolean
qof_date_adddays(QofDate * qd,gint days)1000 qof_date_adddays (QofDate * qd, gint days)
1001 {
1002 g_return_val_if_fail (qd, FALSE);
1003 g_return_val_if_fail (qof_date_valid (qd), FALSE);
1004 qd->qd_mday += days;
1005 return qof_date_valid (qd);
1006 }
1007
1008 gboolean
qof_date_addmonths(QofDate * qd,gint months,gboolean track_last_day)1009 qof_date_addmonths (QofDate * qd, gint months,
1010 gboolean track_last_day)
1011 {
1012 g_return_val_if_fail (qd, FALSE);
1013 g_return_val_if_fail (qof_date_valid (qd), FALSE);
1014 qd->qd_mon += months % 12;
1015 qd->qd_year += months / 12;
1016 g_return_val_if_fail (qof_date_valid (qd), FALSE);
1017 if (track_last_day && qof_date_is_last_mday (qd))
1018 {
1019 qd->qd_mday = qof_date_get_mday (qd->qd_mon,
1020 qd->qd_year);
1021 }
1022 return TRUE;
1023 }
1024
1025 inline gboolean
qof_date_set_day_end(QofDate * qd)1026 qof_date_set_day_end (QofDate * qd)
1027 {
1028 qd->qd_hour = 23;
1029 qd->qd_min = 59;
1030 qd->qd_sec = 59;
1031 qd->qd_nanosecs = (QOF_NSECS - 1);
1032 return qof_date_valid (qd);
1033 }
1034
1035 inline gboolean
qof_date_set_day_start(QofDate * qd)1036 qof_date_set_day_start (QofDate * qd)
1037 {
1038 g_return_val_if_fail (qd, FALSE);
1039 qd->qd_hour = 0;
1040 qd->qd_min = 0;
1041 qd->qd_sec = 0;
1042 qd->qd_nanosecs = G_GINT64_CONSTANT(0);
1043 return qof_date_valid (qd);
1044 }
1045
1046 inline gboolean
qof_date_set_day_middle(QofDate * qd)1047 qof_date_set_day_middle (QofDate * qd)
1048 {
1049 g_return_val_if_fail (qd, FALSE);
1050 qd->qd_hour = 12;
1051 qd->qd_min = 0;
1052 qd->qd_sec = 0;
1053 qd->qd_nanosecs = G_GINT64_CONSTANT(0);
1054 return qof_date_valid (qd);
1055 }
1056
1057 /******************** END OF FILE *************************/
1058