1 /*
2  * datetime.c: Date and time routines grabbed from elsewhere.
3  *
4  * Authors:
5  *   Miguel de Icaza (miguel@gnu.org)
6  *   Morten Welinder <terra@gnome.org>
7  *   Jukka-Pekka Iivonen <iivonen@iki.fi>
8  *   Andreas J. Guelzow <aguelzow@taliesin.ca>
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License as
12  * published by the Free Software Foundation; either version 2 of the
13  * License, or (at your option) version 3.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
23  * USA
24  */
25 
26 #include <goffice/goffice-config.h>
27 #include "datetime.h"
28 #include <goffice/math/go-math.h>
29 
30 #include <math.h>
31 #include <string.h>
32 
33 #define SECS_PER_DAY (24 * 60 * 60)
34 #define HALF_SEC (0.5 / SECS_PER_DAY)
35 
36 /* ------------------------------------------------------------------------- */
37 static GODateConventions *
go_date_conventions_copy(GODateConventions const * dc)38 go_date_conventions_copy (GODateConventions const *dc)
39 {
40 	GODateConventions *res = g_new (GODateConventions, 1);
41 	memcpy (res, dc, sizeof(GODateConventions));
42 	return res;
43 }
44 
45 GType
go_date_conventions_get_type(void)46 go_date_conventions_get_type (void)
47 {
48 	static GType t = 0;
49 
50 	if (t == 0)
51 		t = g_boxed_type_register_static ("GODateConventions",
52 			 (GBoxedCopyFunc)go_date_conventions_copy,
53 			 (GBoxedFreeFunc)g_free);
54 	return t;
55 }
56 
57 /* ------------------------------------------------------------------------- */
58 
59 /* One less that the Julian day number of 19000101.  */
60 static int date_origin_1900 = 0;
61 /* Julian day number of 19040101.  */
62 static int date_origin_1904 = 0;
63 
64 /*
65  * The serial number of 19000228.  Excel allocates a serial number for
66  * the non-existing date 19000229.
67  */
68 static const int date_serial_19000228 = 59;
69 
70 /* 31-Dec-9999 */
71 static const int date_serial_max_1900 = 2958465;
72 static const int date_serial_max_1904 = 2957003;
73 
74 
75 static void
date_init(void)76 date_init (void)
77 {
78 	GDate date;
79 
80 	g_date_clear (&date, 1);
81 
82 	/* Day 1 means 1st of January of 1900 */
83 	g_date_set_dmy (&date, 1, 1, 1900);
84 	date_origin_1900 = g_date_get_julian (&date) - 1;
85 
86 	/* Day 0 means 1st of January of 1904 */
87 	g_date_set_dmy (&date, 1, 1, 1904);
88 	date_origin_1904 = g_date_get_julian (&date);
89 }
90 
91 /* ------------------------------------------------------------------------- */
92 
93 int
go_date_g_to_serial(GDate const * date,GODateConventions const * conv)94 go_date_g_to_serial (GDate const *date, GODateConventions const *conv)
95 {
96 	int day;
97 
98 	if (!date_origin_1900)
99 		date_init ();
100 
101 	if (conv && conv->use_1904)
102 		return g_date_get_julian (date) - date_origin_1904;
103 	day = g_date_get_julian (date) - date_origin_1900;
104 	return day + (day > date_serial_19000228);
105 }
106 
107 /* ------------------------------------------------------------------------- */
108 
109 void
go_date_serial_to_g(GDate * res,int serial,GODateConventions const * conv)110 go_date_serial_to_g (GDate *res, int serial, GODateConventions const *conv)
111 {
112 	if (!date_origin_1900)
113 		date_init ();
114 
115 	g_date_clear (res, 1);
116 	if (conv && conv->use_1904) {
117 		if (serial > date_serial_max_1904)
118 			return;
119 		g_date_set_julian (res, serial + date_origin_1904);
120 	} else if (serial > date_serial_19000228) {
121 		if (serial > date_serial_max_1900)
122 			return;
123 		if (serial == date_serial_19000228 + 1)
124 			return;
125 		g_date_set_julian (res, serial + date_origin_1900 - 1);
126 	} else
127 		g_date_set_julian (res, serial + date_origin_1900);
128 }
129 
130 /* ------------------------------------------------------------------------- */
131 
132 double
go_date_timet_to_serial_raw(time_t t,GODateConventions const * conv)133 go_date_timet_to_serial_raw (time_t t, GODateConventions const *conv)
134 {
135 	struct tm *tm = localtime (&t);
136 	int secs;
137 	GDate date;
138 
139         g_date_clear (&date, 1);
140 	g_date_set_time_t (&date, t);
141 	secs = tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec;
142 	return go_date_g_to_serial (&date, conv) +
143 		secs / (double)SECS_PER_DAY;
144 }
145 
146 /* ------------------------------------------------------------------------- */
147 
148 int
go_date_serial_raw_to_serial(double raw)149 go_date_serial_raw_to_serial (double raw)
150 {
151 	return (int) floor (raw + HALF_SEC);
152 }
153 
154 /* ------------------------------------------------------------------------- */
155 
156 int
go_date_timet_to_serial(time_t t,GODateConventions const * conv)157 go_date_timet_to_serial (time_t t, GODateConventions const *conv)
158 {
159 	return go_date_serial_raw_to_serial (go_date_timet_to_serial_raw (t, conv));
160 }
161 
162 /* ------------------------------------------------------------------------- */
163 
164 time_t
go_date_serial_to_timet(int serial,GODateConventions const * conv)165 go_date_serial_to_timet (int serial, GODateConventions const *conv)
166 {
167 	GDate gd;
168 
169 	go_date_serial_to_g (&gd, serial, conv);
170 	if (g_date_valid (&gd)) {
171 		struct tm tm;
172 		g_date_to_struct_tm (&gd, &tm);
173 		return mktime (&tm);
174 	} else
175 		return -1;
176 }
177 
178 /* ------------------------------------------------------------------------- */
179 /* This is time-only assuming a 24h day.  It probably loses completely on */
180 /* days with summer time ("daylight savings") changes.  */
181 
182 int
go_date_serial_raw_to_seconds(double raw)183 go_date_serial_raw_to_seconds (double raw)
184 {
185 	raw += HALF_SEC;
186 	return (raw - floor (raw)) * SECS_PER_DAY;
187 }
188 
189 /* ------------------------------------------------------------------------- */
190 
191 int
go_date_timet_to_seconds(time_t t)192 go_date_timet_to_seconds (time_t t)
193 {
194 	/* we just want the seconds, actual date does not matter. So we can ignore
195 	 * the date convention (1900 vs 1904) */
196 	return go_date_serial_raw_to_seconds (go_date_timet_to_serial_raw (t, NULL));
197 }
198 
199 /* ------------------------------------------------------------------------- */
200 
201 int
go_date_g_months_between(GDate const * date1,GDate const * date2)202 go_date_g_months_between (GDate const *date1, GDate const *date2)
203 {
204 	g_return_val_if_fail (g_date_valid (date1), 0);
205 	g_return_val_if_fail (g_date_valid (date2), 0);
206 
207 	/* find the difference according to the month and year ordinals,
208 	   but discount the last month if there are not enough days. */
209 	return 12 * (g_date_get_year (date2) - g_date_get_year (date1))
210 		+ g_date_get_month (date2) - g_date_get_month (date1)
211 		- (g_date_get_day (date2) >= g_date_get_day (date1) ? 0 : 1);
212 }
213 
214 /* ------------------------------------------------------------------------- */
215 
216 int
go_date_g_years_between(GDate const * date1,GDate const * date2)217 go_date_g_years_between (GDate const *date1, GDate const *date2)
218 {
219 	int months;
220 
221 	g_return_val_if_fail (g_date_valid (date1), 0);
222 	g_return_val_if_fail (g_date_valid (date2), 0);
223 
224 	months = go_date_g_months_between (date1, date2);
225 	return months > 0 ? months / 12 : -(-months / 12);
226 }
227 
228 /* ------------------------------------------------------------------------- */
229 
230 /**
231  * go_date_weeknum:
232  * @date:      date
233  * @method:    week numbering method
234  *
235  * Returns: week number according to the given method.
236  * 1:   Week starts on Sunday.  January 1 is in week 1.
237  * 2:   Week starts on Monday.  January 1 is in week 1.
238  * 150: ISO 8601 week number.
239  */
240 int
go_date_weeknum(GDate const * date,int method)241 go_date_weeknum (GDate const *date, int method)
242 {
243 	g_return_val_if_fail (g_date_valid (date), -1);
244 	g_return_val_if_fail (method == GO_WEEKNUM_METHOD_SUNDAY ||
245 			      method == GO_WEEKNUM_METHOD_MONDAY ||
246 			      method == GO_WEEKNUM_METHOD_ISO,
247 			      -1);
248 
249 	switch (method) {
250 	case GO_WEEKNUM_METHOD_SUNDAY: {
251 		GDate jan1;
252 		GDateWeekday wd;
253 		int doy;
254 
255 		g_date_clear (&jan1, 1);
256 		g_date_set_dmy (&jan1, 1, 1, g_date_get_year (date));
257 		wd = g_date_get_weekday (&jan1);
258 		doy = g_date_get_day_of_year (date);
259 
260 		return (doy + (int)wd + 6 - (wd == G_DATE_SUNDAY ? 7 : 0)) / 7;
261 	}
262 	case GO_WEEKNUM_METHOD_MONDAY: {
263 		GDate jan1;
264 		GDateWeekday wd;
265 		int doy;
266 
267 		g_date_clear (&jan1, 1);
268 		g_date_set_dmy (&jan1, 1, 1, g_date_get_year (date));
269 		wd = g_date_get_weekday (&jan1);
270 		doy = g_date_get_day_of_year (date);
271 
272 		return (doy + (int)wd + 5) / 7;
273 	}
274 	case GO_WEEKNUM_METHOD_ISO:
275 		return g_date_get_iso8601_week_of_year (date);
276 	default:
277 		return -1;
278 	}
279 }
280 
281 /* ------------------------------------------------------------------------- */
282 
283 static gint32
days_between_GO_BASIS_MSRB_30_360(GDate const * from,GDate const * to)284 days_between_GO_BASIS_MSRB_30_360 (GDate const *from, GDate const *to)
285 {
286 	int y1, m1, d1, y2, m2, d2;
287 
288 	y1 = g_date_get_year (from);
289 	m1 = g_date_get_month (from);
290 	d1 = g_date_get_day (from);
291 	y2 = g_date_get_year (to);
292 	m2 = g_date_get_month (to);
293 	d2 = g_date_get_day (to);
294 
295 	if (d1 == d2 && m1 == m2 && y1 == y2)
296 		return 0;
297 	if (d1 == 31)
298 		d1 = 30;
299 	if (d2 == 31 && d1 == 30)
300 		d2 = 30;
301 
302 	if (m1 == 2 && g_date_is_last_of_month (from)) {
303 		if (m2 == 2 && g_date_is_last_of_month (to))
304 			d2 = 30;
305 		d1 = 30;
306 	}
307 
308 	return (y2 - y1) * 360 + (m2 - m1) * 30 + (d2 - d1);
309 }
310 
311 static gint32
days_between_GO_BASIS_MSRB_30_360_SYM(GDate const * from,GDate const * to)312 days_between_GO_BASIS_MSRB_30_360_SYM (GDate const *from, GDate const *to)
313 {
314 	int y1, m1, d1, y2, m2, d2;
315 
316 	y1 = g_date_get_year (from);
317 	m1 = g_date_get_month (from);
318 	d1 = g_date_get_day (from);
319 	y2 = g_date_get_year (to);
320 	m2 = g_date_get_month (to);
321 	d2 = g_date_get_day (to);
322 
323 	if (m1 == 2 && g_date_is_last_of_month (from))
324 		d1 = 30;
325 	if (m2 == 2 && g_date_is_last_of_month (to))
326 		d2 = 30;
327 	if (d2 == 31 && d1 >= 30)
328 		d2 = 30;
329 	if (d1 == 31)
330 		d1 = 30;
331 
332 	return (y2 - y1) * 360 + (m2 - m1) * 30 + (d2 - d1);
333 }
334 
335 static gint32
days_between_GO_BASIS_30E_360(GDate const * from,GDate const * to)336 days_between_GO_BASIS_30E_360 (GDate const *from, GDate const *to)
337 {
338 	int y1, m1, d1, y2, m2, d2;
339 
340 	y1 = g_date_get_year (from);
341 	m1 = g_date_get_month (from);
342 	d1 = g_date_get_day (from);
343 	y2 = g_date_get_year (to);
344 	m2 = g_date_get_month (to);
345 	d2 = g_date_get_day (to);
346 
347 	if (d1 == 31)
348 		d1 = 30;
349 	if (d2 == 31)
350 		d2 = 30;
351 
352 	return (y2 - y1) * 360 + (m2 - m1) * 30 + (d2 - d1);
353 }
354 
355 static gint32
days_between_GO_BASIS_30Ep_360(GDate const * from,GDate const * to)356 days_between_GO_BASIS_30Ep_360 (GDate const *from, GDate const *to)
357 {
358 	int y1, m1, d1, y2, m2, d2;
359 
360 	y1 = g_date_get_year (from);
361 	m1 = g_date_get_month (from);
362 	d1 = g_date_get_day (from);
363 	y2 = g_date_get_year (to);
364 	m2 = g_date_get_month (to);
365 	d2 = g_date_get_day (to);
366 
367 	if (d1 == 31)
368 		d1 = 30;
369 	if (d2 == 31) {
370 		d2 = 1;
371 		m2++;
372 		/* No need to check for m2 == 13 since 12*30 == 360 */
373 	}
374 
375 	return (y2 - y1) * 360 + (m2 - m1) * 30 + (d2 - d1);
376 }
377 
378 /*
379  * go_date_days_between_basis
380  *
381  * @from: GDate *
382  * @to: GDate *
383  * @basis: GOBasisType
384  * see datetime.h and doc/fn-financial-basis.txt for details
385  *
386  * @in_order: dates are considered in order
387  *
388  * returns    : Number of days after the earlier and not after the later date
389  *
390  */
391 
392 gint32
go_date_days_between_basis(GDate const * from,GDate const * to,GOBasisType basis)393 go_date_days_between_basis (GDate const *from, GDate const *to, GOBasisType basis)
394 {
395 	int sign = 1;
396 
397 	if (g_date_compare (from, to) == 1) {
398 		GDate const *tmp = from;
399 		from = to;
400 		to = tmp;
401 		sign = -1;
402 	}
403 
404 	switch (basis) {
405 	case GO_BASIS_ACT_ACT:
406 	case GO_BASIS_ACT_360:
407 	case GO_BASIS_ACT_365:
408 		return sign * (g_date_get_julian (to) - g_date_get_julian (from));
409 	case GO_BASIS_30E_360:
410 		return sign * days_between_GO_BASIS_30E_360 (from, to);
411 	case GO_BASIS_30Ep_360:
412 		return sign * days_between_GO_BASIS_30Ep_360 (from, to);
413 	case GO_BASIS_MSRB_30_360_SYM:
414 		return sign * days_between_GO_BASIS_MSRB_30_360_SYM (from, to);
415 	case GO_BASIS_MSRB_30_360:
416 	default:
417 		return sign * days_between_GO_BASIS_MSRB_30_360 (from, to);
418 	}
419 }
420 
421 /* ------------------------------------------------------------------------- */
422 
423 /*
424  * go_coup_cd
425  *
426  * @res:
427  * @settlement: GDate *
428  * @maturity: GDate *  must follow settlement strictly
429  * @freq: int      divides 12 evenly
430  * @eom: gboolean whether to do special end of month
431  *                       handling
432  * @next: gboolean whether next or previous date
433  *
434  * returns    : GDate *  next  or previous coupon date
435  *
436  * this function does not depend on the basis of counting!
437  */
438 void
go_coup_cd(GDate * result,GDate const * settlement,GDate const * maturity,int freq,gboolean eom,gboolean next)439 go_coup_cd (GDate *result, GDate const *settlement, GDate const *maturity,
440 	 int freq, gboolean eom, gboolean next)
441 {
442         int        months, periods;
443 	gboolean   is_eom_special;
444 
445 	is_eom_special = eom && g_date_is_last_of_month (maturity);
446 
447 	g_date_clear (result, 1);
448 
449 	months = 12 / freq;
450 	periods = (g_date_get_year(maturity) - g_date_get_year (settlement));
451 	if (periods > 0)
452 		periods = (periods - 1) * freq;
453 
454 	do {
455 		g_date_set_julian (result, g_date_get_julian (maturity));
456 		periods++;
457 		g_date_subtract_months (result, periods * months);
458 		if (is_eom_special) {
459 			int ndays = g_date_get_days_in_month
460 				(g_date_get_month (result),
461 				 g_date_get_year (result));
462 			g_date_set_day (result, ndays);
463 		}
464 	} while (g_date_compare (settlement, result) < 0 );
465 
466 	if (next) {
467 		g_date_set_julian (result, g_date_get_julian (maturity));
468 		periods--;
469 		g_date_subtract_months (result, periods * months);
470 		if (is_eom_special) {
471 			int ndays = g_date_get_days_in_month
472 				(g_date_get_month (result),
473 				 g_date_get_year (result));
474 			g_date_set_day (result, ndays);
475 		}
476 	}
477 }
478 
479 /* ------------------------------------------------------------------------- */
480 
481 
482 /**
483  * go_coupdays:
484  * @settlement: #GDate
485  * @maturity: #GDate
486  * @conv: #GoCouponConvention
487  *
488  * Returns: the number of days in the coupon period of the settlement date.
489  * Currently, returns negative numbers if the branch is not implemented.
490  **/
491 double
go_coupdays(GDate const * settlement,GDate const * maturity,GoCouponConvention const * conv)492 go_coupdays (GDate const *settlement, GDate const *maturity,
493 	     GoCouponConvention const *conv)
494 {
495 	GDate prev, next;
496 
497         switch (conv->basis) {
498 	case GO_BASIS_MSRB_30_360:
499 	case GO_BASIS_ACT_360:
500         case GO_BASIS_30E_360:
501         case GO_BASIS_30Ep_360:
502 		return 360 / conv->freq;
503 	case GO_BASIS_ACT_365:
504 		return 365.0 / conv->freq;
505 	case GO_BASIS_ACT_ACT:
506 	default:
507 		go_coup_cd (&next, settlement, maturity, conv->freq, conv->eom, TRUE);
508 		go_coup_cd (&prev, settlement, maturity, conv->freq, conv->eom, FALSE);
509 		return go_date_days_between_basis (&prev, &next, GO_BASIS_ACT_ACT);
510         }
511 }
512 
513 /* ------------------------------------------------------------------------- */
514 
515 
516 /**
517  * go_coupdaybs:
518  * @settlement: #GDate
519  * @maturity: #GDate
520  * @conv: #GoCouponConvention
521  *
522  * Returns: the number of days from the beginning of the coupon period to the
523  * 	settlement date.
524  **/
525 double
go_coupdaybs(GDate const * settlement,GDate const * maturity,GoCouponConvention const * conv)526 go_coupdaybs (GDate const *settlement, GDate const *maturity,
527 	      GoCouponConvention const *conv)
528 {
529 	GDate prev_coupon;
530 	go_coup_cd (&prev_coupon, settlement, maturity, conv->freq, conv->eom, FALSE);
531 	return go_date_days_between_basis (&prev_coupon, settlement, conv->basis);
532 }
533 
534 /**
535  * go_coupdaysnc:
536  * @settlement:
537  * @maturity:
538  * @conv: #GoCouponConvention
539  *
540  * Returns: the number of days from the settlement date to the next
541  * coupon date.
542  **/
543 double
go_coupdaysnc(GDate const * settlement,GDate const * maturity,GoCouponConvention const * conv)544 go_coupdaysnc (GDate const *settlement, GDate const *maturity,
545 	       GoCouponConvention const *conv)
546 {
547 	GDate next_coupon;
548 	go_coup_cd (&next_coupon, settlement, maturity, conv->freq, conv->eom, TRUE);
549 	return go_date_days_between_basis (settlement, &next_coupon, conv->basis);
550 }
551 
552 int
go_date_convention_base(GODateConventions const * conv)553 go_date_convention_base (GODateConventions const *conv)
554 {
555 	g_return_val_if_fail (conv != NULL, 1900);
556 	return conv->use_1904 ? 1904 : 1900;
557 }
558 
559 const GODateConventions *
go_date_conv_from_str(const char * s)560 go_date_conv_from_str (const char *s)
561 {
562 	static const GODateConventions apple1904 = { TRUE };
563 	static const GODateConventions lotus1900 = { FALSE };
564 
565 	g_return_val_if_fail (s != NULL, NULL);
566 
567 	if (strcmp (s, "Apple:1904") == 0)
568 		return &apple1904;
569 
570 	if (strcmp (s, "Lotus:1900") == 0)
571 		return &lotus1900;
572 
573 	return NULL;
574 }
575 
576 gboolean
go_date_conv_equal(const GODateConventions * a,const GODateConventions * b)577 go_date_conv_equal (const GODateConventions *a, const GODateConventions *b)
578 {
579 	g_return_val_if_fail (a != NULL, FALSE);
580 	g_return_val_if_fail (b != NULL, FALSE);
581 
582 	return a->use_1904 == b->use_1904;
583 }
584 
585 double
go_date_conv_translate(double f,const GODateConventions * src,const GODateConventions * dst)586 go_date_conv_translate (double f,
587 			const GODateConventions *src,
588 			const GODateConventions *dst)
589 {
590 	g_return_val_if_fail (src != NULL, f);
591 	g_return_val_if_fail (dst != NULL, f);
592 
593 	if (!go_finite (f) || go_date_conv_equal (src, dst))
594 		return f;
595 
596 	if (dst->use_1904) {
597 		if (f < date_serial_19000228 + 1)
598 			f += 1;
599 		f -= 1462;
600 	} else {
601 		f += 1462;
602 		if (f < date_serial_19000228 + 2)
603 			f -= 1;
604 	}
605 
606 	return f;
607 }
608 
609 /* ------------------------------------------------------------------------- */
610 
611 static char *
deal_with_spaces(char * buf)612 deal_with_spaces (char *buf)
613 {
614 	/* Abbreviated January in "fi_FI" has a terminating space.  */
615 	gsize len = strlen (buf);
616 
617 	while (len > 0) {
618 		char *prev = g_utf8_prev_char (buf + len);
619 		if (!g_unichar_isspace (g_utf8_get_char (prev)))
620 			break;
621 		len = prev - buf;
622 	}
623 	buf[len] = 0;
624 	return g_strdup (buf);
625 }
626 
627 char *
go_date_weekday_name(GDateWeekday wd,gboolean abbrev)628 go_date_weekday_name (GDateWeekday wd, gboolean abbrev)
629 {
630 	char buf[100];
631 	GDate date;
632 
633 	g_return_val_if_fail (g_date_valid_weekday (wd), NULL);
634 
635 	g_date_clear (&date, 1);
636 	g_date_set_dmy (&date, 5 + (int)wd, 3, 2006);
637 	g_date_strftime (buf, sizeof (buf) - 1,
638 			 abbrev ? "%a" : "%A",
639 			 &date);
640 
641 	return deal_with_spaces (buf);
642 }
643 
644 char *
go_date_month_name(GDateMonth m,gboolean abbrev)645 go_date_month_name (GDateMonth m, gboolean abbrev)
646 {
647 	char buf[100];
648 	GDate date;
649 
650 	g_return_val_if_fail (g_date_valid_month (m), NULL);
651 
652 	g_date_clear (&date, 1);
653 	g_date_set_dmy (&date, 15, m, 2006);
654 	g_date_strftime (buf, sizeof (buf) - 1,
655 			 abbrev ? "%b" : "%B",
656 			 &date);
657 
658 	return deal_with_spaces (buf);
659 }
660 
661 /* ------------------------------------------------------------------------- */
662