/*
* $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'";
syntax highlighted by Code2HTML, v. 0.9.1