QOF  0.7.5
qofdate.c
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
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
149 hash_value_free (gpointer key __attribute__ ((unused)), gpointer value,
150  gpointer data __attribute__ ((unused)))
151 {
152  g_free (value);
153 }
154 
155 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
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
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
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
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 
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 *
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
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 
289 {
290  return dateFormat;
291 }
292 
293 gboolean
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 *
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
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
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
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 
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*
412 date_normalise (QofDate * date)
413 {
414  gint days;
415 
416  g_return_val_if_fail (date, NULL);
417  date->qd_sec -= date->qd_gmt_off;
418  /* if value is negative, just add */
419  if ((date->qd_nanosecs >= QOF_NSECS) ||
420  (date->qd_nanosecs <= -QOF_NSECS))
421  {
422  date->qd_sec += date->qd_nanosecs / QOF_NSECS;
423  date->qd_nanosecs = date->qd_nanosecs % QOF_NSECS;
424  if (date->qd_nanosecs < 0)
425  {
426  date->qd_nanosecs += QOF_NSECS;
427  date->qd_sec--;
428  }
429  }
430  if ((date->qd_sec >= 60) || (date->qd_sec <= -60))
431  {
432  date->qd_min += date->qd_sec / 60;
433  date->qd_sec = date->qd_sec % 60;
434  if (date->qd_sec < 0)
435  {
436  date->qd_sec += 60;
437  date->qd_min--;
438  }
439  }
440  if ((date->qd_min >= 60) || (date->qd_min <= -60))
441  {
442  date->qd_hour += date->qd_min / 60;
443  date->qd_min = date->qd_min % 60;
444  if (date->qd_min < 0)
445  {
446  date->qd_min += 60;
447  date->qd_hour--;
448  }
449  }
450  if ((date->qd_hour >= 24) || (date->qd_hour <= -24))
451  {
452  date->qd_mday += date->qd_hour / 24;
453  date->qd_hour = date->qd_hour % 24;
454  if (date->qd_hour < 0)
455  {
456  date->qd_hour += 24;
457  date->qd_mday--;
458  }
459  }
460  if ((date->qd_mon > 12) || (date->qd_mon < -12))
461  {
462  date->qd_year += date->qd_mon / 12;
463  date->qd_mon = date->qd_mon % 12;
464  if (date->qd_mon < 0)
465  {
466  /* -1 == Dec, -4 == Sep */
467  date->qd_mon += 12 + 1;
468  date->qd_year = (date->qd_year < 0) ?
469  date->qd_year++ : date->qd_year--;
470  }
471  }
472  /* qd_mon starts at 1, not zero */
473  if (date->qd_mon == 0)
474  date->qd_mon = 1;
475  /* Year Zero does not exist, 1BC is immediately followed by 1AD. */
476  if (date->qd_year == 0)
477  date->qd_year = -1;
478  days = days_in_months[qof_date_isleap(date->qd_year)][date->qd_mon];
479  while (date->qd_mday < 0)
480  {
481  date->qd_mday += days;
482  date->qd_mon--;
483  if (date->qd_mon < 1)
484  {
485  date->qd_year -= date->qd_mon / 12;
486  date->qd_mon = date->qd_mon % 12;
487  /* if year was AD and is now zero, reset to BC. */
488  if ((date->qd_year == 0) && (date->qd_mon < 0))
489  date->qd_year = -1;
490  }
491  days = days_in_months[qof_date_isleap(date->qd_year)][date->qd_mon];
492  }
493  while (date->qd_mday > days)
494  {
495  date->qd_mday -= days;
496  date->qd_mon++;
497  if (date->qd_mon > 12)
498  {
499  date->qd_year += date->qd_mon / 12;
500  date->qd_mon = date->qd_mon % 12;
501  /* if year was BC and is now zero, reset to AD. */
502  if ((date->qd_year == 0) && (date->qd_mon > 0))
503  date->qd_year = +1;
504  }
505  days = days_in_months[qof_date_isleap(date->qd_year)][date->qd_mon];
506  }
507  /* use sensible defaults */
508  if (date->qd_mday == 0)
509  date->qd_mday = 1;
510  if (date->qd_mon == 0)
511  date->qd_mon = 1;
512  /* use days_in_year to set yday */
513  date->qd_yday = (date->qd_mday - 1) +
514  days_in_year[qof_date_isleap(date->qd_year)][date->qd_mon];
515  set_day_of_the_week (date);
516  /* qd_year has no realistic limits */
517  date->qd_valid = TRUE;
518  date->qd_zone = "GMT";
519  date->qd_is_dst = 0;
520  date->qd_gmt_off = 0L;
521  return date;
522 }
523 
524 QofDate *
525 qof_date_parse (const gchar * str, QofDateFormat df)
526 {
527  const gchar *format;
528  QofDateError error;
529  QofDate *date;
530  gchar *check;
531 
532  check = NULL;
533  error = ERR_NO_ERROR;
534  date = qof_date_new ();
535  format = qof_date_format_get_format (df);
536  check = strptime_internal (str, format, date, &error);
537  if (error != ERR_NO_ERROR)
538  {
539  qof_date_free (date);
540  return NULL;
541  }
542  date = date_normalise (date);
543  return date;
544 }
545 
546 gchar *
548 {
549  size_t result;
550  gchar temp[MAX_DATE_BUFFER];
551  QofDateEntry *d;
552 
553  g_return_val_if_fail (QofDateInit, NULL);
554  g_return_val_if_fail (date, NULL);
555  g_return_val_if_fail (date->qd_valid, NULL);
556  d = g_hash_table_lookup (DateFormatTable,
557  GINT_TO_POINTER (df));
558  g_return_val_if_fail (d, NULL);
559  temp[0] = '\1';
560  result = strftime_case (FALSE, temp, MAX_DATE_BUFFER,
561  d->format, date, 1, date->qd_nanosecs);
562  if (result == 0 && temp[0] != '\0')
563  {
564  PERR (" qof extended strftime failed");
565  return NULL;
566  }
567  return g_strndup(temp, result);
568 }
569 
570 /* QofDate handlers */
571 
572 QofDate *
574 {
575  QofDate *d;
576 
577  d = g_new0 (QofDate, 1);
578  return d;
579 }
580 
581 QofDate *
583 {
584  QofTime *qt;
585  QofDate *qd;
586 
587  qt = qof_time_get_current ();
588  qd = qof_date_from_qtime (qt);
589  qof_time_free (qt);
590  return qd;
591 }
592 
593 QofDate *
594 qof_date_new_dmy (gint day, gint month, gint64 year)
595 {
596  QofDate *qd;
597 
598  qd = g_new0 (QofDate, 1);
599  qd->qd_mday = day;
600  qd->qd_mon = month;
601  qd->qd_year = year;
602  if(!qof_date_valid (qd))
603  return NULL;
604  return qd;
605 }
606 
607 void
609 {
610  g_return_if_fail (date);
611  g_free (date);
612  date = NULL;
613 }
614 
615 gboolean
617 {
618  g_return_val_if_fail (date, FALSE);
619  date = date_normalise (date);
620  if (date->qd_valid == FALSE)
621  {
622  PERR (" unknown QofDate error");
623  return FALSE;
624  }
625  return TRUE;
626 }
627 
628 gboolean
629 qof_date_equal (const QofDate *d1, const QofDate *d2)
630 {
631  if (0 == qof_date_compare (d1, d2))
632  return TRUE;
633  return FALSE;
634 }
635 
636 gint
637 qof_date_compare (const QofDate * d1, const QofDate * d2)
638 {
639  if ((!d1) && (!d2))
640  return 0;
641  if (d1 == d2)
642  return 0;
643  if (!d1)
644  return -1;
645  if (!d2)
646  return 1;
647  if (d1->qd_year < d2->qd_year)
648  return -1;
649  if (d1->qd_year > d2->qd_year)
650  return 1;
651  if (d1->qd_mon < d2->qd_mon)
652  return -1;
653  if (d1->qd_mon > d2->qd_mon)
654  return 1;
655  if (d1->qd_mday < d2->qd_mday)
656  return -1;
657  if (d1->qd_mday > d2->qd_mday)
658  return 1;
659  if (d1->qd_hour < d2->qd_hour)
660  return -1;
661  if (d1->qd_hour > d2->qd_hour)
662  return 1;
663  if (d1->qd_min < d2->qd_min)
664  return -1;
665  if (d1->qd_min > d2->qd_min)
666  return 1;
667  if (d1->qd_sec < d2->qd_sec)
668  return -1;
669  if (d1->qd_sec > d2->qd_sec)
670  return 1;
671  if (d1->qd_nanosecs < d2->qd_nanosecs)
672  return -1;
673  if (d1->qd_nanosecs > d2->qd_nanosecs)
674  return 1;
675  return 0;
676 }
677 
678 QofDate *
679 qof_date_from_struct_tm (const struct tm *stm)
680 {
681  QofDate *d;
682 
683  g_return_val_if_fail (stm, NULL);
684  d = g_new0 (QofDate, 1);
685  d->qd_sec = stm->tm_sec;
686  d->qd_min = stm->tm_min;
687  d->qd_hour = stm->tm_hour;
688  d->qd_mday = stm->tm_mday;
689  d->qd_mon = stm->tm_mon + 1;
690  d->qd_year = stm->tm_year + 1900;
691  d->qd_wday = stm->tm_wday;
692  d->qd_yday = stm->tm_yday;
693  d->qd_is_dst = stm->tm_isdst;
694  d->qd_gmt_off = stm->tm_gmtoff;
695  d->qd_zone = stm->tm_zone;
696  d->qd_valid = TRUE;
697  d = date_normalise(d);
698  return d;
699 }
700 
701 gboolean
702 qof_date_to_struct_tm (const QofDate * qd, struct tm * stm,
703  glong *nanosecs)
704 {
705  g_return_val_if_fail (qd, FALSE);
706  g_return_val_if_fail (stm, FALSE);
707  g_return_val_if_fail (qd->qd_valid, FALSE);
708  if ((qd->qd_year > G_MAXINT) || (qd->qd_year < 1900))
709  {
710  PERR (" date too large for struct tm");
711  return FALSE;
712  }
713  stm->tm_sec = qd->qd_sec;
714  stm->tm_min = qd->qd_min;
715  stm->tm_hour = qd->qd_hour;
716  stm->tm_mday = qd->qd_mday;
717  stm->tm_mon = qd->qd_mon - 1;
718  stm->tm_year = qd->qd_year - 1900;
719  stm->tm_wday = qd->qd_wday;
720  stm->tm_yday = qd->qd_yday;
721  stm->tm_isdst = qd->qd_is_dst;
722  stm->tm_gmtoff = qd->qd_gmt_off;
723  stm->tm_zone = qd->qd_zone;
724  if (nanosecs != NULL)
725  *nanosecs = qd->qd_nanosecs;
726  return TRUE;
727 }
728 
729 gboolean
730 qof_date_to_gdate (const QofDate *qd, GDate *gd)
731 {
732  g_return_val_if_fail (qd, FALSE);
733  g_return_val_if_fail (gd, FALSE);
734  g_return_val_if_fail (qd->qd_valid, FALSE);
735  if (qd->qd_year >= G_MAXUINT16)
736  {
737  PERR (" QofDate out of range of GDate");
738  return FALSE;
739  }
740  if (!g_date_valid_dmy (qd->qd_mday, qd->qd_mon, qd->qd_year))
741  {
742  PERR (" GDate failed to allow day, month and/or year");
743  return FALSE;
744  }
745  g_date_set_dmy (gd, qd->qd_mday, qd->qd_mon, qd->qd_year);
746  return TRUE;
747 }
748 
749 QofDate *
750 qof_date_from_gdate (const GDate *date)
751 {
752  QofDate * qd;
753 
754  g_return_val_if_fail (g_date_valid (date), NULL);
755  qd = qof_date_new ();
756  qd->qd_year = g_date_get_year (date);
757  qd->qd_mon = g_date_get_month (date);
758  qd->qd_mday = g_date_get_day (date);
759  qd = date_normalise (qd);
760  return qd;
761 }
762 
763 static void
764 qof_date_offset (const QofTime *time, glong offset, QofDate *qd)
765 {
766  glong days;
767  gint64 rem, y, yg;
768  const guint16 *ip;
769  QofTimeSecs t;
770 
771  g_return_if_fail (qd);
772  g_return_if_fail (time);
773  t = qof_time_get_secs ((QofTime*)time);
774  days = t / SECS_PER_DAY;
775  rem = t % SECS_PER_DAY;
776  rem += offset;
777  while (rem < 0)
778  {
779  rem += SECS_PER_DAY;
780  --days;
781  }
782  while (rem >= SECS_PER_DAY)
783  {
784  rem -= SECS_PER_DAY;
785  ++days;
786  }
787  qd->qd_hour = rem / SECS_PER_HOUR;
788  rem %= SECS_PER_HOUR;
789  qd->qd_min = rem / 60;
790  qd->qd_sec = rem % 60;
791  /* January 1, 1970 was a Thursday. */
792  qd->qd_wday = (4 + days) % 7;
793  if (qd->qd_wday < 0)
794  qd->qd_wday += 7;
795  y = 1970;
796  while (days < 0 || days >= (__isleap (y) ? 366 : 365))
797  {
798  /* Guess a corrected year, assuming 365 days per year. */
799  yg = y + days / 365 - (days % 365 < 0);
800  /* Adjust DAYS and Y to match the guessed year. */
801  days -= ((yg - y) * 365
802  + LEAPS_THRU_END_OF (yg - 1)
803  - LEAPS_THRU_END_OF (y - 1));
804  y = yg;
805  }
806  qd->qd_year = y;
807  qd->qd_yday = days;
808  ip = days_in_year[qof_date_isleap(y)];
809  for (y = 12; days < (glong) ip[y]; --y)
810  continue;
811  days -= ip[y];
812  qd->qd_mon = y;
813  qd->qd_mday = days + 1;
814 }
815 
816 /* safe to use time_t here because only values
817 within the range of a time_t have any leapseconds. */
818 static gint
819 count_leapseconds (time_t interval)
820 {
821  time_t altered;
822  struct tm utc;
823 
824  altered = interval;
825  utc = *gmtime_r (&interval, &utc);
826  altered = mktime (&utc);
827  return altered - interval;
828 }
829 
830 /*static inline gint*/
831 static gint
832 extract_interval (const QofTime *qt)
833 {
834  gint leap_seconds;
835  QofTimeSecs t, l;
836  const QofTime *now;
837 
838  leap_seconds = 0;
839  t = qof_time_get_secs (qt);
840  now = qof_time_get_current ();
841  l = (qof_time_get_secs (now) > G_MAXINT32) ?
842  G_MAXINT32 : qof_time_get_secs (now);
843  leap_seconds = ((t > l) || (t < 0)) ?
844  count_leapseconds (l) :
845  count_leapseconds (t);
846  return leap_seconds;
847 }
848 
849 QofDate *
851 {
852  QofDate *qd;
853  gint leap_extra_secs;
854 
855  /* may not want to create a new time or date - it
856  complicates memory management. */
857  g_return_val_if_fail (qt, NULL);
858  g_return_val_if_fail (qof_time_is_valid (qt), NULL);
859  qd = qof_date_new ();
860  leap_extra_secs = 0;
861  setenv ("TZ", "GMT", 1);
862  tzset();
863  leap_extra_secs = extract_interval (qt);
864  qof_date_offset (qt, leap_extra_secs, qd);
866  qd->qd_is_dst = 0;
867  qd->qd_zone = "GMT";
868  qd->qd_gmt_off = 0L;
869  if (!qof_date_valid(qd))
870  return NULL;
871  return qd;
872 }
873 
874 gint64
875 days_between (gint64 year1, gint64 year2)
876 {
877  gint64 i, start, end, l;
878 
879  l = 0;
880  if (year1 == year2)
881  return l;
882  start = (year1 < year2) ? year1 : year2;
883  end = (year2 < year1) ? year1: year2;
884  for (i = start; i < end; i++)
885  {
886  l += (qof_date_isleap(i)) ? 366 : 365;
887  }
888  return l;
889 }
890 
891 QofTime*
893 {
894  QofTime *qt;
895  QofTimeSecs c;
896 
897  g_return_val_if_fail (qd, NULL);
898  g_return_val_if_fail (qd->qd_valid, NULL);
899  c = 0;
900  qt = NULL;
901  if (qd->qd_year < 1970)
902  {
903  c = qd->qd_sec;
904  c += QOF_MIN_TO_SEC(qd->qd_min);
905  c += QOF_HOUR_TO_SEC(qd->qd_hour);
906  c += QOF_DAYS_TO_SEC(qd->qd_yday);
907  c -= QOF_DAYS_TO_SEC(days_between (1970, qd->qd_year));
908  c -= qd->qd_gmt_off;
909  qt = qof_time_set (c, qd->qd_nanosecs);
910  }
911  if (qd->qd_year >= 1970)
912  {
913  c = qd->qd_sec;
914  c += QOF_MIN_TO_SEC(qd->qd_min);
915  c += QOF_HOUR_TO_SEC(qd->qd_hour);
916  c += QOF_DAYS_TO_SEC(qd->qd_yday);
917  c += QOF_DAYS_TO_SEC(days_between (1970, qd->qd_year));
918  c -= qd->qd_gmt_off;
919  qt = qof_time_set (c, qd->qd_nanosecs);
920  }
921  return qt;
922 }
923 
924 QofTime *
926  const QofDate * date2)
927 {
928  gint64 days;
929  QofTime *secs;
930 
931  secs = qof_time_new ();
932  days = days_between (date1->qd_year, date2->qd_year);
933  qof_time_add_secs(secs, QOF_DAYS_TO_SEC(days));
934  if (days >= 0)
935  {
936  /* positive value, add date2 secs, subtract date1 */
937  qof_time_add_secs(secs, -1 *
938  (QOF_HOUR_TO_SEC(date1->qd_hour) -
939  QOF_MIN_TO_SEC(date1->qd_min) -
940  (date1->qd_sec)));
941  qof_time_add_secs(secs,
942  QOF_HOUR_TO_SEC(date2->qd_hour) +
943  QOF_MIN_TO_SEC(date2->qd_min) +
944  (date2->qd_sec));
945  qof_time_set_nanosecs(secs,
946  (date1->qd_nanosecs - date2->qd_nanosecs));
947  }
948  if (days < 0)
949  {
950  /* negative value*/
951  qof_time_add_secs (secs,
952  QOF_HOUR_TO_SEC(date1->qd_hour) -
953  QOF_MIN_TO_SEC(date1->qd_min) -
954  (date1->qd_sec));
955  qof_time_add_secs (secs, -1 *
956  (QOF_HOUR_TO_SEC(date2->qd_hour) +
957  QOF_MIN_TO_SEC(date2->qd_min) +
958  (date2->qd_sec)));
959  qof_time_set_nanosecs(secs,
960  (date2->qd_nanosecs - date1->qd_nanosecs));
961  }
962  return secs;
963 }
964 
965 gboolean
966 qof_date_adddays (QofDate * qd, gint days)
967 {
968  g_return_val_if_fail (qd, FALSE);
969  g_return_val_if_fail (qof_date_valid (qd), FALSE);
970  qd->qd_mday += days;
971  return qof_date_valid (qd);
972 }
973 
974 gboolean
975 qof_date_addmonths (QofDate * qd, gint months,
976  gboolean track_last_day)
977 {
978  g_return_val_if_fail (qd, FALSE);
979  g_return_val_if_fail (qof_date_valid (qd), FALSE);
980  qd->qd_mon += months % 12;
981  qd->qd_year += months / 12;
982  g_return_val_if_fail (qof_date_valid (qd), FALSE);
983  if (track_last_day && qof_date_is_last_mday (qd))
984  {
985  qd->qd_mday = qof_date_get_mday (qd->qd_mon,
986  qd->qd_year);
987  }
988  return TRUE;
989 }
990 
991 inline gboolean
992 qof_date_set_day_end (QofDate * qd)
993 {
994  qd->qd_hour = 23;
995  qd->qd_min = 59;
996  qd->qd_sec = 59;
997  qd->qd_nanosecs = (QOF_NSECS - 1);
998  return qof_date_valid (qd);
999 }
1000 
1001 inline gboolean
1002 qof_date_set_day_start (QofDate * qd)
1003 {
1004  g_return_val_if_fail (qd, FALSE);
1005  qd->qd_hour = 0;
1006  qd->qd_min = 0;
1007  qd->qd_sec = 0;
1008  qd->qd_nanosecs = G_GINT64_CONSTANT(0);
1009  return qof_date_valid (qd);
1010 }
1011 
1012 inline gboolean
1013 qof_date_set_day_middle (QofDate * qd)
1014 {
1015  g_return_val_if_fail (qd, FALSE);
1016  qd->qd_hour = 12;
1017  qd->qd_min = 0;
1018  qd->qd_sec = 0;
1019  qd->qd_nanosecs = G_GINT64_CONSTANT(0);
1020  return qof_date_valid (qd);
1021 }
1022 
1023 /******************** END OF FILE *************************/