calc: Time Zones

 
 8.5.4 Time Zones
 ----------------
 
 Time zones and daylight saving time are a complicated business.  The
 conversions to and from Julian and Unix-style dates automatically
 compute the correct time zone and daylight saving 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 saving 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-saving 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 saving
 time: using Calc’s default daylight saving time rule (see the
 explanation below), ‘julian(<12am may 1 1991>) - julian(<12am apr 1
 1991>)’ evaluates to ‘29.95833’ (that’s 29 days and 23 hours) because
 one hour was lost when daylight saving 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 saving 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 Saving
 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 saving 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 saving time, and a third for “generalized” time in
 which the daylight saving 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 saving adjustment.
        ( "PGT" 8 "PST" "PDT" ) )   ; Generalized time zone.
 
    With no arguments, ‘calc-time-zone’ or ‘tzone()’ will by default get
 the time zone and daylight saving information from the calendar (See
 Calendar/Diary (emacs)Daylight Saving.).  To use a different time zone,
 or if the calendar does not give the desired result, you can set the
 Calc variable ‘TimeZone’ (which is by default ‘nil’) to an appropriate
 time zone name.  (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.)  If the time zone given by ‘TimeZone’ is a generalized
 time zone, e.g., ‘EGT’, Calc examines the date being converted to tell
 whether to use standard or daylight saving time.  But if the current
 time zone is explicit, e.g., ‘EST’ or ‘EDT’, then that adjustment is
 used exactly and Calc’s daylight saving algorithm is not consulted.  The
 special time zone name ‘local’ is equivalent to no argument; i.e., it
 uses the information obtained from the calendar.
 
    The ‘t J’ and ‘t U’ commands with no numeric prefix arguments do the
 same thing as ‘tzone()’; namely, use the information from the calendar
 if ‘TimeZone’ is ‘nil’, otherwise use the time zone given by ‘TimeZone’.
 
    When Calc computes the daylight saving information itself (i.e., when
 the ‘TimeZone’ variable is set), it will by default consider daylight
 saving time to begin at 2 a.m. on the second Sunday of March (for years
 from 2007 on) or on the last Sunday in April (for years before 2007),
 and to end at 2 a.m. on the first Sunday of November.  (for years from
 2007 on) or the last Sunday in October (for years before 2007).  These
 are the rules that have been in effect in much of North America since
 1966 and take into account the rule change that began in 2007.  If you
 are in a country that uses different rules for computing daylight saving
 time, you have two choices: Write your own daylight saving 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 saving 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 saving 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
 saving 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 saving 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
 saving 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 saving 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 saving time, the hour from 1 a.m. to 2 a.m.
 repeats itself; converting a date/time form that falls in this hour
 results in a time value for the first manifestation of that time (_not_
 the one that occurs one hour later).
 
    If ‘math-daylight-savings-hook’ is ‘nil’, then the daylight saving
 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 saving computation is applied to it as it appears.  If DATE is
 a numeric date value, it is adjusted for the daylight-saving version of
 ZONE before being given to the daylight saving hook.  This odd-sounding
 rule ensures that the daylight-saving 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 saving
 adjustment that is appropriate for DATE in time zone ZONE.  If ZONE is
 explicitly in or not in daylight saving 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.