-;;; cal-dst.el --- calendar functions for daylight savings rules.
+;;; cal-dst.el --- calendar functions for daylight savings rules
-;; Copyright (C) 1993 Free Software Foundation, Inc.
+;; Copyright (C) 1993, 1994, 1995, 1996, 2001, 2002, 2003, 2004, 2005,
+;; 2006 Free Software Foundation, Inc.
;; Author: Paul Eggert <eggert@twinsun.com>
;; Edward M. Reingold <reingold@cs.uiuc.edu>
+;; Maintainer: Glenn Morris <rgm@gnu.org>
;; Keywords: calendar
;; Human-Keywords: daylight savings time, calendar, diary, holidays
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING. If not, write to
-;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;; Code:
(require 'calendar)
+(require 'cal-persia)
+
+(defcustom calendar-dst-check-each-year-flag t
+ "Non-nil means to check each year for DST transitions as needed.
+nil means to assume the next two transitions found after the
+current date apply to all years. This is faster, but not always
+correct, since the dates of Daylight Saving transitions sometimes
+change."
+ :type 'boolean
+ :version "22.1"
+ :group 'calendar)
(defvar calendar-current-time-zone-cache nil
"Cache for result of calendar-current-time-zone.")
(defun calendar-time-from-absolute (abs-date s)
"Time of absolute date ABS-DATE, S seconds after midnight.
-Returns the pair (HIGH . LOW) where HIGH and LOW are the high and low
+Returns the list (HIGH LOW) where HIGH and LOW are the high and low
16 bits, respectively, of the number of seconds 1970-01-01 00:00:00 UTC,
ignoring leap seconds, that is the equivalent moment to S seconds after
midnight UTC on absolute date ABS-DATE."
(let* ((a (- abs-date calendar-system-time-basis))
(u (+ (* 163 (mod a 512)) (floor s 128))))
;; Overflow is a terrible thing!
- (cons
+ (list
;; floor((60*60*24*a + s) / 2^16)
(+ a (* 163 (floor a 512)) (floor u 512))
;; (60*60*24*a + s) mod 2^16
(defun calendar-time-zone-daylight-rules (abs-date utc-diff)
"Return daylight transition rule for ABS-DATE, UTC-DIFF sec offset from UTC.
-ABS-DIFF must specify a day that contains a daylight savings transition.
+ABS-DATE must specify a day that contains a daylight savings transition.
The result has the proper form for calendar-daylight-savings-starts'."
(let* ((date (calendar-gregorian-from-absolute abs-date))
(weekday (% abs-date 7))
(cons
(list 'calendar-nth-named-day 1 weekday m 'year j)
l)))
- l)))
+ l)
+ ;; 01-01 and 07-01 for this year's Persian calendar.
+ (if (and (= m 3) (<= 20 d) (<= d 21))
+ '((calendar-gregorian-from-absolute
+ (calendar-absolute-from-persian
+ (list 1 1 (- year 621))))))
+ (if (and (= m 9) (<= 22 d) (<= d 23))
+ '((calendar-gregorian-from-absolute
+ (calendar-absolute-from-persian
+ (list 7 1 (- year 621))))))))
(prevday-sec (- -1 utc-diff)) ;; last sec of previous local day
(year (1+ y)))
;; Scan through the next few years until only one rule remains.
(cdr candidate-rules)))
(car candidate-rules)))
+;; TODO it might be better to extract this information directly from
+;; the system timezone database. But cross-platform...?
+;; See thread
+;; http://lists.gnu.org/archive/html/emacs-pretest-bug/2006-11/msg00060.html
+(defun calendar-dst-find-data (&optional time)
+ "Find data on the first Daylight Saving Time transitions after TIME.
+TIME defaults to `current-time'. Return value is as described
+for `calendar-current-time-zone'."
+ (let* ((t0 (or time (current-time)))
+ (t0-zone (current-time-zone t0))
+ (t0-utc-diff (car t0-zone))
+ (t0-name (car (cdr t0-zone))))
+ (if (not t0-utc-diff)
+ ;; Little or no time zone information is available.
+ (list nil nil t0-name t0-name nil nil nil nil)
+ (let* ((t1 (calendar-next-time-zone-transition t0))
+ (t2 (and t1 (calendar-next-time-zone-transition t1))))
+ (if (not t2)
+ ;; This locale does not have daylight savings time.
+ (list (/ t0-utc-diff 60) 0 t0-name t0-name nil nil 0 0)
+ ;; Use heuristics to find daylight savings parameters.
+ (let* ((t1-zone (current-time-zone t1))
+ (t1-utc-diff (car t1-zone))
+ (t1-name (car (cdr t1-zone)))
+ (t1-date-sec (calendar-absolute-from-time t1 t0-utc-diff))
+ (t2-date-sec (calendar-absolute-from-time t2 t1-utc-diff))
+ ;; TODO When calendar-dst-check-each-year-flag is non-nil,
+ ;; the rules can be simpler than they currently are.
+ (t1-rules (calendar-time-zone-daylight-rules
+ (car t1-date-sec) t0-utc-diff))
+ (t2-rules (calendar-time-zone-daylight-rules
+ (car t2-date-sec) t1-utc-diff))
+ (t1-time (/ (cdr t1-date-sec) 60))
+ (t2-time (/ (cdr t2-date-sec) 60)))
+ (cons
+ (/ (min t0-utc-diff t1-utc-diff) 60)
+ (cons
+ (/ (abs (- t0-utc-diff t1-utc-diff)) 60)
+ (if (< t0-utc-diff t1-utc-diff)
+ (list t0-name t1-name t1-rules t2-rules t1-time t2-time)
+ (list t1-name t0-name t2-rules t1-rules t2-time t1-time)
+ )))))))))
+
+(defvar calendar-dst-transition-cache nil
+ "Internal cal-dst variable storing date of Daylight Saving Time transitions.
+Value is a list with elements of the form (YEAR START END), where
+START and END are expressions that when evaluated return the
+start and end dates (respectively) for DST in YEAR. Used by the
+function `calendar-dst-find-startend'.")
+
+(defun calendar-dst-find-startend (year)
+ "Find the dates in YEAR on which Daylight Saving Time starts and ends.
+Returns a list (YEAR START END), where START and END are
+expressions that when evaluated return the start and end dates,
+respectively. This function first attempts to use pre-calculated
+data from `calendar-dst-transition-cache', otherwise it calls
+`calendar-dst-find-data' (and adds the results to the cache)."
+ (let ((e (assoc year calendar-dst-transition-cache))
+ f)
+ (or e
+ (progn
+ (setq e (calendar-dst-find-data (encode-time 1 0 0 1 1 year))
+ f (nth 4 e)
+ e (list year f (nth 5 e))
+ calendar-dst-transition-cache
+ (append calendar-dst-transition-cache (list e)))
+ e))))
+
(defun calendar-current-time-zone ()
"Return UTC difference, dst offset, names and rules for current time zone.
Some operating systems cannot provide all this information to Emacs; in this
case, `calendar-current-time-zone' returns a list containing nil for the data
it can't find."
- (or
- calendar-current-time-zone-cache
- (setq
- calendar-current-time-zone-cache
- (let* ((t0 (current-time))
- (t0-zone (current-time-zone t0))
- (t0-utc-diff (car t0-zone))
- (t0-name (car (cdr t0-zone))))
- (if (not t0-utc-diff)
- ;; Little or no time zone information is available.
- (list nil nil t0-name t0-name nil nil nil nil)
- (let* ((t1 (calendar-next-time-zone-transition t0))
- (t2 (and t1 (calendar-next-time-zone-transition t1))))
- (if (not t2)
- ;; This locale does not have daylight savings time.
- (list (/ t0-utc-diff 60) 0 t0-name t0-name nil nil 0 0)
- ;; Use heuristics to find daylight savings parameters.
- (let* ((t1-zone (current-time-zone t1))
- (t1-utc-diff (car t1-zone))
- (t1-name (car (cdr t1-zone)))
- (t1-date-sec (calendar-absolute-from-time t1 t0-utc-diff))
- (t2-date-sec (calendar-absolute-from-time t2 t1-utc-diff))
- (t1-rules (calendar-time-zone-daylight-rules
- (car t1-date-sec) t0-utc-diff))
- (t2-rules (calendar-time-zone-daylight-rules
- (car t2-date-sec) t1-utc-diff))
- (t1-time (/ (cdr t1-date-sec) 60))
- (t2-time (/ (cdr t2-date-sec) 60)))
- (cons
- (/ (min t0-utc-diff t1-utc-diff) 60)
- (cons
- (/ (abs (- t0-utc-diff t1-utc-diff)) 60)
- (if (< t0-utc-diff t1-utc-diff)
- (list t0-name t1-name t1-rules t2-rules t2-time t1-time)
- (list t1-name t0-name t2-rules t1-rules t1-time t2-time)
- )))))))))))
-
-;;; The following six defvars relating to daylight savings time should NOT be
+ (unless calendar-current-time-zone-cache
+ (setq calendar-current-time-zone-cache (calendar-dst-find-data))))
+
+;;; The following eight defvars relating to daylight savings time should NOT be
;;; marked to go into loaddefs.el where they would be evaluated when Emacs is
;;; dumped. These variables' appropriate values depend on the conditions under
;;; which the code is INVOKED; so it's inappropriate to initialize them when
(defvar calendar-daylight-time-offset
(or (car (cdr calendar-current-time-zone-cache)) 60)
"*Number of minutes difference between daylight savings and standard time.
-
+
If the locale never uses daylight savings time, set this to 0.")
(defvar calendar-standard-time-zone-name
(or (car (nthcdr 3 calendar-current-time-zone-cache)) "EDT")
"*Abbreviated name of daylight-savings time zone at `calendar-location-name'.
For example, \"EDT\" in New York City, \"PDT\" for Los Angeles.")
-
+
+
+(defun calendar-dst-starts (year)
+ "Return the date of YEAR on which Daylight Saving Time starts.
+This function respects the value of `calendar-dst-check-each-year-flag'."
+ (or (let ((expr (if calendar-dst-check-each-year-flag
+ (cadr (calendar-dst-find-startend year))
+ (nth 4 calendar-current-time-zone-cache))))
+ (if expr (eval expr)))
+ (and (not (zerop calendar-daylight-time-offset))
+ (calendar-nth-named-day 1 0 4 year))))
+
+(defun calendar-dst-ends (year)
+ "Return the date of YEAR on which Daylight Saving Time ends.
+This function respects the value of `calendar-dst-check-each-year-flag'."
+ (or (let ((expr (if calendar-dst-check-each-year-flag
+ (nth 2 (calendar-dst-find-startend year))
+ (nth 5 calendar-current-time-zone-cache))))
+ (if expr (eval expr)))
+ (and (not (zerop calendar-daylight-time-offset))
+ (calendar-nth-named-day -1 0 10 year))))
+
+
;;;###autoload
(put 'calendar-daylight-savings-starts 'risky-local-variable t)
(defvar calendar-daylight-savings-starts
- (or (car (nthcdr 4 calendar-current-time-zone-cache))
- (and (not (zerop calendar-daylight-time-offset))
- '(calendar-nth-named-day 1 0 4 year)))
+ '(calendar-dst-starts year)
"*Sexp giving the date on which daylight savings time starts.
This is an expression in the variable `year' whose value gives the Gregorian
date in the form (month day year) on which daylight savings time starts. It is
If the locale never uses daylight savings time, set this to nil.")
;;;###autoload
-(put 'calendar-daylight-savings-starts 'risky-local-variable t)
+(put 'calendar-daylight-savings-ends 'risky-local-variable t)
(defvar calendar-daylight-savings-ends
- (or (car (nthcdr 5 calendar-current-time-zone-cache))
- (and (not (zerop calendar-daylight-time-offset))
- '(calendar-nth-named-day -1 0 10 year)))
+ '(calendar-dst-ends year)
"*Sexp giving the date on which daylight savings time ends.
This is an expression in the variable `year' whose value gives the Gregorian
date in the form (month day year) on which daylight savings time ends. It is
'(calendar-nth-named-day -1 0 10 year)
If the locale never uses daylight savings time, set this to nil.")
-
+
(defvar calendar-daylight-savings-starts-time
(or (car (nthcdr 6 calendar-current-time-zone-cache)) 120)
"*Number of minutes after midnight that daylight savings time starts.")
-
+
(defvar calendar-daylight-savings-ends-time
(or (car (nthcdr 7 calendar-current-time-zone-cache))
calendar-daylight-savings-starts-time)
"*Number of minutes after midnight that daylight savings time ends.")
+(defun dst-in-effect (date)
+ "True if on absolute DATE daylight savings time is in effect.
+Fractional part of DATE is local standard time of day."
+ (let* ((year (extract-calendar-year
+ (calendar-gregorian-from-absolute (floor date))))
+ (dst-starts-gregorian (eval calendar-daylight-savings-starts))
+ (dst-ends-gregorian (eval calendar-daylight-savings-ends))
+ (dst-starts (and dst-starts-gregorian
+ (+ (calendar-absolute-from-gregorian
+ dst-starts-gregorian)
+ (/ calendar-daylight-savings-starts-time
+ 60.0 24.0))))
+ (dst-ends (and dst-ends-gregorian
+ (+ (calendar-absolute-from-gregorian
+ dst-ends-gregorian)
+ (/ (- calendar-daylight-savings-ends-time
+ calendar-daylight-time-offset)
+ 60.0 24.0)))))
+ (and dst-starts dst-ends
+ (if (< dst-starts dst-ends)
+ (and (<= dst-starts date) (< date dst-ends))
+ (or (<= dst-starts date) (< date dst-ends))))))
+
+(defun dst-adjust-time (date time &optional style)
+ "Adjust, to account for dst on DATE, decimal fraction standard TIME.
+Returns a list (date adj-time zone) where `date' and `adj-time' are the values
+adjusted for `zone'; here `date' is a list (month day year), `adj-time' is a
+decimal fraction time, and `zone' is a string.
+
+Optional parameter STYLE forces the result time to be standard time when its
+value is 'standard and daylight savings time (if available) when its value is
+'daylight.
+
+Conversion to daylight savings time is done according to
+`calendar-daylight-savings-starts', `calendar-daylight-savings-ends',
+`calendar-daylight-savings-starts-time',
+`calendar-daylight-savings-ends-time', and
+`calendar-daylight-savings-offset'."
+
+ (let* ((rounded-abs-date (+ (calendar-absolute-from-gregorian date)
+ (/ (round (* 60 time)) 60.0 24.0)))
+ (dst (dst-in-effect rounded-abs-date))
+ (time-zone (if dst
+ calendar-daylight-time-zone-name
+ calendar-standard-time-zone-name))
+ (time (+ rounded-abs-date
+ (if dst (/ calendar-daylight-time-offset 24.0 60.0) 0))))
+ (list (calendar-gregorian-from-absolute (truncate time))
+ (* 24.0 (- time (truncate time)))
+ time-zone)))
+
(provide 'cal-dst)
+;;; arch-tag: a141d204-213c-4ca5-bdc6-f9df3aa92aad
;;; cal-dst.el ends here