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