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