Time zones and daylight savings time are a complicated business. The conversions to and from Julian and Unix-style dates automatically compute the correct time zone and daylight savings adjustment to use, provided they can figure out this information. This section describes Calc's time zone adjustment algorithm in detail, in case you want to do conversions in different time zones or in case Calc's algorithms can't determine the right correction to use.
Adjustments for time zones and daylight savings time are done by t U, t J, t N, and t C, but not by any other commands. In particular, `<may 1 1991> - <apr 1 1991>' evaluates to exactly 30 days even though there is a daylight-savings transition in between. This is also true for Julian pure dates: `julian(<may 1 1991>) - julian(<apr 1 1991>)'. But Julian and Unix date/times will adjust for daylight savings time: `julian(<12am may 1 1991>) - julian(<12am apr 1 1991>)' evaluates to `29.95834' (that's 29 days and 23 hours) because one hour was lost when daylight savings commenced on April 7, 1991.
In brief, the idiom `julian(date1) - julian(date2)' computes the actual number of 24-hour periods between two dates, whereas `date1 - date2' computes the number of calendar days between two dates without taking daylight savings into account.
The calc-time-zone
[tzone
] command converts the time
zone specified by its numeric prefix argument into a number of
seconds difference from Greenwich mean time (GMT). If the argument
is a number, the result is simply that value multiplied by 3600.
Typical arguments for North America are 5 (Eastern) or 8 (Pacific). If
Daylight Savings time is in effect, one hour should be subtracted from
the normal difference.
If you give a prefix of plain C-u, calc-time-zone
(like other
date arithmetic commands that include a time zone argument) takes the
zone argument from the top of the stack. (In the case of t J
and t U, the normal argument is then taken from the second-to-top
stack position.) This allows you to give a non-integer time zone
adjustment. The time-zone argument can also be an HMS form, or
it can be a variable which is a time zone name in upper- or lower-case.
For example `tzone(PST) = tzone(8)' and `tzone(pdt) = tzone(7)'
(for Pacific standard and daylight savings times, respectively).
North American and European time zone names are defined as follows; note that for each time zone there is one name for standard time, another for daylight savings time, and a third for "generalized" time in which the daylight savings adjustment is computed from context.
YST PST MST CST EST AST NST GMT WET MET MEZ 9 8 7 6 5 4 3.5 0 -1 -2 -2 YDT PDT MDT CDT EDT ADT NDT BST WETDST METDST MESZ 8 7 6 5 4 3 2.5 -1 -2 -3 -3 YGT PGT MGT CGT EGT AGT NGT BGT WEGT MEGT MEGZ 9/8 8/7 7/6 6/5 5/4 4/3 3.5/2.5 0/-1 -1/-2 -2/-3 -2/-3
To define time zone names that do not appear in the above table,
you must modify the Lisp variable math-tzone-names
. This
is a list of lists describing the different time zone names; its
structure is best explained by an example. The three entries for
Pacific Time look like this:
( ( "PST" 8 0 ) ; Name as an upper-case string, then standard ( "PDT" 8 -1 ) ; adjustment, then daylight savings adjustment. ( "PGT" 8 "PST" "PDT" ) ) ; Generalized time zone.
With no arguments, calc-time-zone
or `tzone()' obtains an
argument from the Calc variable TimeZone
if a value has been
stored for that variable. If not, Calc runs the Unix `date'
command and looks for one of the above time zone names in the output;
if this does not succeed, `tzone()' leaves itself unevaluated.
The time zone name in the `date' output may be followed by a signed
adjustment, e.g., `GMT+5' or `GMT+0500' which specifies a
number of hours and minutes to be added to the base time zone.
Calc stores the time zone it finds into TimeZone
to speed
later calls to `tzone()'.
The special time zone name local
is equivalent to no argument,
i.e., it uses the local time zone as obtained from the date
command.
If the time zone name found is one of the standard or daylight
savings zone names from the above table, and Calc's internal
daylight savings algorithm says that time and zone are consistent
(e.g., PDT
accompanies a date that Calc's algorithm would also
consider to be daylight savings, or PST
accompanies a date
that Calc would consider to be standard time), then Calc substitutes
the corresponding generalized time zone (like PGT
).
If your system does not have a suitable `date' command, you
may wish to put a `(setq var-TimeZone ...)' in your Emacs
initialization file to set the time zone. The easiest way to do
this is to edit the TimeZone
variable using Calc's s T
command, then use the s p (calc-permanent-variable
)
command to save the value of TimeZone
permanently.
The t J and t U
commands with no numeric prefix
arguments do the same thing as `tzone()'. If the current
time zone is a generalized time zone, e.g., EGT
, Calc
examines the date being converted to tell whether to use standard
or daylight savings time. But if the current time zone is explicit,
e.g., EST
or EDT
, then that adjustment is used exactly
and Calc's daylight savings algorithm is not consulted.
Some places don't follow the usual rules for daylight savings time.
The state of Arizona, for example, does not observe daylight savings
time. If you run Calc during the winter season in Arizona, the
Unix date
command will report MST
time zone, which
Calc will change to MGT
. If you then convert a time that
lies in the summer months, Calc will apply an incorrect daylight
savings time adjustment. To avoid this, set your TimeZone
variable explicitly to MST
to force the use of standard,
non-daylight-savings time.
By default Calc always considers daylight savings time to begin at
2 a.m. on the first Sunday of April, and to end at 2 a.m. on the
last Sunday of October. This is the rule that has been in effect
in North America since 1987. If you are in a country that uses
different rules for computing daylight savings time, you have two
choices: Write your own daylight savings hook, or control time
zones explicitly by setting the TimeZone
variable and/or
always giving a time-zone argument for the conversion functions.
The Lisp variable math-daylight-savings-hook
holds the
name of a function that is used to compute the daylight savings
adjustment for a given date. The default is
math-std-daylight-savings
, which computes an adjustment
(either 0 or -1) using the North American rules given above.
The daylight savings hook function is called with four arguments:
The date, as a floating-point number in standard Calc format;
a six-element list of the date decomposed into year, month, day,
hour, minute, and second, respectively; a string which contains
the generalized time zone name in upper-case, e.g., "WEGT"
;
and a special adjustment to be applied to the hour value when
converting into a generalized time zone (see below).
The Lisp function math-prev-weekday-in-month
is useful for
daylight savings computations. This is an internal version of
the user-level pwday
function described in the previous
section. It takes four arguments: The floating-point date value,
the corresponding six-element date list, the day-of-month number,
and the weekday number (0-6).
The default daylight savings hook ignores the time zone name, but a more sophisticated hook could use different algorithms for different time zones. It would also be possible to use different algorithms depending on the year number, but the default hook always uses the algorithm for 1987 and later. Here is a listing of the default daylight savings hook:
(defun math-std-daylight-savings (date dt zone bump) (cond ((< (nth 1 dt) 4) 0) ((= (nth 1 dt) 4) (let ((sunday (math-prev-weekday-in-month date dt 7 0))) (cond ((< (nth 2 dt) sunday) 0) ((= (nth 2 dt) sunday) (if (>= (nth 3 dt) (+ 3 bump)) -1 0)) (t -1)))) ((< (nth 1 dt) 10) -1) ((= (nth 1 dt) 10) (let ((sunday (math-prev-weekday-in-month date dt 31 0))) (cond ((< (nth 2 dt) sunday) -1) ((= (nth 2 dt) sunday) (if (>= (nth 3 dt) (+ 2 bump)) 0 -1)) (t 0)))) (t 0)) )
The bump
parameter is equal to zero when Calc is converting
from a date form in a generalized time zone into a GMT date value.
It is -1 when Calc is converting in the other direction. The
adjustments shown above ensure that the conversion behaves correctly
and reasonably around the 2 a.m. transition in each direction.
There is a "missing" hour between 2 a.m. and 3 a.m. at the beginning of daylight savings time; converting a date/time form that falls in this hour results in a time value for the following hour, from 3 a.m. to 4 a.m. At the end of daylight savings time, the hour from 1 a.m. to 2 a.m. repeats itself; converting a date/time form that falls in in this hour results in a time value for the first manifestion of that time (not the one that occurs one hour later).
If math-daylight-savings-hook
is nil
, then the
daylight savings adjustment is always taken to be zero.
In algebraic formulas, `tzone(zone, date)' computes the time zone adjustment for a given zone name at a given date. The date is ignored unless zone is a generalized time zone. If date is a date form, the daylight savings computation is applied to it as it appears. If date is a numeric date value, it is adjusted for the daylight-savings version of zone before being given to the daylight savings hook. This odd-sounding rule ensures that the daylight-savings computation is always done in local time, not in the GMT time that a numeric date is typically represented in.
The `dsadj(date, zone)' function computes the
daylight savings adjustment that is appropriate for date in
time zone zone. If zone is explicitly in or not in
daylight savings time (e.g., PDT
or PST
) the
date is ignored. If zone is a generalized time zone,
the algorithms described above are used. If zone is omitted,
the computation is done for the current time zone.
See section Reporting Bugs, for the address of Calc's author, if you
should wish to contribute your improved versions of
math-tzone-names
and math-daylight-savings-hook
to the Calc distribution.