calendar: Use sensible error reporting in calendar_gregorian_to_offset().
[pspp] / src / data / calendar.c
1 #include <config.h>
2 #include "calendar.h"
3 #include <assert.h>
4 #include <stdbool.h>
5 #include <data/settings.h>
6 #include <data/val-type.h>
7
8 #include "gettext.h"
9 #define _(msgid) gettext (msgid)
10
11 /* 14 Oct 1582. */
12 #define EPOCH (-577734)
13
14 /* Calculates and returns floor(a/b) for integer b > 0. */
15 static int
16 floor_div (int a, int b)
17 {
18   assert (b > 0);
19   return (a >= 0 ? a : a - b + 1) / b;
20 }
21
22 /* Calculates floor(a/b) and the corresponding remainder and
23    stores them into *Q and *R. */
24 static void
25 floor_divmod (int a, int b, int *q, int *r)
26 {
27   *q = floor_div (a, b);
28   *r = a - b * *q;
29 }
30
31 /* Returns true if Y is a leap year, false otherwise. */
32 static bool
33 is_leap_year (int y)
34 {
35   return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
36 }
37
38 static int
39 raw_gregorian_to_offset (int y, int m, int d)
40 {
41   return (EPOCH - 1
42           + 365 * (y - 1)
43           + floor_div (y - 1, 4)
44           - floor_div (y - 1, 100)
45           + floor_div (y - 1, 400)
46           + floor_div (367 * m - 362, 12)
47           + (m <= 2 ? 0 : (m >= 2 && is_leap_year (y) ? -1 : -2))
48           + d);
49 }
50
51 /* Returns the number of days from 14 Oct 1582 to (Y,M,D) in the
52    Gregorian calendar.  Returns SYSMIS for dates before 14 Oct
53    1582. */
54 double
55 calendar_gregorian_to_offset (int y, int m, int d, char **errorp)
56 {
57   /* Normalize year. */
58   if (y >= 0 && y < 100)
59     {
60       int epoch = settings_get_epoch ();
61       int century = epoch / 100 + (y < epoch % 100);
62       y += century * 100;
63     }
64
65   /* Normalize month. */
66   if (m < 1 || m > 12)
67     {
68       if (m == 0)
69         {
70           y--;
71           m = 12;
72         }
73       else if (m == 13)
74         {
75           y++;
76           m = 1;
77         }
78       else
79         {
80           if (errorp != NULL)
81             *errorp = xasprintf (_("Month %d is not in acceptable range of "
82                                    "0 to 13."), m);
83           return SYSMIS;
84         }
85     }
86
87   /* Normalize day. */
88   if (d < 0 || d > 31)
89     {
90       if (errorp != NULL)
91         *errorp = xasprintf (_("Day %d is not in acceptable range of "
92                                "0 to 31."), d);
93       return SYSMIS;
94     }
95
96   /* Validate date. */
97   if (y < 1582 || (y == 1582 && (m < 10 || (m == 10 && d < 15))))
98     {
99       if (errorp != NULL)
100         *errorp = xasprintf (_("Date %04d-%d-%d is before the earliest "
101                                "acceptable date of 1582-10-15."), y, m, d);
102       return SYSMIS;
103     }
104
105   /* Calculate offset. */
106   if (errorp != NULL)
107     *errorp = NULL;
108   return raw_gregorian_to_offset (y, m, d);
109 }
110
111 /* Returns the number of days in the given YEAR from January 1 up
112    to (but not including) the first day of MONTH. */
113 static int
114 cum_month_days (int year, int month)
115 {
116   static const int cum_month_days[12] =
117     {
118       0,
119       31, /* Jan */
120       31 + 28, /* Feb */
121       31 + 28 + 31, /* Mar */
122       31 + 28 + 31 + 30, /* Apr */
123       31 + 28 + 31 + 30 + 31, /* May */
124       31 + 28 + 31 + 30 + 31 + 30, /* Jun */
125       31 + 28 + 31 + 30 + 31 + 30 + 31, /* Jul */
126       31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, /* Aug */
127       31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, /* Sep */
128       31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, /* Oct */
129       31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, /* Nov */
130     };
131
132   assert (month >= 1 && month <= 12);
133   return cum_month_days[month - 1] + (month >= 3 && is_leap_year (year));
134 }
135
136 /* Takes a count of days from 14 Oct 1582 and returns the
137    Gregorian calendar year it is in.  Dates both before and after
138    the epoch are supported. */
139 int
140 calendar_offset_to_year (int ofs)
141 {
142   int d0;
143   int n400, d1;
144   int n100, d2;
145   int n4, d3;
146   int n1;
147   int y;
148
149   d0 = ofs - EPOCH;
150   floor_divmod (d0, 365 * 400 + 100 - 3, &n400, &d1);
151   floor_divmod (d1, 365 * 100 + 25 - 1, &n100, &d2);
152   floor_divmod (d2, 365 * 4 + 1, &n4, &d3);
153   n1 = floor_div (d3, 365);
154   y = 400 * n400 + 100 * n100 + 4 * n4 + n1;
155   if (n100 != 4 && n1 != 4)
156     y++;
157
158   return y;
159 }
160
161 /* Takes a count of days from 14 Oct 1582 and translates it into
162    a Gregorian calendar date in (*Y,*M,*D).  Also stores the
163    year-relative day number into *YD.  Dates both before and
164    after the epoch are supported. */
165 void
166 calendar_offset_to_gregorian (int ofs, int *y, int *m, int *d, int *yd)
167 {
168   int year = *y = calendar_offset_to_year (ofs);
169   int january1 = raw_gregorian_to_offset (year, 1, 1);
170   int yday = *yd = ofs - january1 + 1;
171   int march1 = january1 + cum_month_days (year, 3);
172   int correction = ofs < march1 ? 0 : (is_leap_year (year) ? 1 : 2);
173   int month = *m = (12 * (yday - 1 + correction) + 373) / 367;
174   *d = yday - cum_month_days (year, month);
175 }
176
177 /* Takes a count of days from 14 Oct 1582 and returns the 1-based
178    year-relative day number, that is, the number of days from the
179    beginning of the year. */
180 int
181 calendar_offset_to_yday (int ofs)
182 {
183   int year = calendar_offset_to_year (ofs);
184   int january1 = raw_gregorian_to_offset (year, 1, 1);
185   int yday = ofs - january1 + 1;
186   return yday;
187 }
188
189 /* Takes a count of days from 14 Oct 1582 and returns the
190    corresponding weekday 1...7, with 1=Sunday. */
191 int
192 calendar_offset_to_wday (int ofs)
193 {
194   int wday = (ofs - EPOCH + 1) % 7 + 1;
195   if (wday <= 0)
196     wday += 7;
197   return wday;
198 }
199
200 /* Takes a count of days from 14 Oct 1582 and returns the month
201    it is in. */
202 int
203 calendar_offset_to_month (int ofs)
204 {
205   int y, m, d, yd;
206   calendar_offset_to_gregorian (ofs, &y, &m, &d, &yd);
207   return m;
208 }
209
210 /* Takes a count of days from 14 Oct 1582 and returns the
211    corresponding day of the month. */
212 int
213 calendar_offset_to_mday (int ofs)
214 {
215   int y, m, d, yd;
216   calendar_offset_to_gregorian (ofs, &y, &m, &d, &yd);
217   return d;
218 }
219
220 /* Returns the number of days in the specified month. */
221 int
222 calendar_days_in_month (int y, int m)
223 {
224   static const int days_per_month[12]
225     = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
226
227   assert (m >= 1 && m <= 12);
228   return m == 2 && is_leap_year (y) ? 29 : days_per_month[m - 1];
229 }