/* * $Id: calendar.i,v 1.1 2005/12/30 21:50:55 dhmunro Exp $ * date.i * functions for computing time of day, day of year, etc. */ /* Copyright (c) 2005, The Regents of the University of California. * All rights reserved. * This file is part of yorick (http://yorick.sourceforge.net). * Read the accompanying LICENSE file for details. */ /* * The Julian calendar comprises a 4 year cycle of 1461 days * -- three years of 365 days followed by a year of 366 days. * The Gregorian calendar of modern times reduced the number of * days in a century from 36525 to 36524 for three centuries out * of every four, thus having a cycle of 146097 days every 400 * years, with a mean year of 365.2425 days. * * See wikipedia.org/wiki/Mean_tropical_year for a discussion of * the accuracy of this convention; the rate of change of earth's * orbital parameters and the gradual lengthening of the day * invalidate the concept of any calendric periodicity at about * this level. (The mean tropical year is currently 365.2422 days * while the time between vernal equinoxes is 365.2424 days, for * example.) * * The wikipedia.org/wiki/Gregorian_calendar article describes the * history of the Western calendar. In summary: * The final day of the Julian calendar was 1582 Oct 4, which was * followed by 1582 Oct 15, by papal decree. The Julian calendar, * however, was used in many non-Catholic countries long after this * date (and even in some Catholic countries until 1587). In particular, * 1752 Sep 2 was followed by 1752 Sep 14 in the British Empire. Russia * did not switch until 1918. To confuse dates prior to about 1750 even * further, the date on which the year number changed varied; 25 Dec, * 25 Mar, and Easter were all popular choices. * * Months in our calendar, like weeks, have become totally arbitrary * divisions. While you will easily find the 146097 and 1461 day * cycles in the following algorithms, the 153 day "cycle" associated * with the months has no physical basis or meaning at all; it is * merely taking advantage of a lucky accident of the pattern of * the number of days in the month. To explain this, notice first * that the natural place to begin the year, from a purely computational * point of view, is 1 March, because it is most convenient to put * February, the only month with a variable number of days, last, so * that as the number of days in a year oscillates between 365 and 366, * the number of days in the final month can simply oscillate. With * that ordering of the months, their lengths are: * Mar Apr May Jun Jul Aug Sep Oct Nov Dec Jan Feb * 31 30 31 30 31 31 30 31 30 31 31 (28 or 29) * A very convenient way to describe this is as the first 2.4 cycles * of a 5 month pattern 31-30-31-30-31 of 153 days. (There are many * other ways to generate this same pattern; this one happens to be * relatively easy to explain or excuse. If the pattern had been only * slightly more irregular, no simple formula would have existed.) */ /* Julian Days (JD) * = astronomical day count based on exactly 365.25 days per Julian year * * J2000 astronomical epoch is 2000 January 1.5 (noon GMT on Jan 1, 2000) * (the instant it becomes 2000 January 1 everywhere on earth) * = 2451545.0 JD * * UNIX time * = seconds since 1970 January 1 00:00:00 UTC = 2440587.5 JD * = 2^31 at 2038 January 19 03:14:08 UTC = 2465442.634815 JD * = 946728000 at J2000 */ func calendar(&y, &m, &d, n, julian=, noabbrev=) /* DOCUMENT jdn = calendar(y, m, d) * or calendar, y, m, d, jdn * returns the Julian Day Number, JDN, associated with year Y, * month M (1=Jan, 2=Feb, ..., 12=Dec) and day of month D in the * first form. In the second form, JDN is the input, and Y, M, * and D are outputs. * * The Julian Day Number is a day count used by astronomers, which * is by definition independent of any calendar; each passing day * increments the count by one. The zero day of the count falls a * bit before 4700 BC, for esoteric reasons (see Wikipedia). * * Julian Day Number modulo 7 (JDN%7) is the day of the week, with * 0=Mon, 1=Tue, ..., 6=Sun. * * By default, Y, M, and D are in the modern Gregorian calendar. * However, with the julian=1 keyword in either form, calendar * treats Y, M, and D as the date in the Julian calendar, used * in Roman Catholic countries before 1582, in the British * empire before 1752, and in Eastern Orthodox countries well * into the twentieth century (again, see Wikipedia). * * In the first form (when computing JDN), if Y<50, calendar * assumes 2000+Y, and if 50<=Y<100, calendar assumes 1900+Y, * unless the noabbrev=1 keyword is present, in which case it * assumes you are doing ill-advised archeological work. In * the second form, the output Y will always include the century. * The julian=1 keyword implies noabbrev=1. * * SEE ALSO: unix_time, julian_day, datestamp */ { if (is_void(n)) { yy = long(y); mm = long(m); dd = long(d); janfeb = (mm < 3); if (!noabbrev && !julian) yy += (yy<100)*1900 + (yy<50)*100; yy += 4800 - janfeb; mm -= 3 - janfeb*12; if (julian) dd -= 32083; else dd += yy/400 - yy/100 - 32045; return 1461*yy/4 + (153*mm + 2)/5 + dd; } else { n = long(n); if (julian) { n += 32082; } else { n += 32044; c = (4*n + 3)/146097; n += (3*c + 3)/4; } y = (4*n + 3)/1461; d = n - 1461*y/4; m = (5*d+2)/153; d -= (153*m + 2)/5 - 1; janfeb = (m > 9); y += janfeb - 4800; m += 3 - janfeb*12; } } func unix_time(&y, &m, &d, &h, t, loc=, now=) /* DOCUMENT t = unix_time(y, m, d, h) * or unix_time, y, m, d, h, t * or t = unix_time(now=1) * converts numeric year Y, month M, day D, and hour H into the * corresponding "UNIX time" T, the number of seconds since * 1970 Jan 1 00:00:00 UTC = 2440587.5 JD. * The hour H can be a floating point number with a fractional part. * In the second form, returns Y, M, D, and H, given T. * In the third form, returns T for the current time. * This requires calculating your time zone, as noted below. * * By default, the input or returned Y, M, D, H represent Greenwich * time (UT). (The UNIX time is, by definition, in the Greenwich * time zone.) However, with the loc=1 keyword in the first two * forms, Y, M, D, H are the local time. * * The loc=1 or now=1 keywords require the time zone. If it has not * been previously set, the tz_set function is invoked to set it. * * SEE ALSO: julian_day, calendar, datestamp, base60, tz_set */ { /* zero for 1970 Jan 1 0000 UT */ if (now) { if (is_void(tz_offset)) tz_set; ts = timestamp(t); if (!is_void(t) && t>=0) return t; datestamp, y, m, d, h, ts; return unix_time(y, m, d, h) + tz_offset; } if (loc && is_void(tz_offset)) tz_set; if (is_void(t)) { hh = is_void(h)? 0.0 : h; t = (calendar(y,m,d)-2440588)*86400 + long(3600.0*hh+0.5); if (loc) t += tz_offset; } else { if (loc) t -= tz_offset; calendar, y, m, d, (t / 86400) + 2440588; h = (t % 86400) / 3600.0; } return t; } func julian_day(&y, &m, &d, &h, jd, loc=, now=) /* DOCUMENT jd = julian_day(y, m, d, h) * or julian_day, y, m, d, h, jd * or jd = julian_day(now=1) * converts numeric year Y, month M, day D, and hour H into the * corresponding Julian Day, which includes the fractional part * of the day, measured from noon (not midnight) GMT. * The hour H can be a floating point number with a fractional part. * In the second form, returns Y, M, D, and H, given JD. * In the third form, returns JD for the current time. * This requires calculating your time zone, as noted below. * * By default, the input or returned Y, M, D, H represent Greenwich * time (UT). (The UNIX time is, by definition, in the Greenwich * time zone.) However, with the loc=1 keyword in the first two * forms, Y, M, D, H are the local time. * * The loc=1 or now=1 keywords require the time zone. If it has not * been previously set, the tz_set function is invoked to set it. * * SEE ALSO: unix_time, calendar, datestamp, base60, tz_set */ { if (now) { t = unix_time(now=1); return (t / 86400.0) + 2440587.5; } if (loc && is_void(tz_offset)) tz_set; if (is_void(jd)) { jd = calendar(y,m,d) + (is_void(h)? 0.0 : (h-12.0)/24.0); if (loc) jd += tz_offset/86400.0; } else { if (loc) jd -= tz_offset/86400.0; h = long(jd + 0.5); calendar, y, m, d, h; h = (jd + 0.5 - h) * 24.0; } return jd; } func base60(&h, &m, &s, dec) /* DOCUMENT dec = base60(h, m, s) * or base60, h, m, s, dec * converts hours (or degrees) H, minutes M, and seconds S into * decimal hours (or degrees) DEC, in the first form. In the * second form, DEC is the input, and H, M, and S are the outputs * (H and M are whole numbers of type double in that case). * SEE ALSO: unix_time, julian_day, datestamp, base60 */ { if (is_void(dec)) { return h + m/60.0 + s/3600.0; } else { h = floor(dec); s = 60.0*(dec-h); m = floor(s); s = 60.0*(s-m); } } func datestamp(&y, &m, &d, &h, ts) /* DOCUMENT ts = datestamp(y, m, d, h) * or datestamp, y, m, d, h, ts * or ts = datestamp(y, m, d, h, 1) * converts numeric year Y, month M, day D, and hour H into the 25 * character string format TS returned by the timestamp() function. * The hour H can be a floating point number witha fractional part. * In the second form, returns Y, M, D, and H, given TS. * In the third form, returns Y, M, D, H, and TS for the current * local time, as returned by the timestamp() function. * SEE ALSO: timestamp, unix_time, julian_day, base60, calendar */ { if (!is_void(ts) && structof(ts)!=string) ts = timestamp(); if (is_void(ts)) { hh = long(h); s = 60.0*(h-hh); mm = long(s); s = long(60.*(s-mm)); dow = _day_names(calendar(y,m,d)%7 + 1); mon = _month_names(m); y += (y<100)*1900 + (y<50)*100; ts = swrite(format="%s %s %2ld %02ld:%02ld:%02ld %04ld", dow, mon, d, hh,mm,s, y); } else { dow = mon = ""; d = h = mm = s = y = 0; sread, ts, format="%s %s %ld %ld:%ld:%ld %ld", dow, mon, d, h, mm, s, y; m = where(mon == _month_names)(1); h += (60*mm + s)/3600.0; } return ts; } _month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; _day_names = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; func tz_set(offset) /* DOCUMENT tz_set * or tz_set, offset * sets the tz_offset global variable, which is the difference * between local time and Greenwich mean time in seconds. In the * second form, you specify the offset directly. In the first form, * tz_set attempts to compute it, either from the UNIX time returned * by the timestamp function, or from the system date utility if * that is not available. If neither of these attempts works, * sets tz_offset to zero. * SEE ALSO: unix_time, julian_day */ { extern tz_offset; if (!is_void(offset)) { tz_offset = long(offset); return; } local ut, y, m, d, h; datestamp, y, m, d, h, timestamp(ut); t = unix_time(y,m,d,h, loc=0); if (is_void(ut) || ut<0) { uh = mm = ss = -1; sread, rdline(popen(tz_date_cmd, 0)), format="%ld %ld %ld", uh, mm, ss; if (ss < 0) { /* just hope we're in Greenwich */ tz_offset = 0; return; } uh += (mm*60+ss)/3600.0; /* round time difference to nearest hour */ dh = long(floor(uh - h + 24.5)); } else { /* round time difference to nearest hour */ dh = (ut - t + 88200)/3600; } tz_offset = (dh%24)*3600; } /* command line to retrieve UT from system date utility */ tz_date_cmd = "date -u '+%H %M %S'";