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