1 /*
2  * $Id: calendar.i,v 1.1 2005-12-30 21:50:55 dhmunro Exp $
3  * date.i
4  * functions for computing time of day, day of year, etc.
5  */
6 /* Copyright (c) 2005, The Regents of the University of California.
7  * All rights reserved.
8  * This file is part of yorick (http://yorick.sourceforge.net).
9  * Read the accompanying LICENSE file for details.
10  */
11 
12 /*
13  * The Julian calendar comprises a 4 year cycle of 1461 days
14  * -- three years of 365 days followed by a year of 366 days.
15  * The Gregorian calendar of modern times reduced the number of
16  * days in a century from 36525 to 36524 for three centuries out
17  * of every four, thus having a cycle of 146097 days every 400
18  * years, with a mean year of 365.2425 days.
19  *
20  * See wikipedia.org/wiki/Mean_tropical_year for a discussion of
21  * the accuracy of this convention; the rate of change of earth's
22  * orbital parameters and the gradual lengthening of the day
23  * invalidate the concept of any calendric periodicity at about
24  * this level.  (The mean tropical year is currently 365.2422 days
25  * while the time between vernal equinoxes is 365.2424 days, for
26  * example.)
27  *
28  * The wikipedia.org/wiki/Gregorian_calendar article describes the
29  * history of the Western calendar.  In summary:
30  * The final day of the Julian calendar was 1582 Oct 4, which was
31  * followed by 1582 Oct 15, by papal decree.  The Julian calendar,
32  * however, was used in many non-Catholic countries long after this
33  * date (and even in some Catholic countries until 1587).  In particular,
34  * 1752 Sep 2 was followed by 1752 Sep 14 in the British Empire.  Russia
35  * did not switch until 1918.  To confuse dates prior to about 1750 even
36  * further, the date on which the year number changed varied; 25 Dec,
37  * 25 Mar, and Easter were all popular choices.
38  *
39  * Months in our calendar, like weeks, have become totally arbitrary
40  * divisions.  While you will easily find the 146097 and 1461 day
41  * cycles in the following algorithms, the 153 day "cycle" associated
42  * with the months has no physical basis or meaning at all; it is
43  * merely taking advantage of a lucky accident of the pattern of
44  * the number of days in the month.  To explain this, notice first
45  * that the natural place to begin the year, from a purely computational
46  * point of view, is 1 March, because it is most convenient to put
47  * February, the only month with a variable number of days, last, so
48  * that as the number of days in a year oscillates between 365 and 366,
49  * the number of days in the final month can simply oscillate.  With
50  * that ordering of the months, their lengths are:
51  *   Mar Apr May Jun Jul   Aug Sep Oct Nov Dec   Jan Feb
52  *   31  30  31  30  31    31  30  31  30  31    31 (28 or 29)
53  * A very convenient way to describe this is as the first 2.4 cycles
54  * of a 5 month pattern 31-30-31-30-31 of 153 days.  (There are many
55  * other ways to generate this same pattern; this one happens to be
56  * relatively easy to explain or excuse.  If the pattern had been only
57  * slightly more irregular, no simple formula would have existed.)
58  */
59 
60 /* Julian Days (JD)
61  * = astronomical day count based on exactly 365.25 days per Julian year
62  *
63  * J2000 astronomical epoch is 2000 January 1.5 (noon GMT on Jan 1, 2000)
64  * (the instant it becomes 2000 January 1 everywhere on earth)
65  * = 2451545.0 JD
66  *
67  * UNIX time
68  * = seconds since 1970 January 1 00:00:00 UTC = 2440587.5 JD
69  * = 2^31 at 2038 January 19 03:14:08 UTC = 2465442.634815 JD
70  * = 946728000 at J2000
71  */
72 
73 func calendar(&y, &m, &d, n, julian=, noabbrev=)
74 /* DOCUMENT jdn = calendar(y, m, d)
75  *       or calendar, y, m, d, jdn
76  *   returns the Julian Day Number, JDN, associated with year Y,
77  *   month M (1=Jan, 2=Feb, ..., 12=Dec) and day of month D in the
78  *   first form.  In the second form, JDN is the input, and Y, M,
79  *   and D are outputs.
80  *
81  *   The Julian Day Number is a day count used by astronomers, which
82  *   is by definition independent of any calendar; each passing day
83  *   increments the count by one.  The zero day of the count falls a
84  *   bit before 4700 BC, for esoteric reasons (see Wikipedia).
85  *
86  *   Julian Day Number modulo 7 (JDN%7) is the day of the week, with
87  *   0=Mon, 1=Tue, ..., 6=Sun.
88  *
89  *   By default, Y, M, and D are in the modern Gregorian calendar.
90  *   However, with the julian=1 keyword in either form, calendar
91  *   treats Y, M, and D as the date in the Julian calendar, used
92  *   in Roman Catholic countries before 1582, in the British
93  *   empire before 1752, and in Eastern Orthodox countries well
94  *   into the twentieth century (again, see Wikipedia).
95  *
96  *   In the first form (when computing JDN), if Y<50, calendar
97  *   assumes 2000+Y, and if 50<=Y<100, calendar assumes 1900+Y,
98  *   unless the noabbrev=1 keyword is present, in which case it
99  *   assumes you are doing ill-advised archeological work.  In
100  *   the second form, the output Y will always include the century.
101  *   The julian=1 keyword implies noabbrev=1.
102  *
103  * SEE ALSO: unix_time, julian_day, datestamp
104  */
105 {
106   if (is_void(n)) {
107     yy = long(y);
108     mm = long(m);
109     dd = long(d);
110     janfeb = (mm < 3);
111     if (!noabbrev && !julian) yy += (yy<100)*1900 + (yy<50)*100;
112     yy += 4800 - janfeb;
113     mm -= 3 - janfeb*12;
114     if (julian) dd -= 32083;
115     else dd += yy/400 - yy/100 - 32045;
116     return 1461*yy/4 + (153*mm + 2)/5 + dd;
117 
118   } else {
119     n = long(n);
120     if (julian) {
121       n += 32082;
122     } else {
123       n += 32044;
124       c = (4*n + 3)/146097;
125       n += (3*c + 3)/4;
126     }
127     y = (4*n + 3)/1461;
128     d = n - 1461*y/4;
129     m = (5*d+2)/153;
130     d -= (153*m + 2)/5 - 1;
131     janfeb = (m > 9);
132     y += janfeb - 4800;
133     m += 3 - janfeb*12;
134   }
135 }
136 
137 func unix_time(&y, &m, &d, &h, t, loc=, now=)
138 /* DOCUMENT t = unix_time(y, m, d, h)
139  *       or unix_time, y, m, d, h, t
140  *       or t = unix_time(now=1)
141  *   converts numeric year Y, month M, day D, and hour H into the
142  *   corresponding "UNIX time" T, the number of seconds since
143  *   1970 Jan 1 00:00:00 UTC = 2440587.5 JD.
144  *   The hour H can be a floating point number with a fractional part.
145  *   In the second form, returns Y, M, D, and H, given T.
146  *   In the third form, returns T for the current time.
147  *   This requires calculating your time zone, as noted below.
148  *
149  *   By default, the input or returned Y, M, D, H represent Greenwich
150  *   time (UT).  (The UNIX time is, by definition, in the Greenwich
151  *   time zone.)  However, with the loc=1 keyword in the first two
152  *   forms, Y, M, D, H are the local time.
153  *
154  *   The loc=1 or now=1 keywords require the time zone.  If it has not
155  *   been previously set, the tz_set function is invoked to set it.
156  *
157  * SEE ALSO: julian_day, calendar, datestamp, base60, tz_set
158  */
159 {
160   /* zero for 1970 Jan 1 0000 UT */
161   if (now) {
162     if (is_void(tz_offset)) tz_set;
163     ts = timestamp(t);
164     if (!is_void(t) && t>=0) return t;
165     datestamp, y, m, d, h, ts;
166     return unix_time(y, m, d, h) + tz_offset;
167   }
168   if (loc && is_void(tz_offset)) tz_set;
169   if (is_void(t)) {
170     hh = is_void(h)? 0.0 : h;
171     t = (calendar(y,m,d)-2440588)*86400 + long(3600.0*hh+0.5);
172     if (loc) t += tz_offset;
173   } else {
174     if (loc) t -= tz_offset;
175     calendar, y, m, d, (t / 86400) + 2440588;
176     h = (t % 86400) / 3600.0;
177   }
178   return t;
179 }
180 
181 func julian_day(&y, &m, &d, &h, jd, loc=, now=)
182 /* DOCUMENT jd = julian_day(y, m, d, h)
183  *       or julian_day, y, m, d, h, jd
184  *       or jd = julian_day(now=1)
185  *   converts numeric year Y, month M, day D, and hour H into the
186  *   corresponding Julian Day, which includes the fractional part
187  *   of the day, measured from noon (not midnight) GMT.
188  *   The hour H can be a floating point number with a fractional part.
189  *   In the second form, returns Y, M, D, and H, given JD.
190  *   In the third form, returns JD for the current time.
191  *   This requires calculating your time zone, as noted below.
192  *
193  *   By default, the input or returned Y, M, D, H represent Greenwich
194  *   time (UT).  (The UNIX time is, by definition, in the Greenwich
195  *   time zone.)  However, with the loc=1 keyword in the first two
196  *   forms, Y, M, D, H are the local time.
197  *
198  *   The loc=1 or now=1 keywords require the time zone.  If it has not
199  *   been previously set, the tz_set function is invoked to set it.
200  *
201  * SEE ALSO: unix_time, calendar, datestamp, base60, tz_set
202  */
203 {
204   if (now) {
205     t = unix_time(now=1);
206     return (t / 86400.0) + 2440587.5;
207   }
208   if (loc && is_void(tz_offset)) tz_set;
209   if (is_void(jd)) {
210     jd = calendar(y,m,d) + (is_void(h)? 0.0 : (h-12.0)/24.0);
211     if (loc) jd += tz_offset/86400.0;
212   } else {
213     if (loc) jd -= tz_offset/86400.0;
214     h = long(jd + 0.5);
215     calendar, y, m, d, h;
216     h = (jd + 0.5 - h) * 24.0;
217   }
218   return jd;
219 }
220 
221 func base60(&h, &m, &s, dec)
222 /* DOCUMENT dec = base60(h, m, s)
223  *       or base60, h, m, s, dec
224  *   converts hours (or degrees) H, minutes M, and seconds S into
225  *   decimal hours (or degrees) DEC, in the first form.  In the
226  *   second form, DEC is the input, and H, M, and S are the outputs
227  *   (H and M are whole numbers of type double in that case).
228  * SEE ALSO: unix_time, julian_day, datestamp, base60
229  */
230 {
231   if (is_void(dec)) {
232     return h + m/60.0 + s/3600.0;
233   } else {
234     h = floor(dec);
235     s = 60.0*(dec-h);
236     m = floor(s);
237     s = 60.0*(s-m);
238   }
239 }
240 
241 func datestamp(&y, &m, &d, &h, ts)
242 /* DOCUMENT ts = datestamp(y, m, d, h)
243  *       or datestamp, y, m, d, h, ts
244  *       or ts = datestamp(y, m, d, h, 1)
245  *   converts numeric year Y, month M, day D, and hour H into the 25
246  *   character string format TS returned by the timestamp() function.
247  *   The hour H can be a floating point number witha fractional part.
248  *   In the second form, returns Y, M, D, and H, given TS.
249  *   In the third form, returns Y, M, D, H, and TS for the current
250  *   local time, as returned by the timestamp() function.
251  * SEE ALSO: timestamp, unix_time, julian_day, base60, calendar
252  */
253 {
254   if (!is_void(ts) && structof(ts)!=string)
255     ts = timestamp();
256   if (is_void(ts)) {
257     hh = long(h);
258     s = 60.0*(h-hh);
259     mm = long(s);
260     s = long(60.*(s-mm));
261     dow = _day_names(calendar(y,m,d)%7 + 1);
262     mon = _month_names(m);
263     y += (y<100)*1900 + (y<50)*100;
264     ts = swrite(format="%s %s %2ld %02ld:%02ld:%02ld %04ld",
265                 dow, mon, d, hh,mm,s, y);
266   } else {
267     dow = mon = "";
268     d = h = mm = s = y = 0;
269     sread, ts, format="%s %s %ld %ld:%ld:%ld %ld", dow, mon, d, h, mm, s, y;
270     m = where(mon == _month_names)(1);
271     h += (60*mm + s)/3600.0;
272   }
273   return ts;
274 }
275 
276 _month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
277                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
278 _day_names = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
279 
tz_set(offset)280 func tz_set(offset)
281 /* DOCUMENT tz_set
282  *       or tz_set, offset
283  *   sets the tz_offset global variable, which is the difference
284  *   between local time and Greenwich mean time in seconds.  In the
285  *   second form, you specify the offset directly.  In the first form,
286  *   tz_set attempts to compute it, either from the UNIX time returned
287  *   by the timestamp function, or from the system date utility if
288  *   that is not available.  If neither of these attempts works,
289  *   sets tz_offset to zero.
290  * SEE ALSO: unix_time, julian_day
291  */
292 {
293   extern tz_offset;
294   if (!is_void(offset)) {
295     tz_offset = long(offset);
296     return;
297   }
298   local ut, y, m, d, h;
299   datestamp, y, m, d, h, timestamp(ut);
300   t = unix_time(y,m,d,h, loc=0);
301   if (is_void(ut) || ut<0) {
302     uh = mm = ss = -1;
303     sread, rdline(popen(tz_date_cmd, 0)), format="%ld %ld %ld", uh, mm, ss;
304     if (ss < 0) {
305       /* just hope we're in Greenwich */
306       tz_offset = 0;
307       return;
308     }
309     uh += (mm*60+ss)/3600.0;
310     /* round time difference to nearest hour */
311     dh = long(floor(uh - h + 24.5));
312   } else {
313     /* round time difference to nearest hour */
314     dh = (ut - t + 88200)/3600;
315   }
316   tz_offset = (dh%24)*3600;
317 }
318 
319 /* command line to retrieve UT from system date utility */
320 tz_date_cmd = "date -u '+%H %M %S'";
321