1 /*
2  *  gretl -- Gnu Regression, Econometrics and Time-series Library
3  *  Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  */
19 
20 #include "libgretl.h"
21 
22 /**
23  * SECTION:calendar
24  * @short_description: functions for working with dates
25  * @title: Calendar
26  * @include: libgretl.h
27  *
28  * Here we have various functions dealing with calendar dates;
29  * for the most part these are designed for handling daily
30  * time-series data.
31  */
32 
33 static int days_in_month[2][13] = {
34     {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
35     {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
36 };
37 
38 /* Jan 1, 0001, was a Monday on the proleptic Gregorian calendar */
39 #define DAY1 1
40 
41 /* Note on the GLib API used below: where "julian" occurs in the names
42    of GLib calendrical functions it refers to the "Julian day"; that is,
43    the number of days since some fixed starting point, as used by
44    astronomers. This is quite distinct from the Julian calendar.
45    However, GLib takes Julian day 1 to be the first of January in
46    AD 1, as opposed to the astronomical starting point in 4713 BC,
47    so these are not strictly Julian days, and in our own functions
48    which use the same concept we call them "epoch days".
49 */
50 
leap_year(int yr)51 static int leap_year (int yr)
52 {
53     return (!(yr % 4) && (yr % 100)) || !(yr % 400);
54 }
55 
valid_ymd(int y,int m,int d,int julian)56 static int valid_ymd (int y, int m, int d, int julian)
57 {
58     int ok = 1;
59 
60     if (!g_date_valid_dmy(d, m, y)) {
61 	ok = 0;
62 	if (julian && y > 0 && m == 2 && d == 29 && y%4 == 0) {
63 	    ok = 1;
64 	}
65     }
66 
67     return ok;
68 }
69 
day_of_week_from_ymd(int y,int m,int d,int julian)70 static int day_of_week_from_ymd (int y, int m, int d, int julian)
71 {
72     GDate date;
73     int wd;
74 
75     if (!valid_ymd(y, m, d, julian)) {
76 	return -1;
77     }
78 
79     g_date_clear(&date, 1);
80 
81     if (julian) {
82 	guint32 ed = epoch_day_from_julian_ymd(y, m, d);
83 
84 	g_date_set_julian(&date, ed);
85     } else {
86 	g_date_set_dmy(&date, d, m, y);
87     }
88 
89     wd = g_date_get_weekday(&date);
90 
91     /* switch to Sunday == 0 */
92     return wd == G_DATE_SUNDAY ? 0 : wd;
93 }
94 
95 /**
96  * epoch_day_from_ymd:
97  * @y: year (1 <= y <= 9999).
98  * @m: month (1 <= m <= 12).
99  * @d: day of month (1 <= d <= 31).
100  *
101  * Returns: the epoch day number, which equals 1 for the first of
102  * January in the year AD 1 on the proleptic Gregorian calendar,
103  * or 0 on error.
104  */
105 
epoch_day_from_ymd(int y,int m,int d)106 guint32 epoch_day_from_ymd (int y, int m, int d)
107 {
108     GDate date;
109 
110     if (!g_date_valid_dmy(d, m, y)) {
111 	return 0;
112     }
113 
114     g_date_clear(&date, 1);
115     g_date_set_dmy(&date, d, m, y);
116 
117     return g_date_get_julian(&date);
118 }
119 
120 /**
121  * ymd_bits_from_epoch_day:
122  * @ed: epoch day (ed >= 1).
123  * @y: location to receive year.
124  * @m: location to receive month.
125  * @m: location to receive day.
126  *
127  * Returns: 0 on success, non-zero on error.
128  */
129 
ymd_bits_from_epoch_day(guint32 ed,int * y,int * m,int * d)130 int ymd_bits_from_epoch_day (guint32 ed, int *y, int *m, int *d)
131 {
132     GDate date;
133 
134     if (!g_date_valid_julian(ed)) {
135 	return E_INVARG;
136     }
137 
138     g_date_clear(&date, 1);
139     g_date_set_julian(&date, ed);
140 
141     *y = g_date_get_year(&date);
142     *m = g_date_get_month(&date);
143     *d = g_date_get_day(&date);
144 
145     return 0;
146 }
147 
148 /**
149  * julian_ymd_bits_from_epoch_day:
150  * @ed: epoch day (ed >= 1).
151  * @y: location to receive year.
152  * @m: location to receive month.
153  * @m: location to receive day.
154  *
155  * Follows the algorithm of E.G. Richards (2013), "Calendars," In S.E.
156  * Urban & P.K. Seidelmann, eds. Explanatory Supplement to the Astronomical
157  * Almanac, 3rd ed. (pp. 585-624), Mill Valley, CA: University Science Books
158  * (as set out on https://en.wikipedia.org/wiki/Julian_day).
159  *
160  * There are other algorithms for this purpose on the internet but they are
161  * mostly wrong (at least, not right for all dates); many of them fail
162  * the round-trip test (date -> epoch day -> date) for some dates.
163  *
164  * Returns: 0 on success, non-zero on error.
165  */
166 
julian_ymd_bits_from_epoch_day(guint32 ed,int * py,int * pm,int * pd)167 int julian_ymd_bits_from_epoch_day (guint32 ed, int *py,
168 				    int *pm, int *pd)
169 {
170     int y = 4716;
171     int p = 1461;
172     int f = ed + 1721425 + 1401;
173     int e = 4 * f + 3;
174     int g = (e % p)/4;
175     int h = 5 * g + 2;
176 
177     /* The addition of 1721425 above translates from our
178        "epoch day" to Julian Day Number; the addition of 1401
179        to the JDN is specified by Richards' algorithm.
180     */
181 
182     *pd = (h % 153)/5 + 1;
183     *pm = (h/153 + 2) % 12 + 1;
184     *py = e/p - y + (14 - *pm)/12;
185 
186     return 0;
187 }
188 
189 /**
190  * epoch_day_from_julian_ymd:
191  * @y: year (y >= 1).
192  * @m: month (1 <= m <= 12).
193  * @d: day of month (1 <= d <= 31).
194  *
195  * The @y, @m and @d arguments are assumed to refer to a date on
196  * the Julian calendar. The conversion algorithm is taken from
197  * https://en.wikipedia.org/wiki/Julian_day, where it appears to
198  * be credited to the Department of Computer Science at UT, Austin.
199  *
200  * Returns: the epoch day number, which equals 1 for the first of
201  * January in the year AD 1 on the proleptic Gregorian calendar,
202  * or 0 on error.
203  */
204 
epoch_day_from_julian_ymd(int y,int m,int d)205 guint32 epoch_day_from_julian_ymd (int y, int m, int d)
206 {
207     int a = (14 - m)/12;
208     int jd;
209 
210     y = y + 4800 - a;
211     m = m + 12*a - 3;
212 
213     jd = d + (153*m + 2)/5 + 365*y + y/4 - 32083;
214 
215     if (jd <= 1721425) {
216 	/* prior to AD 1 */
217 	return 0;
218     } else {
219 	return (guint32) jd - 1721425;
220     }
221 }
222 
223 /**
224  * ymd_extended_from_epoch_day:
225  * @ed: epoch day (ed >= 1).
226  * @julian: non-zero to use Julian calendar, otherwise Gregorian.
227  * @err: location to receive error code.
228  *
229  * Returns: a string on the pattern YYYY-MM-DD (ISO 8601 extended
230  * date format) given the epoch day number, which equals 1 for the
231  * first of January in the year 1 AD, or NULL on error.
232  */
233 
ymd_extended_from_epoch_day(guint32 ed,int julian,int * err)234 char *ymd_extended_from_epoch_day (guint32 ed, int julian, int *err)
235 {
236     char *ret = NULL;
237     int y, m, d;
238     int myerr;
239 
240     if (julian) {
241 	myerr = julian_ymd_bits_from_epoch_day(ed, &y, &m, &d);
242     } else {
243 	myerr = ymd_bits_from_epoch_day(ed, &y, &m, &d);
244     }
245 
246     if (!myerr) {
247 	ret = calloc(12, 1);
248 	if (ret == NULL) {
249 	    myerr = E_ALLOC;
250 	} else {
251 	    sprintf(ret, "%04d-%02d-%02d", y, m, d);
252 	}
253     }
254 
255     if (err != NULL) {
256 	*err = myerr;
257     }
258 
259     return ret;
260 }
261 
262 /**
263  * ymd_basic_from_epoch_day:
264  * @ed: epoch day (ed >= 1).
265  * @julian: non-zero to use Julian calendar, otherwise Gregorian.
266  * @err: location to receive error code.
267  *
268  * Returns: an 8-digit number on the pattern YYYYMMDD (ISO 8601 basic
269  * date format) given the epoch day number, which equals 1 for the
270  * first of January in the year 1 AD, or #NADBL on error.
271  */
272 
ymd_basic_from_epoch_day(guint32 ed,int julian,int * err)273 double ymd_basic_from_epoch_day (guint32 ed, int julian, int *err)
274 {
275     int y = 0, m = 0, d = 0;
276 
277     if (julian) {
278 	*err = julian_ymd_bits_from_epoch_day(ed, &y, &m, &d);
279     } else {
280 	*err = ymd_bits_from_epoch_day(ed, &y, &m, &d);
281     }
282 
283     if (*err) {
284 	return NADBL;
285     } else {
286 	return 10000*y + 100*m + d;
287     }
288 }
289 
290 /**
291  * epoch_day_from_ymd_basic:
292  * @ymd: number that is supposed to represent YYYYMMDD.
293  *
294  * Returns: the epoch day number corresponding to @ymd, interpreted
295  * as YYYYMMDD (ISO 8601 "basic") if possible, or 0 on error.
296  */
297 
epoch_day_from_ymd_basic(double ymd)298 guint32 epoch_day_from_ymd_basic (double ymd)
299 {
300     gchar tmp[9];
301     int y, m, d, n;
302 
303     n = g_snprintf(tmp, 9, "%.0f", ymd);
304     if (n != 8) {
305 	return 0;
306     }
307 
308     y = floor(ymd / 10000);
309     ymd -= y * 10000;
310     m = floor(ymd / 100);
311     d = ymd - m * 100;
312 
313     return epoch_day_from_ymd(y, m, d);
314 }
315 
316 /**
317  * weekday_from_epoch_day:
318  * @ed: epoch day (ed >= 1).
319  *
320  * Returns: the weekday (Sunday = 0) corrsponding to @ed,
321  * or -1 on error.
322  */
323 
weekday_from_epoch_day(guint32 ed)324 int weekday_from_epoch_day (guint32 ed)
325 {
326     GDate date;
327     int wd;
328 
329     if (!g_date_valid_julian(ed)) {
330 	return -1;
331     }
332 
333     g_date_clear(&date, 1);
334     g_date_set_julian(&date, ed);
335     wd = g_date_get_weekday(&date);
336 
337     return wd == G_DATE_SUNDAY ? 0 : wd;
338 }
339 
340 /**
341  * get_epoch_day:
342  * @datestr: string representation of calendar date, in form
343  * YY[YY]-MM-DD.
344  *
345  * Returns: the epoch day number, or -1 on failure.
346  */
347 
get_epoch_day(const char * datestr)348 guint32 get_epoch_day (const char *datestr)
349 {
350     GDate date;
351     int y, m, d, nf = 0;
352     int ydigits = 0;
353 
354     if (strchr(datestr, '-')) {
355 	ydigits = strcspn(datestr, "-");
356 	nf = sscanf(datestr, YMD_READ_FMT, &y, &m, &d);
357     } else if (strchr(datestr, '/')) {
358 	ydigits = strcspn(datestr, "/");
359 	nf = sscanf(datestr, "%d/%d/%d", &y, &m, &d);
360     } else if (strlen(datestr) == 8) {
361 	ydigits = 4;
362 	nf = sscanf(datestr, "%4d%2d%2d", &y, &m, &d);
363     }
364 
365     if (nf != 3 || (ydigits != 4 && ydigits != 2)) {
366 	return 0;
367     }
368 
369     if (ydigits == 2) {
370 	y = FOUR_DIGIT_YEAR(y);
371     }
372 
373     if (!g_date_valid_dmy(d, m, y)) {
374 	return 0;
375     }
376 
377     g_date_clear(&date, 1);
378     g_date_set_dmy(&date, d, m, y);
379 
380     return g_date_get_julian(&date);
381 }
382 
383 /**
384  * calendar_obs_number:
385  * @datestr: string representation of calendar date, in form
386  * YY[YY]/MM/DD.
387  * @dset: pointer to dataset information.
388  *
389  * Returns: The zero-based observation number for the given
390  * date within the current data set.
391  */
392 
calendar_obs_number(const char * datestr,const DATASET * dset)393 int calendar_obs_number (const char *datestr, const DATASET *dset)
394 {
395     guint32 ed0 = (guint32) dset->sd0;
396     guint32 t = get_epoch_day(datestr);
397 
398 #ifdef CAL_DEBUG
399     fprintf(stderr, "calendar_obs_number: '%s' gave epoch day = %u\n",
400 	    datestr, t);
401 #endif
402 
403     if (t <= 0 || t < ed0) {
404 	return -1;
405     } else if (t == ed0) {
406 	return 0;
407     }
408 
409     /* subtract starting day for dataset */
410     t -= ed0;
411 
412     if (t <= 0) {
413 	return -1;
414     }
415 
416     if (dset->pd == 52) {
417 	/* weekly data */
418 	t /= 7;
419     } else if (dset->pd == 5 || dset->pd == 6) {
420 	/* daily, 5- or 6-day week: subtract number of irrelevant days */
421 	int startday = (ed0 - 1 + DAY1) % 7;
422 	int wkends = (t + startday - 1) / 7;
423 
424 #ifdef CAL_DEBUG
425 	printf("calendar_obs_number: ed0=%d, date=%s, t=%d, startday=%d, wkends=%d\n",
426 	       (int) ed0, date, (int) t, startday, wkends);
427 #endif
428 
429 	if (dset->pd == 5) {
430 	    t -= (2 * wkends);
431 	} else {
432 	    t -= wkends;
433 	}
434     }
435 
436     return (int) t;
437 }
438 
439 /* convert from $t in dataset to epoch day */
440 
t_to_epoch_day(int t,guint32 start,int wkdays)441 static int t_to_epoch_day (int t, guint32 start, int wkdays)
442 {
443     int startday = (start - 1 + DAY1) % 7;
444     int wkends = (t + startday - 1) / wkdays;
445 
446     if (wkdays == 5) {
447 	wkends *= 2;
448     }
449 
450     return start + t + wkends;
451 }
452 
453 /**
454  * epoch_day_from_t:
455  * @t: 0-based observation index.
456  * @dset: pointer to dataset.
457  *
458  * Returns: the epoch day based on calendrical information
459  * in @dset. In case of error 0 is returned.
460  */
461 
epoch_day_from_t(int t,const DATASET * dset)462 guint32 epoch_day_from_t (int t, const DATASET *dset)
463 {
464     guint32 d0 = (guint32) dset->sd0;
465     guint32 dt = 0;
466 
467     if (dset->pd == 52) {
468 	dt = d0 + 7 * t;
469     } else if (dset->pd == 7) {
470 	dt = d0 + t;
471     } else {
472 	dt = t_to_epoch_day(t, d0, dset->pd);
473     }
474 
475     return dt;
476 }
477 
478 /**
479  * calendar_date_string:
480  * @targ: string to be filled out.
481  * @t: zero-based index of observation.
482  * @dset: pointer to dataset.
483  *
484  * Writes to @targ the calendar representation of the date of
485  * observation @t, in the form YY[YY]/MM/DD.
486  *
487  * Returns: 0 on success, non-zero on error.
488  */
489 
calendar_date_string(char * targ,int t,const DATASET * dset)490 int calendar_date_string (char *targ, int t, const DATASET *dset)
491 {
492     guint32 d0, dt = 0;
493     int y, m, d;
494     int err = 0;
495 
496     *targ = '\0';
497     d0 = (guint32) dset->sd0;
498 
499     if (dset->pd == 52) {
500 	dt = d0 + 7 * t;
501     } else if (dset->pd == 7) {
502 	dt = d0 + t;
503     } else {
504 	/* 5- or 6-day data */
505 	if (t == 0 && dset->pd == 5) {
506 	    int wd = weekday_from_epoch_day(d0);
507 
508 	    if (wd == 0 || wd == 6) {
509 		gretl_errmsg_sprintf(_("Invalid starting date for %d-day data"), dset->pd);
510 		return E_DATA;
511 	    }
512 	}
513 	dt = t_to_epoch_day(t, d0, dset->pd);
514     }
515 
516     err = ymd_bits_from_epoch_day(dt, &y, &m, &d);
517 
518     if (!err) {
519 	if (strlen(dset->stobs) == 8) {
520 	    sprintf(targ, YMD_WRITE_Y2_FMT, y % 100, m, d);
521 	} else {
522 	    sprintf(targ, YMD_WRITE_Y4_FMT, y, m, d);
523 	}
524     }
525 
526     return err;
527 }
528 
529 /**
530  * MS_excel_date_string:
531  * @targ: date string to be filled out.
532  * @mst: MS Excel-type date code: days since base.
533  * @pd: periodicity of data (or 0 if unknown).
534  * @d1904: set to 1 if the base is 1904/01/01; otherwise
535  * the base is assumed to be 1899/12/31.
536  *
537  * Writes to @targ the calendar representation of the date of
538  * observation @mst, in the form YYYY-MM-DD if @pd is 0, 5,
539  * 6, 7 or 52 (unknown, daily, or weekly frequency), otherwise
540  * in the appropriate format for annual, quarterly or monthly
541  * according to @pd.
542  *
543  * Returns: 0.
544  */
545 
MS_excel_date_string(char * targ,int mst,int pd,int d1904)546 int MS_excel_date_string (char *targ, int mst, int pd, int d1904)
547 {
548     int y = (d1904)? 1904 : 1900;
549     int d = (d1904)? 2 : 1;
550     int m = 1;
551     int leap, drem;
552 
553     *targ = '\0';
554 
555     if (mst == 0) {
556 	/* date coincident with base */
557 	if (d1904) {
558 	    d = 1;
559 	} else {
560 	    y = 1899;
561 	    m = 12;
562 	    d = 31;
563 	}
564     } else if (mst > 0) {
565 	/* date subsequent to base */
566 	drem = mst + d1904;
567 
568 	while (1) {
569 	    int yd = 365 + leap_year(y);
570 
571 	    /* MS nincompoopery */
572 	    if (y == 1900) yd++;
573 
574 	    if (drem > yd) {
575 		drem -= yd;
576 		y++;
577 	    } else {
578 		break;
579 	    }
580 	}
581 
582 	leap = leap_year(y) + (y == 1900);
583 
584 	for (m=1; m<=12; m++) {
585 	    int md = days_in_month[leap][m];
586 
587 	    if (drem > md) {
588 		drem -= md;
589 	    } else {
590 		d = drem;
591 		break;
592 	    }
593 	}
594     } else {
595 	/* mst < 0, date prior to base */
596 	drem = -(mst + d1904);
597 
598 	y = (d1904)? 1903 : 1899;
599 
600 	while (1) {
601 	    int yd = 365 + leap_year(y);
602 
603 	    if (drem > yd) {
604 		drem -= yd;
605 		y--;
606 	    } else {
607 		break;
608 	    }
609 	}
610 
611 	leap = leap_year(y);
612 
613 	for (m=12; m>0; m--) {
614 	    int md = days_in_month[leap][m];
615 
616 	    if (drem >= md) {
617 		drem -= md;
618 	    } else {
619 		d = md - drem;
620 		break;
621 	    }
622 	}
623     }
624 
625     if (pd == 1) {
626 	sprintf(targ, "%d", y);
627     } else if (pd == 12) {
628 	sprintf(targ, "%d:%02d", y, m);
629     } else if (pd == 4) {
630 	int q = 1 + m / 3.25;
631 
632 	sprintf(targ, "%d:%d", y, q);
633     } else {
634 	sprintf(targ, YMD_WRITE_Y4_FMT, y, m, d);
635     }
636 
637     return 0;
638 }
639 
640 /**
641  * get_dec_date:
642  * @datestr: calendar representation of date: YYYY-MM-DD.
643  *
644  * Returns: representation of date as year plus fraction of year.
645  */
646 
get_dec_date(const char * datestr)647 double get_dec_date (const char *datestr)
648 {
649     GDate date;
650     int y, m, d, nf;
651     int ydigits = 0;
652     double yrday, frac;
653 
654     nf = sscanf(datestr, YMD_READ_FMT, &y, &m, &d);
655 
656     if (nf == 3) {
657 	ydigits = strcspn(datestr, "-");
658     } else if (strchr(datestr, '/') != NULL) {
659 	/* backward compatibility */
660 	ydigits = strcspn(datestr, "/");
661 	nf = sscanf(datestr, "%d/%d/%d", &y, &m, &d);
662     }
663 
664     if (nf != 3 || (ydigits != 4 && ydigits != 2)) {
665 	return NADBL;
666     }
667 
668     if (ydigits == 2) {
669 	y = FOUR_DIGIT_YEAR(y);
670     }
671 
672     if (!g_date_valid_dmy(d, m, y)) {
673 	return NADBL;
674     }
675 
676     g_date_clear(&date, 1);
677     g_date_set_dmy(&date, d, m, y);
678     yrday = g_date_get_day_of_year(&date);
679     frac = yrday / (365 + g_date_is_leap_year(y));
680 
681     return y + frac;
682 }
683 
684 /**
685  * day_of_week:
686  * @y: year.
687  * @m: month, 1 to 12.
688  * @d: day in month, 1 to 31.
689  * @julian: non-zero to use Julian calendar, otherwise Gregorian.
690  * @err: location to receive error code.
691  *
692  * Returns: the day of the week for the supplied date
693  * (Sunday = 0, Monday = 1, ...) or %NADBL on failure
694  * (the date is invalid).
695  */
696 
day_of_week(int y,int m,int d,int julian,int * err)697 double day_of_week (int y, int m, int d, int julian, int *err)
698 {
699     int wd = day_of_week_from_ymd(y, m, d, julian);
700 
701     return wd < 0 ? NADBL : wd;
702 }
703 
704 #define day_in_calendar(w, d) (((w) == 6 && d != 0) || \
705 			       ((w) == 5 && d != 0 && d != 6))
706 
707 /**
708  * weekday_from_date:
709  * @datestr: calendar representation of date, [YY]YY/MM/DD
710  *
711  * Returns: day of week as integer, Sunday = 0.
712  */
713 
weekday_from_date(const char * datestr)714 int weekday_from_date (const char *datestr)
715 {
716     int y, m, d;
717     int ydigits;
718 
719     if (sscanf(datestr, YMD_READ_FMT, &y, &m, &d) != 3) {
720 	return -1;
721     }
722 
723     ydigits = strcspn(datestr, "-");
724     if (ydigits != 4 && ydigits != 2) {
725 	return -1;
726     }
727 
728     if (ydigits == 2) {
729 	y = FOUR_DIGIT_YEAR(y);
730     }
731 
732     return day_of_week_from_ymd(y, m, d, 0);
733 }
734 
735 /**
736  * day_starts_month:
737  * @d: day of month, 1-based
738  * @m: month number, 1-based
739  * @y: 4-digit year
740  * @wkdays: number of days in week (7, 6 or 5)
741  * @pad: location to receive 1 if the first day of the month
742  * can reasonably be padded by a missing value (Jan 1), or NULL.
743  *
744  * Returns: 1 if the day is the "first day of the month",
745  * allowance made for the possibility of a 5- or 6-day week,
746  * else 0.
747  */
748 
day_starts_month(int d,int m,int y,int wkdays,int * pad)749 int day_starts_month (int d, int m, int y, int wkdays, int *pad)
750 {
751     int ret = 0;
752 
753     if (wkdays == 7) {
754 	if (d == 1) {
755 	    ret = 1;
756 	} else if (pad != NULL && m == 1 && d == 2) {
757 	    /* second of January */
758 	    *pad = 1;
759 	    ret = 1;
760 	}
761     } else {
762 	/* 5- or 6-day week: check for first weekday or non-Sunday */
763 	int i, idx = day_of_week_from_ymd(y, m, 1, 0);
764 
765 	for (i=1; i<6; i++) {
766 	   if (day_in_calendar(wkdays, idx % 7)) {
767 	       break;
768 	   }
769 	   idx++;
770 	}
771 	if (d == i) {
772 	    ret = 1;
773 	} else if (pad != NULL && m == 1 && d == i + 1) {
774 	    /* January again */
775 	    *pad = 1;
776 	    ret = 1;
777 	}
778     }
779 
780     return ret;
781 }
782 
783 /**
784  * day_ends_month:
785  * @d: day of month, 1-based
786  * @m: month number, 1-based
787  * @y: 4-digit year
788  * @wkdays: number of days in week (7, 6 or 5)
789  *
790  * Returns: 1 if the day is the "last day of the month",
791  * allowance made for the possibility of a 5- or 6-day week, else 0.
792  */
793 
day_ends_month(int d,int m,int y,int wkdays)794 int day_ends_month (int d, int m, int y, int wkdays)
795 {
796     int ret = 0;
797     int leap = (m == 2)? leap_year(y) : 0;
798     int dm = days_in_month[leap][m];
799 
800     if (wkdays == 7) {
801 	ret = (d == dm);
802     } else {
803 	/* 5- or 6-day week: check for last weekday or non-Sunday */
804 	int i, idx = day_of_week_from_ymd(y, m, dm, 0);
805 
806 	for (i=dm; i>0; i--) {
807 	    if (day_in_calendar(wkdays, idx % 7)) {
808 		break;
809 	    }
810 	    idx--;
811 	}
812 	ret = (d == i);
813     }
814 
815     return ret;
816 }
817 
818 /**
819  * get_days_in_month:
820  * @m: month number, 1-based
821  * @y: 4-digit year
822  * @wkdays: number of days in week (7, 6 or 5)
823  * @julian: non-zero for Julian calendar, otherwise Gregorian.
824  *
825  * Returns: the number of (relevant) days in the month, allowance
826  * made for the possibility of a 5- or 6-day week.
827  */
828 
get_days_in_month(int m,int y,int wkdays,int julian)829 int get_days_in_month (int m, int y, int wkdays, int julian)
830 {
831     int dm, leap = 0;
832     int ret = 0;
833 
834     if (m == 2) {
835 	leap = julian ? (y%4 == 0) : leap_year(y);
836     }
837     dm = days_in_month[leap][m];
838 
839     if (wkdays == 7) {
840 	ret = dm;
841     } else {
842 	int i, idx = day_of_week_from_ymd(y, m, 1, julian);
843 
844 	for (i=0; i<dm; i++) {
845 	    if (day_in_calendar(wkdays, idx % 7)) {
846 		ret++;
847 	    }
848 	    idx++;
849 	}
850     }
851 
852     return ret;
853 }
854 
855 /**
856  * fill_monthlen_array:
857  * @mlen: array to be filled.
858  * @t1: starting index for fill.
859  * @t2: stopping index for fill.
860  * @wkdays: number of days in week (7, 6 or 5).
861  * @mo: month (ignored if @movec is non-NULL).
862  * @yr: year (ignored if @yrvec is non-NULL).
863  * @movec: array of month values (or NULL).
864  * @yrvec: array of year values (or NULL).
865  * @julian: non-zero for Julian calendar, otherwise Gregorian.
866  *
867  * Fills @mlen from @t1 to @t2 with the number of days in
868  * each month/year pair. It is assumed that at least one
869  * of @movec and @yrvec is non-NULL. The various arrays
870  * are doubles only because in context they will be
871  * dataset series; in concept they are integers.
872  *
873  * Returns: 0 on success, non-zero code on error.
874  */
875 
fill_monthlen_array(double * mlen,int t1,int t2,int wkdays,int mo,int yr,const double * movec,const double * yrvec,int julian)876 int fill_monthlen_array (double *mlen, int t1, int t2,
877 			 int wkdays, int mo, int yr,
878 			 const double *movec,
879 			 const double *yrvec,
880 			 int julian)
881 {
882     int t, err = 0;
883 
884     if (movec == NULL && yrvec == NULL) {
885 	return E_INVARG;
886     }
887 
888     for (t=t1; t<=t2; t++) {
889 	if (movec != NULL) {
890 	    mo = gretl_int_from_double(movec[t], &err);
891 	    if (err || mo < 1 || mo > 12) {
892 		err = E_INVARG;
893 		break;
894 	    }
895 	}
896 	if (yrvec != NULL) {
897 	    yr = gretl_int_from_double(yrvec[t], &err);
898 	    if (err) {
899 		break;
900 	    }
901 	    if (yr < 0) {
902 		yr = -yr;
903 		julian = 1;
904 	    } else {
905 		julian = 0;
906 	    }
907 	}
908 	mlen[t] = get_days_in_month(mo, yr, wkdays, julian);
909     }
910 
911     return err;
912 }
913 
914 /**
915  * month_day_index:
916  * @y: 4-digit year
917  * @m: month number, 1-based
918  * @d: day in month.
919  * @wkdays: number of days in week (7, 6 or 5)
920  *
921  * Returns: the 1-based index of calendar day @d in month
922  * @m of year @y, allowing for the possibility of a 5- or
923  * 6-day week.
924  */
925 
month_day_index(int y,int m,int d,int wkdays)926 int month_day_index (int y, int m, int d, int wkdays)
927 {
928     int ret = 0;
929 
930     if (wkdays == 7) {
931 	ret = d;
932     } else {
933 	int i, idx = day_of_week_from_ymd(y, m, 1, 0);
934 
935 	for (i=1; i<=d; i++) {
936 	    if (day_in_calendar(wkdays, idx)) {
937 		ret++;
938 	    }
939 	    idx = (idx == 6)? 0 : idx + 1;
940 	}
941     }
942 
943     return ret;
944 }
945 
946 /**
947  * days_in_month_before:
948  * @y: 4-digit year
949  * @m: month number, 1-based
950  * @d: day in month.
951  * @wkdays: number of days in week (7, 6 or 5)
952  *
953  * Returns: the number of relevant days in the month prior to
954  * the supplied date, allowing for the possibility of a 5- or
955  * 6-day week.
956  */
957 
days_in_month_before(int y,int m,int d,int wkdays)958 int days_in_month_before (int y, int m, int d, int wkdays)
959 {
960     int ret = 0;
961 
962     if (wkdays == 7) {
963 	ret = d - 1;
964     } else {
965 	int i, idx = day_of_week_from_ymd(y, m, 1, 0);
966 
967 	for (i=1; i<d; i++) {
968 	    if (day_in_calendar(wkdays, idx % 7)) {
969 		ret++;
970 	    }
971 	    idx++;
972 	}
973     }
974 
975     return ret;
976 }
977 
978 /**
979  * days_in_month_after:
980  * @y: 4-digit year
981  * @m: month number, 1-based
982  * @d: day in month.
983  * @wkdays: number of days in week (7, 6 or 5)
984  *
985  * Returns: the number of relevant days in the month after
986  * the supplied date, allowing for the possibility of a 5- or
987  * 6-day week.
988  */
989 
days_in_month_after(int y,int m,int d,int wkdays)990 int days_in_month_after (int y, int m, int d, int wkdays)
991 {
992     int leap = (m == 2)? leap_year(y) : 0;
993     int dm = days_in_month[leap][m];
994     int ret = 0;
995 
996     if (wkdays == 7) {
997 	ret = dm - d;
998     } else {
999 	int i, wd = day_of_week_from_ymd(y, m, dm, 0);
1000 
1001 	for (i=dm; i>d; i--) {
1002 	    if (day_in_calendar(wkdays, wd)) {
1003 		ret++;
1004 	    }
1005 	    if (wd > 0) {
1006 		wd--;
1007 	    } else {
1008 		wd = 6;
1009 	    }
1010 	}
1011     }
1012 
1013     return ret;
1014 }
1015 
1016 /**
1017  * date_to_daily_index:
1018  * @datestr: date in format YYYY-MM-DD.
1019  * @wkdays: number of days in week (7, 6 or 5)
1020  *
1021  * Returns: the zero-based index of the specified day
1022  * within the specified month and year. In the case
1023  * of 5- or 6-day data index zero does not necessarily
1024  * correspond to the first day of the month but rather
1025  * to the first relevant day.
1026  */
1027 
date_to_daily_index(const char * datestr,int wkdays)1028 int date_to_daily_index (const char *datestr, int wkdays)
1029 {
1030     int y, m, d, seq = 0;
1031 
1032     if (sscanf(datestr, YMD_READ_FMT, &y, &m, &d) != 3) {
1033 	return -1;
1034     }
1035 
1036     if (wkdays == 7) {
1037 	seq = d - 1;
1038     } else {
1039 	int leap = leap_year(y);
1040 	int n = days_in_month[leap][m];
1041 	int i, idx = day_of_week_from_ymd(y, m, 1, 0);
1042 
1043 	for (i=1; i<=n; i++) {
1044 	    if (d == i) {
1045 		break;
1046 	    }
1047 	    if (day_in_calendar(wkdays, idx % 7)) {
1048 		seq++;
1049 	    }
1050 	    idx++;
1051 	}
1052     }
1053 
1054     return seq;
1055 }
1056 
1057 /**
1058  * daily_index_to_date:
1059  * @targ: location to receive the date (YYYY-MM-DD).
1060  * @y: year.
1061  * @m: month.
1062  * @idx: zero-based index of day within month.
1063  * @wkdays: number of days in week (7, 6 or 5)
1064  *
1065  * Fills out @targ with the calendar data implied by
1066  * the specification of @y, @m, @seq and @wkdays,
1067  * provided this specification corresponds to an actual
1068  * calendar date.
1069 
1070  * Returns: 0 on successful completion, non-zero if
1071  * there is no such calendar date.
1072  */
1073 
daily_index_to_date(char * targ,int y,int m,int idx,int wkdays)1074 int daily_index_to_date (char *targ, int y, int m, int idx,
1075 			 int wkdays)
1076 {
1077     int day = 0;
1078 
1079     *targ = '\0';
1080 
1081     if (m < 1 || m > 12 || idx < 0 || idx > 30) {
1082 	fprintf(stderr, "daily_index_to_date: y=%d, m=%d, seq=%d\n",
1083 		y, m, idx);
1084 	return E_INVARG;
1085     }
1086 
1087     if (wkdays == 7) {
1088 	day = idx + 1;
1089     } else {
1090 	int leap = leap_year(y);
1091 	int n = days_in_month[leap][m];
1092 	int wd = day_of_week_from_ymd(y, m, 1, 0);
1093 	int i, seq = 0;
1094 
1095 	for (i=1; i<=n; i++) {
1096 	    if (day_in_calendar(wkdays, wd % 7)) {
1097 		if (seq == idx) {
1098 		    day = i;
1099 		    break;
1100 		}
1101 		seq++;
1102 	    }
1103 	    wd++;
1104 	}
1105     }
1106 
1107     if (day <= 0) {
1108 	return E_DATA;
1109     } else {
1110 	sprintf(targ, YMD_WRITE_FMT, y, m, day);
1111 	return 0;
1112     }
1113 }
1114 
1115 /**
1116  * n_hidden_missing_obs:
1117  * @dset: dataset information.
1118  * @t1: first observation.
1119  * @t2: last observation.
1120  *
1121  * For daily data with user-supplied data strings,
1122  * determine the number of "hidden" missing observations
1123  * in the range @t1 to @t2 inclusive. This is
1124  * the difference between the actual number of
1125  * observations and the number that should be there,
1126  * according to the calendar. Allowance is made for
1127  * 5- or 6-day data, via the data frequency given
1128  * in @dset.
1129  *
1130  * Returns: number of hidden observations.
1131  */
1132 
n_hidden_missing_obs(const DATASET * dset,int t1,int t2)1133 int n_hidden_missing_obs (const DATASET *dset, int t1, int t2)
1134 {
1135     int n_present = t2 - t1 + 1;
1136     int cal_n;
1137 
1138     if (!dated_daily_data(dset) || dset->S == NULL) {
1139 	return 0;
1140     }
1141 
1142     t1 = calendar_obs_number(dset->S[t1], dset);
1143     t2 = calendar_obs_number(dset->S[t2], dset);
1144 
1145     cal_n = t2 - t1 + 1;
1146 
1147     return cal_n - n_present;
1148 }
1149 
1150 /**
1151  * guess_daily_pd:
1152  * @dset: dataset information.
1153  *
1154  * Based on user-supplied daily date strings recorded in
1155  * @dset, try to guess whether the number of observations
1156  * per week is 5, 6 or 7 (given that some observations
1157  * may be missing).
1158  *
1159  * Returns: best quess at data frequency.
1160  */
1161 
guess_daily_pd(const DATASET * dset)1162 int guess_daily_pd (const DATASET *dset)
1163 {
1164     int t, wd, pd = 5;
1165     int wdbak = -1;
1166     int havesat = 0;
1167     int gotsat = 0, gotsun = 0;
1168     int contig = 0;
1169 
1170     wd = weekday_from_date(dset->S[0]);
1171     if (6 - wd < dset->n) {
1172 	havesat = 1;
1173     }
1174 
1175     for (t=0; t<dset->n && t<28; t++) {
1176 	wd = weekday_from_date(dset->S[t]);
1177 	if (wd == 0) {
1178 	    gotsun = 1;
1179 	} else if (wd == 6) {
1180 	    gotsat = 1;
1181 	}
1182 	if ((wdbak + 1) % 7 == wd) {
1183 	    contig++;
1184 	}
1185 	wdbak = wd;
1186     }
1187 
1188     if (gotsat && gotsun) {
1189 	pd = 7;
1190     } else if (contig > 10) {
1191 	if (gotsun) pd = 7;
1192 	else if (gotsat) pd = 6;
1193     } else if (dset->n > 7) {
1194 	if (!gotsun && !gotsat) {
1195 	    pd = 5;
1196 	} else if (!gotsun) {
1197 	    pd = 6;
1198 	}
1199     } else if (havesat && !gotsat) {
1200 	pd = 5;
1201     } else {
1202 	pd = 7;
1203     }
1204 
1205     return pd;
1206 }
1207 
1208 /**
1209  * iso_basic_to_extended:
1210  * @b: source array of YYYYMMDD values.
1211  * @y: array to hold year values.
1212  * @m: array to hold month values.
1213  * @d: array to hold day-of-week values.
1214  * @n: length of all the above arrays.
1215  *
1216  * Given the array @b of ISO 8601 "basic" daily dates (YYYYMMDD as
1217  * doubles), fill out the arrays @y, @m and @d with year, month
1218  * and day.
1219  *
1220  * Returns: 0.
1221  */
1222 
iso_basic_to_extended(const double * b,double * y,double * m,double * d,int n)1223 int iso_basic_to_extended (const double *b, double *y, double *m,
1224 			   double *d, int n)
1225 {
1226     int bi, yi, mi, di;
1227     int i, julian;
1228 
1229     for (i=0; i<n; i++) {
1230 	julian = 0;
1231 	if (na(b[i])) {
1232 	    y[i] = m[i] = NADBL;
1233 	    if (d != NULL) {
1234 		d[i] = NADBL;
1235 	    }
1236 	} else {
1237 	    bi = (int) b[i];
1238 	    if (bi < 0) {
1239 		julian = 1;
1240 		bi = -bi;
1241 	    }
1242 	    yi = bi / 10000;
1243 	    mi = (bi - 10000*yi) / 100;
1244 	    di = bi - 10000*yi - 100*mi;
1245 	    /* now check for legit date */
1246 	    if (!valid_ymd(yi, mi, di, julian)) {
1247 		y[i] = m[i] = NADBL;
1248 		if (d != NULL) {
1249 		    d[i] = NADBL;
1250 		}
1251 	    } else {
1252 		y[i] = yi;
1253 		m[i] = mi;
1254 		if (d != NULL) {
1255 		    d[i] = di;
1256 		}
1257 	    }
1258 	}
1259     }
1260 
1261     return 0;
1262 }
1263 
1264 /**
1265  * easterdate:
1266  * @year: year for which we want Easter date (Gregorian).
1267  *
1268  * Algorithm taken from Wikipedia page
1269  * https://en.wikipedia.org/wiki/Computus
1270  * under the heading "Anonymous Gregorian algorithm".
1271  *
1272  * Returns: the date of Easter in the Gregorian calendar as
1273  * (month + day/100). Note that April the 10th is, under
1274  * this convention, 4.1; hence, 4.2 is April the 20th, not
1275  * April the 2nd (which would be 4.02).
1276  */
1277 
easterdate(int year)1278 double easterdate (int year)
1279 {
1280     int a = year % 19;
1281     int b = year / 100;
1282     int c = year % 100;
1283     int d = b / 4;
1284     int e = b % 4;
1285     int f = (b + 8) / 25;
1286     int g = (b - f + 1) / 3;
1287     int h = (19 * a + b - d - g + 15) % 30;
1288     int i = c / 4;
1289     int k = c % 4;
1290     int L = (32 + 2 * e + 2 * i - h - k) % 7;
1291     int m = (a + 11 * h + 22 * L) / 451 ;
1292 
1293     int month = (h + L - 7 * m + 114) / 31;
1294     int day = ((h + L - 7 * m + 114) % 31) + 1;
1295 
1296     return month + day * 0.01;
1297 }
1298 
1299 /**
1300  * dayspan:
1301  * @ed1: first epoch day.
1302  * @ed2: last epoch day.
1303  * @wkdays: relevant days per week (5, 6 or 7).
1304  * @err: location to receive error code.
1305  *
1306  * Returns: The number of days in the interval @ed1 to
1307  * @ed2, inclusive, taking account of the number of daily
1308  * observations per week, @wkdays. If @wkdays = 6 Sundays
1309  * are disregarded; if @wkdays = 5 both Saturdays and
1310  * Sundays are disregarded.
1311  */
1312 
day_span(guint32 ed1,guint32 ed2,int wkdays,int * err)1313 int day_span (guint32 ed1, guint32 ed2, int wkdays, int *err)
1314 {
1315     int n = 0;
1316 
1317     if (!g_date_valid_julian(ed1) ||
1318 	!g_date_valid_julian(ed2) ||
1319 	ed2 < ed1) {
1320 	*err = E_INVARG;
1321     } else if (wkdays == 7) {
1322 	/* simple! */
1323 	n = ed2 - ed1 + 1;
1324     } else {
1325 	GDate date;
1326 	guint32 i;
1327 	int idx;
1328 
1329 	g_date_clear(&date, 1);
1330 	g_date_set_julian(&date, ed1);
1331 	idx = g_date_get_weekday(&date) - 1;
1332 
1333 	for (i=ed1; i<=ed2; i++) {
1334 	    if (day_in_calendar(wkdays, idx % 7)) {
1335 		n++;
1336 	    }
1337 	    idx++;
1338 	}
1339     }
1340 
1341     return n;
1342 }
1343 
1344 /**
1345  * iso_week_number:
1346  * @y: year.
1347  * @m: month (1 to 12).
1348  * @d: day on month.
1349  * @err: location to receive error code.
1350  *
1351  * Returns: The ISO 8601 week number (1 to 53) corresponding
1352  * to the Gregorian date specified by the first 3 arguments,
1353  * or -1 on invalid input.
1354  */
1355 
iso_week_number(int y,int m,int d,int * err)1356 int iso_week_number (int y, int m, int d, int *err)
1357 {
1358     GDate date;
1359     int wnum = -1;
1360 
1361     if (!g_date_valid_dmy(d, m, y)) {
1362 	*err = E_INVARG;
1363     } else {
1364 	g_date_clear(&date, 1);
1365 	g_date_set_dmy(&date, d, m, y);
1366 	wnum = g_date_get_iso8601_week_of_year(&date);
1367     }
1368 
1369     return wnum;
1370 }
1371 
1372 /**
1373  * iso_week_from_date:
1374  * @datestr: date in ISO 8601 format, YYYY-MM-DD.
1375  *
1376  * Returns: The ISO 8601 week number (1 to 53) corresponding
1377  * to the Gregorian date specified by @datestr, or -1 on
1378  * invalid input.
1379  */
1380 
iso_week_from_date(const char * datestr)1381 int iso_week_from_date (const char *datestr)
1382 {
1383     int y, m, d;
1384     int ydigits;
1385     int err = 0;
1386 
1387     if (sscanf(datestr, YMD_READ_FMT, &y, &m, &d) != 3) {
1388 	return -1;
1389     }
1390 
1391     ydigits = strcspn(datestr, "-");
1392 
1393     if (ydigits != 4 && ydigits != 2) {
1394 	return -1;
1395     } else if (ydigits == 2) {
1396 	y = FOUR_DIGIT_YEAR(y);
1397     }
1398 
1399     return iso_week_number(y, m, d, &err);
1400 }
1401