timezone.el 16 KB

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