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