better tests
[pspp] / src / data / calendar.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2006, 2007, 2008, 2010, 2011 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "data/calendar.h"
20
21 #include <assert.h>
22 #include <stdbool.h>
23
24 #include "data/settings.h"
25 #include "data/val-type.h"
26
27 #include "gettext.h"
28 #define _(msgid) gettext (msgid)
29
30 /* 14 Oct 1582. */
31 #define EPOCH (-577734)
32
33 /* Calculates and returns floor(a/b) for integer b > 0. */
34 static int
35 floor_div (int a, int b)
36 {
37   assert (b > 0);
38   return (a >= 0 ? a : a - b + 1) / b;
39 }
40
41 /* Calculates floor(a/b) and the corresponding remainder and
42    stores them into *Q and *R. */
43 static void
44 floor_divmod (int a, int b, int *q, int *r)
45 {
46   *q = floor_div (a, b);
47   *r = a - b * *q;
48 }
49
50 /* Returns true if Y is a leap year, false otherwise. */
51 static bool
52 is_leap_year (int y)
53 {
54   return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
55 }
56
57 int
58 calendar_raw_gregorian_to_offset (int y, int m, int d)
59 {
60   return (EPOCH - 1
61           + 365 * (y - 1)
62           + floor_div (y - 1, 4)
63           - floor_div (y - 1, 100)
64           + floor_div (y - 1, 400)
65           + floor_div (367 * m - 362, 12)
66           + (m <= 2 ? 0 : (m >= 2 && is_leap_year (y) ? -1 : -2))
67           + d);
68 }
69
70 int *
71 calendar_gregorian_adjust (int *y, int *m, int *d,
72                            const struct fmt_settings *settings)
73 {
74   /* Normalize year. */
75   if (*y >= 0 && *y < 100)
76     {
77       int epoch = fmt_settings_get_epoch (settings);
78       int century = epoch / 100 + (*y < epoch % 100);
79       *y += century * 100;
80     }
81
82   /* Normalize month. */
83   if (*m < 1 || *m > 12)
84     {
85       if (*m == 0)
86         {
87           *y -= 1;
88           *m = 12;
89         }
90       else if (*m == 13)
91         {
92           *y += 1;
93           *m = 1;
94         }
95       else
96         return m;
97     }
98
99   /* Normalize day. */
100   if (*d < 0 || *d > 31)
101     return d;
102
103   /* Validate date. */
104   if (*y < 1582 || (*y == 1582 && (*m < 10 || (*m == 10 && *d < 15))))
105     return y;
106
107   return NULL;
108 }
109
110 /* Returns the number of days from 14 Oct 1582 to (Y,M,D) in the
111    Gregorian calendar.  Returns SYSMIS for dates before 14 Oct
112    1582. */
113 double
114 calendar_gregorian_to_offset (int y, int m, int d,
115                               const struct fmt_settings *settings,
116                               char **errorp)
117 {
118   int *bad_value = calendar_gregorian_adjust (&y, &m, &d, settings);
119   if (!bad_value)
120     {
121       if (errorp)
122         *errorp = NULL;
123       return calendar_raw_gregorian_to_offset (y, m, d);
124     }
125   else
126     {
127       if (errorp)
128         {
129           if (bad_value == &y)
130             *errorp = xasprintf (_("Date %04d-%d-%d is before the earliest "
131                                    "supported date 1582-10-15."), y, m, d);
132           else if (bad_value == &m)
133             *errorp = xasprintf (_("Month %d is not in acceptable range of "
134                                    "0 to 13."), m);
135           else
136             *errorp = xasprintf (_("Day %d is not in acceptable range of "
137                                    "0 to 31."), d);
138         }
139       return SYSMIS;
140     }
141 }
142
143 /* Returns the number of days in the given YEAR from January 1 up
144    to (but not including) the first day of MONTH. */
145 static int
146 cum_month_days (int year, int month)
147 {
148   static const int cum_month_days[12] =
149     {
150       0,
151       31, /* Jan */
152       31 + 28, /* Feb */
153       31 + 28 + 31, /* Mar */
154       31 + 28 + 31 + 30, /* Apr */
155       31 + 28 + 31 + 30 + 31, /* May */
156       31 + 28 + 31 + 30 + 31 + 30, /* Jun */
157       31 + 28 + 31 + 30 + 31 + 30 + 31, /* Jul */
158       31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, /* Aug */
159       31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, /* Sep */
160       31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, /* Oct */
161       31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, /* Nov */
162     };
163
164   assert (month >= 1 && month <= 12);
165   return cum_month_days[month - 1] + (month >= 3 && is_leap_year (year));
166 }
167
168 /* Takes a count of days from 14 Oct 1582 and returns the
169    Gregorian calendar year it is in.  Dates both before and after
170    the epoch are supported. */
171 int
172 calendar_offset_to_year (int ofs)
173 {
174   int d0;
175   int n400, d1;
176   int n100, d2;
177   int n4, d3;
178   int n1;
179   int y;
180
181   d0 = ofs - EPOCH;
182   floor_divmod (d0, 365 * 400 + 100 - 3, &n400, &d1);
183   floor_divmod (d1, 365 * 100 + 25 - 1, &n100, &d2);
184   floor_divmod (d2, 365 * 4 + 1, &n4, &d3);
185   n1 = floor_div (d3, 365);
186   y = 400 * n400 + 100 * n100 + 4 * n4 + n1;
187   if (n100 != 4 && n1 != 4)
188     y++;
189
190   return y;
191 }
192
193 /* Takes a count of days from 14 Oct 1582 and translates it into
194    a Gregorian calendar date in (*Y,*M,*D).  Also stores the
195    year-relative day number into *YD.  Dates both before and
196    after the epoch are supported. */
197 void
198 calendar_offset_to_gregorian (int ofs, int *y, int *m, int *d, int *yd)
199 {
200   int year = *y = calendar_offset_to_year (ofs);
201   int january1 = calendar_raw_gregorian_to_offset (year, 1, 1);
202   int yday = *yd = ofs - january1 + 1;
203   int march1 = january1 + cum_month_days (year, 3);
204   int correction = ofs < march1 ? 0 : (is_leap_year (year) ? 1 : 2);
205   int month = *m = (12 * (yday - 1 + correction) + 373) / 367;
206   *d = yday - cum_month_days (year, month);
207 }
208
209 /* Takes a count of days from 14 Oct 1582 and returns the 1-based
210    year-relative day number, that is, the number of days from the
211    beginning of the year. */
212 int
213 calendar_offset_to_yday (int ofs)
214 {
215   int year = calendar_offset_to_year (ofs);
216   int january1 = calendar_raw_gregorian_to_offset (year, 1, 1);
217   int yday = ofs - january1 + 1;
218   return yday;
219 }
220
221 /* Takes a count of days from 14 Oct 1582 and returns the
222    corresponding weekday 1...7, with 1=Sunday. */
223 int
224 calendar_offset_to_wday (int ofs)
225 {
226   int wday = (ofs - EPOCH + 1) % 7 + 1;
227   if (wday <= 0)
228     wday += 7;
229   return wday;
230 }
231
232 /* Takes a count of days from 14 Oct 1582 and returns the month
233    it is in. */
234 int
235 calendar_offset_to_month (int ofs)
236 {
237   int y, m, d, yd;
238   calendar_offset_to_gregorian (ofs, &y, &m, &d, &yd);
239   return m;
240 }
241
242 /* Takes a count of days from 14 Oct 1582 and returns the
243    corresponding day of the month. */
244 int
245 calendar_offset_to_mday (int ofs)
246 {
247   int y, m, d, yd;
248   calendar_offset_to_gregorian (ofs, &y, &m, &d, &yd);
249   return d;
250 }
251
252 /* Returns the number of days in the specified month. */
253 int
254 calendar_days_in_month (int y, int m)
255 {
256   static const int days_per_month[12]
257     = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
258
259   assert (m >= 1 && m <= 12);
260   return m == 2 && is_leap_year (y) ? 29 : days_per_month[m - 1];
261 }