1;;; timezone.el --- time zone package for GNU Emacs
2
3;; Copyright (C) 1990, 1991, 1992, 1993, 1996, 1999, 2001, 2002, 2003,
4;;   2004, 2005, 2006, 2007 Free Software Foundation, Inc.
5
6;; Author: Masanobu Umeda
7;; Maintainer: umerin@mse.kyutech.ac.jp
8;; Keywords: news
9
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software; you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation; either version 2, or (at your option)
15;; any later version.
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs; see the file COPYING.  If not, write to the
24;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25;; Boston, MA 02110-1301, USA.
26
27;;; Commentary:
28
29;;; Code:
30
31(defvar timezone-world-timezones
32  '(("PST" .  -800)
33    ("PDT" .  -700)
34    ("MST" .  -700)
35    ("MDT" .  -600)
36    ("CST" .  -600)
37    ("CDT" .  -500)
38    ("EST" .  -500)
39    ("EDT" .  -400)
40    ("AST" .  -400)			;by <clamen@CS.CMU.EDU>
41    ("NST" .  -330)			;by <clamen@CS.CMU.EDU>
42    ("UT"  .  +000)
43    ("GMT" .  +000)
44    ("BST" .  +100)
45    ("MET" .  +100)
46    ("EET" .  +200)
47    ("JST" .  +900)
48    ("GMT+1"  .  +100) ("GMT+2"  .  +200) ("GMT+3"  .  +300)
49    ("GMT+4"  .  +400) ("GMT+5"  .  +500) ("GMT+6"  .  +600)
50    ("GMT+7"  .  +700) ("GMT+8"  .  +800) ("GMT+9"  .  +900)
51    ("GMT+10" . +1000) ("GMT+11" . +1100) ("GMT+12" . +1200) ("GMT+13" . +1300)
52    ("GMT-1"  .  -100) ("GMT-2"  .  -200) ("GMT-3"  .  -300)
53    ("GMT-4"  .  -400) ("GMT-5"  .  -500) ("GMT-6"  .  -600)
54    ("GMT-7"  .  -700) ("GMT-8"  .  -800) ("GMT-9"  .  -900)
55    ("GMT-10" . -1000) ("GMT-11" . -1100) ("GMT-12" . -1200))
56  "*Time differentials of timezone from GMT in +-HHMM form.
57This list is obsolescent, and is present only for backwards compatibility,
58because time zone names are ambiguous in practice.
59Use `current-time-zone' instead.")
60
61(defvar timezone-months-assoc
62  '(("JAN" .  1)("FEB" .  2)("MAR" .  3)
63    ("APR" .  4)("MAY" .  5)("JUN" .  6)
64    ("JUL" .  7)("AUG" .  8)("SEP" .  9)
65    ("OCT" . 10)("NOV" . 11)("DEC" . 12))
66  "Alist of first three letters of a month and its numerical representation.")
67
68(defun timezone-make-date-arpa-standard (date &optional local timezone)
69  "Convert DATE to an arpanet standard date.
70Optional 2nd argument LOCAL specifies the default local timezone of the DATE;
71if nil, GMT is assumed.
72Optional 3rd argument TIMEZONE specifies a time zone to be represented in;
73if nil, the local time zone is assumed."
74  (let ((new (timezone-fix-time date local timezone)))
75    (timezone-make-arpa-date (aref new 0) (aref new 1) (aref new 2)
76			     (timezone-make-time-string
77			      (aref new 3) (aref new 4) (aref new 5))
78			     (aref new 6))
79    ))
80
81(defun timezone-make-date-sortable (date &optional local timezone)
82  "Convert DATE to a sortable date string.
83Optional 2nd argument LOCAL specifies the default local timezone of the DATE;
84if nil, GMT is assumed.
85Optional 3rd argument TIMEZONE specifies a timezone to be represented in;
86if nil, the local time zone is assumed."
87  (let ((new (timezone-fix-time date local timezone)))
88    (timezone-make-sortable-date (aref new 0) (aref new 1) (aref new 2)
89				 (timezone-make-time-string
90				  (aref new 3) (aref new 4) (aref new 5)))
91    ))
92
93
94;;
95;; Parsers and Constructors of Date and Time
96;;
97
98(defun timezone-make-arpa-date (year month day time &optional timezone)
99  "Make arpanet standard date string from YEAR, MONTH, DAY, and TIME.
100Optional argument TIMEZONE specifies a time zone."
101  (let ((zone
102	 (if (listp timezone)
103	     (let* ((m (timezone-zone-to-minute timezone))
104		    (absm (if (< m 0) (- m) m)))
105	       (format "%c%02d%02d"
106		       (if (< m 0) ?- ?+) (/ absm 60) (% absm 60)))
107	   timezone)))
108    (format "%02d %s %04d %s %s"
109	    day
110	    (capitalize (car (rassq month timezone-months-assoc)))
111	    year
112	    time
113	    zone)))
114
115(defun timezone-make-sortable-date (year month day time)
116  "Make sortable date string from YEAR, MONTH, DAY, and TIME."
117  (format "%4d%02d%02d%s"
118	  year month day time))
119
120(defun timezone-make-time-string (hour minute second)
121  "Make time string from HOUR, MINUTE, and SECOND."
122  (format "%02d:%02d:%02d" hour minute second))
123
124(defun timezone-parse-date (date)
125  "Parse DATE and return a vector [YEAR MONTH DAY TIME TIMEZONE].
126Two-digit dates are `windowed'.  Those <69 have 2000 added; otherwise 1900
127is added.  Three-digit dates have 1900 added.
128TIMEZONE is nil for DATEs without a zone field.
129
130Understands the following styles:
131 (1) 14 Apr 89 03:20[:12] [GMT]
132 (2) Fri, 17 Mar 89 4:01[:33] [GMT]
133 (3) Mon Jan 16 16:12[:37] [GMT] 1989
134 (4) 6 May 1992 1641-JST (Wednesday)
135 (5) 22-AUG-1993 10:59:12.82
136 (6) Thu, 11 Apr 16:17:12 91 [MET]
137 (7) Mon, 6  Jul 16:47:20 T 1992 [MET]
138 (8) 1996-06-24 21:13:12 [GMT]
139 (9) 1996-06-24 21:13-ZONE"
140 ;; Get rid of any text properties.
141  (and (stringp date)
142       (or (text-properties-at 0 date)
143	   (next-property-change 0 date))
144       (setq date (copy-sequence date))
145       (set-text-properties 0 (length date) nil date))
146  (let ((date (or date ""))
147	(year nil)
148	(month nil)
149	(day nil)
150	(time nil)
151	(zone nil))			;This may be nil.
152    (cond ((string-match
153	    "\\([0-9]+\\)[ \t]+\\([^ \t,]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+:[0-9:]+\\)[ \t]+\\([-+a-zA-Z0-9]+\\)" date)
154	   ;; Styles: (1) and (2) with timezone and buggy timezone
155	   ;; This is most common in mail and news,
156	   ;; so it is worth trying first.
157	   (setq year 3 month 2 day 1 time 4 zone 5))
158	  ((string-match
159	    "\\([0-9]+\\)[ \t]+\\([^ \t,]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+:[0-9:]+\\)[ \t]*\\'" date)
160	   ;; Styles: (1) and (2) without timezone
161	   (setq year 3 month 2 day 1 time 4 zone nil))
162	  ((string-match
163	    "\\([^ \t,]+\\),[ \t]+\\([0-9]+\\)[ \t]+\\([^ \t,]+\\)[ \t]+\\([0-9]+:[0-9:]+\\)[ \t]+\\(T[ \t]+\\|\\)\\([0-9]+\\)[ \t]*\\'" date)
164	   ;; Styles: (6) and (7) without timezone
165	   (setq year 6 month 3 day 2 time 4 zone nil))
166	  ((string-match
167	    "\\([^ \t,]+\\),[ \t]+\\([0-9]+\\)[ \t]+\\([^ \t,]+\\)[ \t]+\\([0-9]+:[0-9:]+\\)[ \t]+\\(T[ \t]+\\|\\)\\([0-9]+\\)[ \t]*\\([-+a-zA-Z0-9]+\\)" date)
168	   ;; Styles: (6) and (7) with timezone and buggy timezone
169	   (setq year 6 month 3 day 2 time 4 zone 7))
170	  ((string-match
171	    "\\([^ \t,]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+:[0-9:]+\\)[ \t]+\\([0-9]+\\)" date)
172	   ;; Styles: (3) without timezone
173	   (setq year 4 month 1 day 2 time 3 zone nil))
174	  ((string-match
175	    "\\([^ \t,]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+:[0-9:]+\\)[ \t]+\\([-+a-zA-Z0-9]+\\)[ \t]+\\([0-9]+\\)" date)
176	   ;; Styles: (3) with timezone
177	   (setq year 5 month 1 day 2 time 3 zone 4))
178	  ((string-match
179	    "\\([0-9]+\\)[ \t]+\\([^ \t,]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+\\)[ \t]*\\([-+a-zA-Z0-9]+\\)" date)
180	   ;; Styles: (4) with timezone
181	   (setq year 3 month 2 day 1 time 4 zone 5))
182	  ((string-match
183	    "\\([0-9]+\\)-\\([A-Za-z]+\\)-\\([0-9]+\\)[ \t]+\\([0-9]+:[0-9]+:[0-9]+\\)\\(\\.[0-9]+\\)?[ \t]+\\([-+a-zA-Z0-9]+\\)" date)
184	   ;; Styles: (5) with timezone.
185	   (setq year 3 month 2 day 1 time 4 zone 6))
186	  ((string-match
187	    "\\([0-9]+\\)-\\([A-Za-z]+\\)-\\([0-9]+\\)[ \t]+\\([0-9]+:[0-9]+:[0-9]+\\)\\(\\.[0-9]+\\)?" date)
188	   ;; Styles: (5) without timezone.
189	   (setq year 3 month 2 day 1 time 4 zone nil))
190	  ((string-match
191	    "\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\)[ \t]+\\([0-9]+:[0-9]+:[0-9]+\\)[ \t]+\\([-+a-zA-Z0-9]+\\)" date)
192	   ;; Styles: (8) with timezone.
193	   (setq year 1 month 2 day 3 time 4 zone 5))
194	  ((string-match
195	    "\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\)[T \t]+\\([0-9]+:[0-9]+\\)[ \t]+\\([-+a-zA-Z0-9:]+\\)" date)
196	   ;; Styles: (8) with timezone with a colon in it.
197	   (setq year 1 month 2 day 3 time 4 zone 5))
198	  ((string-match
199	    "\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\)[T \t]+\\([0-9]+:[0-9]+:[0-9]+\\)" date)
200	   ;; Styles: (8) without timezone.
201	   (setq year 1 month 2 day 3 time 4 zone nil))
202	  )
203    (when year
204      (setq year (match-string year date))
205      ;; Guess ambiguous years.  Assume years < 69 don't predate the
206      ;; Unix Epoch, so are 2000+.  Three-digit years are assumed to
207      ;; be relative to 1900.
208      (if (< (length year) 4)
209	  (let ((y (string-to-number year)))
210	    (if (< y 69)
211		(setq y (+ y 100)))
212	    (setq year (int-to-string (+ 1900 y)))))
213      (setq month
214	    (if (= (aref date (+ (match-beginning month) 2)) ?-)
215		;; Handle numeric months, spanning exactly two digits.
216		(substring date
217			   (match-beginning month)
218			   (+ (match-beginning month) 2))
219	      (let* ((string (substring date
220					(match-beginning month)
221					(+ (match-beginning month) 3)))
222		     (monthnum
223		      (cdr (assoc (upcase string) timezone-months-assoc))))
224		(if monthnum
225		    (int-to-string monthnum)))))
226      (setq day (match-string day date))
227      (setq time (match-string time date)))
228    (if zone (setq zone (match-string zone date)))
229    ;; Return a vector.
230    (if (and year month)
231	(vector year month day time zone)
232      (vector "0" "0" "0" "0" nil))))
233
234(defun timezone-parse-time (time)
235  "Parse TIME (HH:MM:SS) and return a vector [hour minute second].
236Recognize HH:MM:SS, HH:MM, HHMMSS, HHMM."
237  (let ((time (or time ""))
238	hour minute second)
239    (cond ((string-match "\\`\\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\)\\'" time)
240	   ;; HH:MM:SS
241	   (setq hour 1 minute 2 second 3))
242	  ((string-match "\\`\\([0-9]+\\):\\([0-9]+\\)\\'" time)
243	   ;; HH:MM
244	   (setq hour 1 minute 2 second nil))
245	  ((string-match "\\`\\([0-9][0-9]\\)\\([0-9][0-9]\\)\\([0-9][0-9]\\)\\'" time)
246	   ;; HHMMSS
247	   (setq hour 1 minute 2 second 3))
248	  ((string-match "\\`\\([0-9][0-9]\\)\\([0-9][0-9]\\)\\'" time)
249	   ;; HHMM
250	   (setq hour 1 minute 2 second nil))
251	  )
252    ;; Return [hour minute second]
253    (vector
254     (if hour (match-string hour time) "0")
255     (if minute (match-string minute time) "0")
256     (if second (match-string second time) "0"))))
257
258
259;; Miscellaneous
260
261(defun timezone-zone-to-minute (timezone)
262  "Translate TIMEZONE to an integer minute offset from GMT.
263TIMEZONE can be a cons cell containing the output of current-time-zone,
264or an integer of the form +-HHMM, or a time zone name."
265  (cond
266     ((consp timezone)
267      (/ (car timezone) 60))
268     (timezone
269      (progn
270	(setq timezone
271	      (or (cdr (assoc (upcase timezone) timezone-world-timezones))
272		  ;; +900
273		  timezone))
274	(if (stringp timezone)
275	    (setq timezone (string-to-number timezone)))
276	;; Taking account of minute in timezone.
277	;; HHMM -> MM
278	(let* ((abszone (abs timezone))
279 	       (minutes (+ (* 60 (/ abszone 100)) (% abszone 100))))
280 	  (if (< timezone 0) (- minutes) minutes))))
281     (t 0)))
282
283(defun timezone-time-from-absolute (date seconds)
284  "Compute the UTC time equivalent to DATE at time SECONDS after midnight.
285Return a list suitable as an argument to current-time-zone,
286or nil if the date cannot be thus represented.
287DATE is the number of days elapsed since the (imaginary)
288Gregorian date Sunday, December 31, 1 BC."
289  (let* ((current-time-origin 719163)
290	    ;; (timezone-absolute-from-gregorian 1 1 1970)
291	 (days (- date current-time-origin))
292	 (seconds-per-day (float 86400))
293	 (seconds (+ seconds (* days seconds-per-day)))
294	 (current-time-arithmetic-base (float 65536))
295	 (hi (floor (/ seconds current-time-arithmetic-base)))
296	 (hibase (* hi current-time-arithmetic-base))
297	 (lo (floor (- seconds hibase))))
298     (and (< (abs (- seconds (+ hibase lo))) 2) ;; Check for integer overflow.
299	  (cons hi lo))))
300
301(defun timezone-time-zone-from-absolute (date seconds)
302  "Compute the local time zone for DATE at time SECONDS after midnight.
303Return a list in the same format as current-time-zone's result,
304or nil if the local time zone could not be computed.
305DATE is the number of days elapsed since the (imaginary)
306Gregorian date Sunday, December 31, 1 BC."
307   (and (fboundp 'current-time-zone)
308	(let ((utc-time (timezone-time-from-absolute date seconds)))
309	  (and utc-time
310	       (let ((zone (current-time-zone utc-time)))
311		 (and (car zone) zone))))))
312
313(defun timezone-fix-time (date local timezone)
314  "Convert DATE (default timezone LOCAL) to YYYY-MM-DD-HH-MM-SS-ZONE vector.
315If LOCAL is nil, it is assumed to be GMT.
316If TIMEZONE is nil, use the local time zone."
317  (let* ((date   (timezone-parse-date date))
318	 (year   (string-to-number (aref date 0)))
319	 (year	 (cond ((< year 69)
320			(+ year 2000))
321		       ((< year 100)
322			(+ year 1900))
323		       ((< year 1000)	; possible 3-digit years.
324			(+ year 1900))
325		       (t year)))
326	 (month  (string-to-number (aref date 1)))
327	 (day    (string-to-number (aref date 2)))
328	 (time   (timezone-parse-time (aref date 3)))
329	 (hour   (string-to-number (aref time 0)))
330	 (minute (string-to-number (aref time 1)))
331	 (second (string-to-number (aref time 2)))
332	 (local  (or (aref date 4) local)) ;Use original if defined
333	 (timezone
334	  (or timezone
335	      (timezone-time-zone-from-absolute
336	       (timezone-absolute-from-gregorian month day year)
337	       (+ second (* 60 (+ minute (* 60 hour)))))))
338	 (diff   (- (timezone-zone-to-minute timezone)
339		    (timezone-zone-to-minute local)))
340	 (minute (+ minute diff))
341	 (hour-fix (floor minute 60)))
342    (setq hour (+ hour hour-fix))
343    (setq minute (- minute (* 60 hour-fix)))
344    ;; HOUR may be larger than 24 or smaller than 0.
345    (cond ((<= 24 hour)			;24 -> 00
346	   (setq hour (- hour 24))
347	   (setq day  (1+ day))
348	   (when (< (timezone-last-day-of-month month year) day)
349	     (setq month (1+ month))
350	     (setq day 1)
351	     (when (< 12 month)
352	       (setq month 1)
353	       (setq year (1+ year)))))
354	  ((> 0 hour)
355	   (setq hour (+ hour 24))
356	   (setq day  (1- day))
357	   (when (> 1 day)
358	     (setq month (1- month))
359	     (when (> 1 month)
360	       (setq month 12)
361	       (setq year (1- year)))
362	     (setq day (timezone-last-day-of-month month year)))))
363    (vector year month day hour minute second timezone)))
364
365;; Partly copied from Calendar program by Edward M. Reingold.
366;; Thanks a lot.
367
368(defun timezone-last-day-of-month (month year)
369  "The last day in MONTH during YEAR."
370  (if (and (= month 2) (timezone-leap-year-p year))
371      29
372    (aref [31 28 31 30 31 30 31 31 30 31 30 31] (1- month))))
373
374(defun timezone-leap-year-p (year)
375  "Returns t if YEAR is a Gregorian leap year."
376  (or (and (zerop  (% year 4))
377	   (not (zerop (% year 100))))
378      (zerop (% year 400))))
379
380(defun timezone-day-number (month day year)
381  "Return the day number within the year of the date month/day/year."
382  (let ((day-of-year (+ day (* 31 (1- month)))))
383    (if (> month 2)
384	(progn
385	  (setq day-of-year (- day-of-year (/ (+ 23 (* 4 month)) 10)))
386	  (if (timezone-leap-year-p year)
387	      (setq day-of-year (1+ day-of-year)))))
388    day-of-year))
389
390(defun timezone-absolute-from-gregorian (month day year)
391  "The number of days between the Gregorian date 12/31/1 BC and month/day/year.
392The Gregorian date Sunday, December 31, 1 BC is imaginary."
393  (+ (timezone-day-number month day year);; Days this year
394     (* 365 (1- year));;	+ Days in prior years
395     (/ (1- year) 4);;		+ Julian leap years
396     (- (/ (1- year) 100));;	- century years
397     (/ (1- year) 400)));;	+ Gregorian leap years
398
399(provide 'timezone)
400
401;;; arch-tag: e23d5bc6-f32d-48ba-8996-323e9d654b3f
402;;; timezone.el ends here
403