aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/lispref/os.texi13
-rw-r--r--etc/NEWS9
-rw-r--r--lisp/calendar/iso8601.el370
-rw-r--r--test/lisp/calendar/iso8601-tests.el291
4 files changed, 683 insertions, 0 deletions
diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index d397a125738..b3444838d3b 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -1622,6 +1622,19 @@ ISO 8601 string, like ``Fri, 25 Mar 2016 16:24:56 +0100'' or
1622less well-formed time strings as well. 1622less well-formed time strings as well.
1623@end defun 1623@end defun
1624 1624
1625@vindex ISO 8601 date/time strings
1626@defun iso8601-parse string
1627For a more strict function (that will error out upon invalid input),
1628this function can be used instead. It's able to parse all variants of
1629the ISO 8601 standard, so in addition to the formats mentioned above,
1630it also parses things like ``1998W45-3'' (week number) and
1631``1998-245'' (ordinal day number). To parse durations, there's
1632@code{iso8601-parse-duration}, and to parse intervals, there's
1633@code{iso8601-parse-interval}. All these functions return decoded
1634time structures, except the final one, which returns three of them
1635(the start, the end, and the duration).
1636@end defun
1637
1625@defun format-time-string format-string &optional time zone 1638@defun format-time-string format-string &optional time zone
1626 1639
1627This function converts @var{time} (or the current time, if 1640This function converts @var{time} (or the current time, if
diff --git a/etc/NEWS b/etc/NEWS
index 2bdbfcb8d08..7c21cc79307 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2056,6 +2056,15 @@ TICKS is an integer and HZ is a positive integer denoting a clock
2056frequency. The old 'encode-time' API is still supported. 2056frequency. The old 'encode-time' API is still supported.
2057 2057
2058+++ 2058+++
2059*** A new package to parse ISO 8601 time, date, durations and
2060intervals has been added. The main function to use is
2061'iso8601-parse', but there's also 'iso8601-parse-date',
2062'iso8601-parse-time', 'iso8601-parse-duration' and
2063'iso8601-parse-interval'. All these functions return decoded time
2064structures, except the final one, which returns three of them (start,
2065end and duration).
2066
2067+++
2059*** 'time-add', 'time-subtract', and 'time-less-p' now accept 2068*** 'time-add', 'time-subtract', and 'time-less-p' now accept
2060infinities and NaNs too, and propagate them or return nil like 2069infinities and NaNs too, and propagate them or return nil like
2061floating-point operators do. 2070floating-point operators do.
diff --git a/lisp/calendar/iso8601.el b/lisp/calendar/iso8601.el
new file mode 100644
index 00000000000..ab0077ac58d
--- /dev/null
+++ b/lisp/calendar/iso8601.el
@@ -0,0 +1,370 @@
1;;; iso8601.el --- parse ISO 8601 date/time strings -*- lexical-binding:t -*-
2
3;; Copyright (C) 2019 Free Software Foundation, Inc.
4
5;; Keywords: dates
6
7;; This file is part of GNU Emacs.
8
9;; GNU Emacs is free software: you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
11;; the Free Software Foundation, either version 3 of the License, or
12;; (at your option) any later version.
13
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
18
19;; You should have received a copy of the GNU General Public License
20;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
21
22;;; Commentary:
23
24;; ISO8601 times basically look like 1985-04-01T15:23:49... Or so
25;; you'd think. This is what everybody means when they say "ISO8601",
26;; but it's in reality a quite large collection of syntaxes, including
27;; week numbers, ordinal dates, durations and intervals. This package
28;; has functions for parsing them all.
29;;
30;; The interface functions are `iso8601-parse', `iso8601-parse-date',
31;; `iso8601-parse-time', `iso8601-parse-zone',
32;; `iso8601-parse-duration' and `iso8601-parse-interval'. They all
33;; return decoded time objects, except the last one, which returns a
34;; list of three of them.
35;;
36;; (iso8601-parse-interval "P1Y2M10DT2H30M/2008W32T153000-01")
37;; '((0 0 13 24 5 2007 nil nil -3600)
38;; (0 30 15 3 8 2008 nil nil -3600)
39;; (0 30 2 10 2 1 nil nil nil))
40;;
41;;
42;; The standard can be found at:
43;;
44;; http://www.loc.gov/standards/datetime/iso-tc154-wg5_n0038_iso_wd_8601-1_2016-02-16.pdf
45;;
46;; The Wikipedia page on the standard is also informative:
47;;
48;; https://en.wikipedia.org/wiki/ISO_8601
49;;
50;; RFC3339 defines the subset that everybody thinks of as "ISO8601".
51
52;;; Code:
53
54(require 'time-date)
55(require 'cl-lib)
56
57(defun iso8601--concat-regexps (regexps)
58 (mapconcat (lambda (regexp)
59 (concat "\\(?:"
60 (replace-regexp-in-string "(" "(?:" regexp)
61 "\\)"))
62 regexps "\\|"))
63
64(defconst iso8601--year-match
65 "\\([-+]\\)?\\([0-9][0-9][0-9][0-9]\\)")
66(defconst iso8601--full-date-match
67 "\\([-+]\\)?\\([0-9][0-9][0-9][0-9]\\)-?\\([0-9][0-9]\\)-?\\([0-9][0-9]\\)")
68(defconst iso8601--without-day-match
69 "\\([-+]\\)?\\([0-9][0-9][0-9][0-9]\\)-\\([0-9][0-9]\\)")
70(defconst iso8601--outdated-date-match
71 "--\\([0-9][0-9]\\)-?\\([0-9][0-9]\\)")
72(defconst iso8601--week-date-match
73 "\\([-+]\\)?\\([0-9][0-9][0-9][0-9]\\)-?W\\([0-9][0-9]\\)-?\\([0-9]\\)?")
74(defconst iso8601--ordinal-date-match
75 "\\([-+]\\)?\\([0-9][0-9][0-9][0-9]\\)-?\\([0-9][0-9][0-9]\\)")
76(defconst iso8601--date-match
77 (iso8601--concat-regexps
78 (list iso8601--year-match
79 iso8601--full-date-match
80 iso8601--without-day-match
81 iso8601--outdated-date-match
82 iso8601--week-date-match
83 iso8601--ordinal-date-match)))
84
85(defconst iso8601--time-match
86 "\\([0-9][0-9]\\):?\\([0-9][0-9]\\)?:?\\([0-9][0-9]\\)?\\.?\\([0-9][0-9][0-9]\\)?")
87
88(defconst iso8601--zone-match
89 "\\(Z\\|\\([-+]\\)\\([0-9][0-9]\\):?\\([0-9][0-9]\\)?\\)")
90
91(defconst iso8601--full-time-match
92 (concat "\\(" (replace-regexp-in-string "(" "(?:" iso8601--time-match) "\\)"
93 "\\(" iso8601--zone-match "\\)?"))
94
95(defconst iso8601--combined-match
96 (concat "\\(" iso8601--date-match "\\)"
97 "\\(?:T\\("
98 (replace-regexp-in-string "(" "(?:" iso8601--time-match)
99 "\\)"
100 "\\(" iso8601--zone-match "\\)?\\)?"))
101
102(defconst iso8601--duration-full-match
103 "P\\([0-9]+Y\\)?\\([0-9]+M\\)?\\([0-9]+D\\)?\\(T\\([0-9]+H\\)?\\([0-9]+M\\)?\\([0-9]+S\\)?\\)?")
104(defconst iso8601--duration-week-match
105 "P\\([0-9]+\\)W")
106(defconst iso8601--duration-combined-match
107 (concat "P" iso8601--combined-match))
108(defconst iso8601--duration-match
109 (iso8601--concat-regexps
110 (list iso8601--duration-full-match
111 iso8601--duration-week-match
112 iso8601--duration-combined-match)))
113
114(defun iso8601-parse (string)
115 "Parse an ISO 8601 date/time string and return a `decoded-time' structure.
116
117The ISO 8601 date/time strings look like \"2008-03-02T13:47:30\",
118but shorter, incomplete strings like \"2008-03-02\" are valid, as
119well as variants like \"2008W32\" (week number) and
120\"2008-234\" (ordinal day number)."
121 (if (not (iso8601-valid-p string))
122 (signal 'wrong-type-argument string)
123 (let* ((date-string (match-string 1 string))
124 (time-string (match-string 2 string))
125 (zone-string (match-string 3 string))
126 (date (iso8601-parse-date date-string)))
127 ;; The time portion is optional.
128 (when time-string
129 (let ((time (iso8601-parse-time time-string)))
130 (setf (decoded-time-hour date) (decoded-time-hour time))
131 (setf (decoded-time-minute date) (decoded-time-minute time))
132 (setf (decoded-time-second date) (decoded-time-second time))))
133 ;; The time zone is optional.
134 (when zone-string
135 (setf (decoded-time-zone date)
136 ;; The time zone in decoded times are in seconds.
137 (* (iso8601-parse-zone zone-string) 60)))
138 date)))
139
140(defun iso8601-parse-date (string)
141 "Parse STRING (which should be on ISO 8601 format) and return a time value."
142 (cond
143 ;; Just a year: [-+]YYYY.
144 ((iso8601--match iso8601--year-match string)
145 (iso8601--decoded-time
146 :year (iso8601--adjust-year (match-string 1 string)
147 (match-string 2 string))))
148 ;; Calendar dates: YYYY-MM-DD and variants.
149 ((iso8601--match iso8601--full-date-match string)
150 (iso8601--decoded-time
151 :year (iso8601--adjust-year (match-string 1 string)
152 (match-string 2 string))
153 :month (match-string 3 string)
154 :day (match-string 4 string)))
155 ;; Calendar date without day: YYYY-MM.
156 ((iso8601--match iso8601--without-day-match string)
157 (iso8601--decoded-time
158 :year (iso8601--adjust-year (match-string 1 string)
159 (match-string 2 string))
160 :month (match-string 3 string)))
161 ;; Outdated date without year: --MM-DD
162 ((iso8601--match iso8601--outdated-date-match string)
163 (iso8601--decoded-time
164 :month (match-string 1 string)
165 :day (match-string 2 string)))
166 ;; Week dates: YYYY-Www-D
167 ((iso8601--match iso8601--week-date-match string)
168 (let* ((year (iso8601--adjust-year (match-string 1 string)
169 (match-string 2 string)))
170 (week (string-to-number (match-string 3 string)))
171 (day-of-week (and (match-string 4 string)
172 (string-to-number (match-string 4 string))))
173 (jan-start (decoded-time-weekday
174 (decode-time
175 (iso8601--encode-time
176 (iso8601--decoded-time :year year
177 :month 1
178 :day 4)))))
179 (correction (+ (if (zerop jan-start) 7 jan-start)
180 3))
181 (ordinal (+ (* week 7) (or day-of-week 0) (- correction))))
182 (cond
183 ;; Monday 29 December 2008 is written "2009-W01-1".
184 ((< ordinal 1)
185 (setq year (1- year)
186 ordinal (+ ordinal (if (date-leap-year-p year)
187 366 365))))
188 ;; Sunday 3 January 2010 is written "2009-W53-7".
189 ((> ordinal (if (date-leap-year-p year)
190 366 365))
191 (setq ordinal (- ordinal (if (date-leap-year-p year)
192 366 365))
193 year (1+ year))))
194 (let ((month-day (date-ordinal-to-time year ordinal)))
195 (iso8601--decoded-time :year year
196 :month (decoded-time-month month-day)
197 :day (decoded-time-day month-day)))))
198 ;; Ordinal dates: YYYY-DDD
199 ((iso8601--match iso8601--ordinal-date-match string)
200 (let* ((year (iso8601--adjust-year (match-string 1 string)
201 (match-string 2 string)))
202 (ordinal (string-to-number (match-string 3 string)))
203 (month-day (date-ordinal-to-time year ordinal)))
204 (iso8601--decoded-time :year year
205 :month (decoded-time-month month-day)
206 :day (decoded-time-day month-day))))
207 (t
208 (signal 'wrong-type-argument string))))
209
210(defun iso8601--adjust-year (sign year)
211 (save-match-data
212 (let ((year (if (stringp year)
213 (string-to-number year)
214 year)))
215 (if (string= sign "-")
216 ;; -0001 is 2 BCE.
217 (1- (- year))
218 year))))
219
220(defun iso8601-parse-time (string)
221 "Parse STRING, which should be an ISO 8601 time string, and return a time value."
222 (if (not (iso8601--match iso8601--full-time-match string))
223 (signal 'wrong-type-argument string)
224 (let ((time (match-string 1 string))
225 (zone (match-string 2 string)))
226 (if (not (iso8601--match iso8601--time-match time))
227 (signal 'wrong-type-argument string)
228 (let ((hour (string-to-number (match-string 1 time)))
229 (minute (and (match-string 2 time)
230 (string-to-number (match-string 2 time))))
231 (second (and (match-string 3 time)
232 (string-to-number (match-string 3 time))))
233 ;; Hm...
234 (_millisecond (and (match-string 4 time)
235 (string-to-number (match-string 4 time)))))
236 (iso8601--decoded-time :hour hour
237 :minute (or minute 0)
238 :second (or second 0)
239 :zone (and zone
240 (* 60 (iso8601-parse-zone
241 zone)))))))))
242
243(defun iso8601-parse-zone (string)
244 "Parse STRING, which should be an ISO 8601 time zone.
245Return the number of minutes."
246 (if (not (iso8601--match iso8601--zone-match string))
247 (signal 'wrong-type-argument string)
248 (if (match-string 2 string)
249 ;; HH:MM-ish.
250 (let ((hour (string-to-number (match-string 3 string)))
251 (minute (and (match-string 4 string)
252 (string-to-number (match-string 4 string)))))
253 (* (if (equal (match-string 2 string) "-")
254 -1
255 1)
256 (+ (* hour 60)
257 (or minute 0))))
258 ;; "Z".
259 0)))
260
261(defun iso8601-valid-p (string)
262 "Say whether STRING is a valid ISO 8601 representation."
263 (iso8601--match iso8601--combined-match string))
264
265(defun iso8601-parse-duration (string)
266 "Parse ISO 8601 durations on the form P3Y6M4DT12H30M5S."
267 (cond
268 ((and (iso8601--match iso8601--duration-full-match string)
269 ;; Just a "P" isn't valid; there has to be at least one
270 ;; element, like P1M.
271 (> (length (match-string 0 string)) 2))
272 (iso8601--decoded-time :year (or (match-string 1 string) 0)
273 :month (or (match-string 2 string) 0)
274 :day (or (match-string 3 string) 0)
275 :hour (or (match-string 5 string) 0)
276 :minute (or (match-string 6 string) 0)
277 :second (or (match-string 7 string) 0)))
278 ;; PnW: Weeks.
279 ((iso8601--match iso8601--duration-week-match string)
280 (let ((weeks (string-to-number (match-string 1 string))))
281 ;; Does this make sense? Hm...
282 (iso8601--decoded-time :day (* weeks 7))))
283 ;; P<date>T<time>
284 ((iso8601--match iso8601--duration-combined-match string)
285 (iso8601-parse (substring string 1)))
286 (t
287 (signal 'wrong-type-argument string))))
288
289(defun iso8601-parse-interval (string)
290 "Parse ISO 8601 intervals."
291 (let ((bits (split-string string "/"))
292 start end duration)
293 (if (not (= (length bits) 2))
294 (signal 'wrong-type-argument string)
295 ;; The intervals may be an explicit start/end times, or either a
296 ;; start or an end, and an accompanying duration.
297 (cond
298 ((and (string-match "\\`P" (car bits))
299 (iso8601-valid-p (cadr bits)))
300 (setq duration (iso8601-parse-duration (car bits))
301 end (iso8601-parse (cadr bits))))
302 ((and (string-match "\\`P" (cadr bits))
303 (iso8601-valid-p (car bits)))
304 (setq duration (iso8601-parse-duration (cadr bits))
305 start (iso8601-parse (car bits))))
306 ((and (iso8601-valid-p (car bits))
307 (iso8601-valid-p (cadr bits)))
308 (setq start (iso8601-parse (car bits))
309 end (iso8601-parse (cadr bits))))
310 (t
311 (signal 'wrong-type-argument string))))
312 (unless end
313 (setq end (decoded-time-add start duration)))
314 (unless start
315 (setq start (decoded-time-add end
316 ;; We negate the duration so that
317 ;; we get a subtraction.
318 (mapcar (lambda (elem)
319 (if (numberp elem)
320 (- elem)
321 elem))
322 duration))))
323 (list start end
324 (or duration
325 (decode-time (time-subtract (iso8601--encode-time end)
326 (iso8601--encode-time start))
327 (or (decoded-time-zone end) 0))))))
328
329(defun iso8601--match (regexp string)
330 (string-match (concat "\\`" regexp "\\'") string))
331
332(defun iso8601--value (elem &optional default)
333 (if (stringp elem)
334 (string-to-number elem)
335 (or elem default)))
336
337(cl-defun iso8601--decoded-time (&key second minute hour
338 day month year
339 dst zone)
340 (list (iso8601--value second)
341 (iso8601--value minute)
342 (iso8601--value hour)
343 (iso8601--value day)
344 (iso8601--value month)
345 (iso8601--value year)
346 nil
347 dst
348 zone))
349
350(defun iso8601--encode-time (time)
351 "Like `encode-time', but fill in nil values in TIME."
352 (setq time (copy-sequence time))
353 (unless (decoded-time-second time)
354 (setf (decoded-time-second time) 0))
355 (unless (decoded-time-minute time)
356 (setf (decoded-time-minute time) 0))
357 (unless (decoded-time-hour time)
358 (setf (decoded-time-hour time) 0))
359
360 (unless (decoded-time-day time)
361 (setf (decoded-time-day time) 1))
362 (unless (decoded-time-month time)
363 (setf (decoded-time-month time) 1))
364 (unless (decoded-time-year time)
365 (setf (decoded-time-year time) 0))
366 (encode-time time))
367
368(provide 'iso8601)
369
370;;; iso8601.el ends here
diff --git a/test/lisp/calendar/iso8601-tests.el b/test/lisp/calendar/iso8601-tests.el
new file mode 100644
index 00000000000..2959f54b811
--- /dev/null
+++ b/test/lisp/calendar/iso8601-tests.el
@@ -0,0 +1,291 @@
1;;; iso8601-tests.el --- tests for calendar/iso8601.el -*- lexical-binding:t -*-
2
3;; Copyright (C) 2019 Free Software Foundation, Inc.
4
5;; This file is part of GNU Emacs.
6
7;; GNU Emacs is free software: you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
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
17;; You should have received a copy of the GNU General Public License
18;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
19
20;;; Code:
21
22(require 'ert)
23(require 'iso8601)
24
25(ert-deftest test-iso8601-date-years ()
26 (should (equal (iso8601-parse-date "1985")
27 '(nil nil nil nil nil 1985 nil nil nil)))
28 (should (equal (iso8601-parse-date "-0003")
29 '(nil nil nil nil nil -4 nil nil nil)))
30 (should (equal (iso8601-parse-date "+1985")
31 '(nil nil nil nil nil 1985 nil nil nil))))
32
33(ert-deftest test-iso8601-date-dates ()
34 (should (equal (iso8601-parse-date "1985-03-14")
35 '(nil nil nil 14 3 1985 nil nil nil)))
36 (should (equal (iso8601-parse-date "19850314")
37 '(nil nil nil 14 3 1985 nil nil nil)))
38 (should (equal (iso8601-parse-date "1985-02")
39 '(nil nil nil nil 2 1985 nil nil nil))))
40
41(ert-deftest test-iso8601-date-obsolete ()
42 (should (equal (iso8601-parse-date "--02-01")
43 '(nil nil nil 1 2 nil nil nil nil)))
44 (should (equal (iso8601-parse-date "--0201")
45 '(nil nil nil 1 2 nil nil nil nil))))
46
47(ert-deftest test-iso8601-date-weeks ()
48 (should (equal (iso8601-parse-date "2008W39-6")
49 '(nil nil nil 27 9 2008 nil nil nil)))
50 (should (equal (iso8601-parse-date "2009W01-1")
51 '(nil nil nil 29 12 2008 nil nil nil)))
52 (should (equal (iso8601-parse-date "2009W53-7")
53 '(nil nil nil 3 1 2010 nil nil nil))))
54
55(ert-deftest test-iso8601-date-ordinals ()
56 (should (equal (iso8601-parse-date "1981-095")
57 '(nil nil nil 5 4 1981 nil nil nil))))
58
59(ert-deftest test-iso8601-time ()
60 (should (equal (iso8601-parse-time "13:47:30")
61 '(30 47 13 nil nil nil nil nil nil)))
62 (should (equal (iso8601-parse-time "134730")
63 '(30 47 13 nil nil nil nil nil nil)))
64 (should (equal (iso8601-parse-time "1347")
65 '(0 47 13 nil nil nil nil nil nil))))
66
67(ert-deftest test-iso8601-combined ()
68 (should (equal (iso8601-parse "2008-03-02T13:47:30")
69 '(30 47 13 2 3 2008 nil nil nil)))
70 (should (equal (iso8601-parse "2008-03-02T13:47:30Z")
71 '(30 47 13 2 3 2008 nil nil 0)))
72 (should (equal (iso8601-parse "2008-03-02T13:47:30+01:00")
73 '(30 47 13 2 3 2008 nil nil 3600)))
74 (should (equal (iso8601-parse "2008-03-02T13:47:30-01")
75 '(30 47 13 2 3 2008 nil nil -3600))))
76
77(ert-deftest test-iso8601-duration ()
78 (should (equal (iso8601-parse-duration "P3Y6M4DT12H30M5S")
79 '(5 30 12 4 6 3 nil nil nil)))
80 (should (equal (iso8601-parse-duration "P1M")
81 '(0 0 0 0 1 0 nil nil nil)))
82 (should (equal (iso8601-parse-duration "PT1M")
83 '(0 1 0 0 0 0 nil nil nil)))
84 (should (equal (iso8601-parse-duration "P0003-06-04T12:30:05")
85 '(5 30 12 4 6 3 nil nil nil))))
86
87(ert-deftest test-iso8601-invalid ()
88 (should-not (iso8601-valid-p " 2008-03-02T13:47:30-01"))
89 (should-not (iso8601-valid-p "2008-03-02T13:47:30-01:200"))
90 (should-not (iso8601-valid-p "2008-03-02T13:47:30-01 "))
91 (should-not (iso8601-valid-p "2008-03-02 T 13:47:30-01 "))
92 (should-not (iso8601-valid-p "20008-03-02T13:47:30-01")))
93
94(ert-deftest test-iso8601-intervals ()
95 (should (equal
96 (iso8601-parse-interval "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z")
97 '((0 0 13 1 3 2007 nil nil 0)
98 (0 30 15 11 5 2008 nil nil 0)
99 ;; Hm... can't really use decode-time for time differences...
100 (0 30 2 14 3 1971 0 nil 0))))
101 (should (equal (iso8601-parse-interval "2007-03-01T13:00:00Z/P1Y2M10DT2H30M")
102 '((0 0 13 1 3 2007 nil nil 0)
103 (0 30 15 11 5 2008 nil nil 0)
104 (0 30 2 10 2 1 nil nil nil))))
105 (should (equal (iso8601-parse-interval "P1Y2M10DT2H30M/2008-05-11T15:30:00Z")
106 '((0 0 13 1 3 2007 nil nil 0)
107 (0 30 15 11 5 2008 nil nil 0)
108 (0 30 2 10 2 1 nil nil nil)))))
109
110(ert-deftest standard-test-dates ()
111 (should (equal (iso8601-parse-date "19850412")
112 '(nil nil nil 12 4 1985 nil nil nil)))
113 (should (equal (iso8601-parse-date "1985-04-12")
114 '(nil nil nil 12 4 1985 nil nil nil)))
115
116 (should (equal (iso8601-parse-date "1985102")
117 '(nil nil nil 12 4 1985 nil nil nil)))
118 (should (equal (iso8601-parse-date "1985-102")
119 '(nil nil nil 12 4 1985 nil nil nil)))
120
121 (should (equal (iso8601-parse-date "1985W155")
122 '(nil nil nil 12 4 1985 nil nil nil)))
123 (should (equal (iso8601-parse-date "1985-W15-5")
124 '(nil nil nil 12 4 1985 nil nil nil)))
125
126 (should (equal (iso8601-parse-date "1985W15")
127 '(nil nil nil 7 4 1985 nil nil nil)))
128 (should (equal (iso8601-parse-date "1985-W15")
129 '(nil nil nil 7 4 1985 nil nil nil)))
130
131 (should (equal (iso8601-parse-date "1985-04")
132 '(nil nil nil nil 4 1985 nil nil nil)))
133
134 (should (equal (iso8601-parse-date "1985")
135 '(nil nil nil nil nil 1985 nil nil nil)))
136
137 (should (equal (iso8601-parse-date "+1985-04-12")
138 '(nil nil nil 12 4 1985 nil nil nil)))
139 (should (equal (iso8601-parse-date "+19850412")
140 '(nil nil nil 12 4 1985 nil nil nil))))
141
142(ert-deftest standard-test-time-of-day-local-time ()
143 (should (equal (iso8601-parse-time "152746")
144 '(46 27 15 nil nil nil nil nil nil)))
145 (should (equal (iso8601-parse-time "15:27:46")
146 '(46 27 15 nil nil nil nil nil nil)))
147
148 (should (equal (iso8601-parse-time "1528")
149 '(0 28 15 nil nil nil nil nil nil)))
150 (should (equal (iso8601-parse-time "15:28")
151 '(0 28 15 nil nil nil nil nil nil)))
152
153 (should (equal (iso8601-parse-time "15")
154 '(0 0 15 nil nil nil nil nil nil))))
155
156(ert-deftest standard-test-time-of-day-fractions ()
157 ;; decoded-time doesn't support sub-second times.
158 ;; (should (equal (iso8601-parse-time "152735,5")
159 ;; '(46 27 15 nil nil nil nil nil nil)))
160 ;; (should (equal (iso8601-parse-time "15:27:35,5")
161 ;; '(46 27 15 nil nil nil nil nil nil)))
162 )
163
164(ert-deftest standard-test-time-of-day-beginning-of-day ()
165 (should (equal (iso8601-parse-time "000000")
166 '(0 0 0 nil nil nil nil nil nil)))
167 (should (equal (iso8601-parse-time "00:00:00")
168 '(0 0 0 nil nil nil nil nil nil)))
169
170 (should (equal (iso8601-parse-time "0000")
171 '(0 0 0 nil nil nil nil nil nil)))
172 (should (equal (iso8601-parse-time "00:00")
173 '(0 0 0 nil nil nil nil nil nil))))
174
175(ert-deftest standard-test-time-of-day-utc ()
176 (should (equal (iso8601-parse-time "232030Z")
177 '(30 20 23 nil nil nil nil nil 0)))
178 (should (equal (iso8601-parse-time "23:20:30Z")
179 '(30 20 23 nil nil nil nil nil 0)))
180
181 (should (equal (iso8601-parse-time "2320Z")
182 '(0 20 23 nil nil nil nil nil 0)))
183 (should (equal (iso8601-parse-time "23:20Z")
184 '(0 20 23 nil nil nil nil nil 0)))
185
186 (should (equal (iso8601-parse-time "23Z")
187 '(0 0 23 nil nil nil nil nil 0))))
188
189
190(ert-deftest standard-test-time-of-day-zone ()
191 (should (equal (iso8601-parse-time "152746+0100")
192 '(46 27 15 nil nil nil nil nil 3600)))
193 (should (equal (iso8601-parse-time "15:27:46+0100")
194 '(46 27 15 nil nil nil nil nil 3600)))
195
196 (should (equal (iso8601-parse-time "152746+01")
197 '(46 27 15 nil nil nil nil nil 3600)))
198 (should (equal (iso8601-parse-time "15:27:46+01")
199 '(46 27 15 nil nil nil nil nil 3600)))
200
201 (should (equal (iso8601-parse-time "152746-0500")
202 '(46 27 15 nil nil nil nil nil -18000)))
203 (should (equal (iso8601-parse-time "15:27:46-0500")
204 '(46 27 15 nil nil nil nil nil -18000)))
205
206 (should (equal (iso8601-parse-time "152746-05")
207 '(46 27 15 nil nil nil nil nil -18000)))
208 (should (equal (iso8601-parse-time "15:27:46-05")
209 '(46 27 15 nil nil nil nil nil -18000))))
210
211(ert-deftest standard-test-date-and-time-of-day ()
212 (should (equal (iso8601-parse "19850412T101530")
213 '(30 15 10 12 4 1985 nil nil nil)))
214 (should (equal (iso8601-parse "1985-04-12T10:15:30")
215 '(30 15 10 12 4 1985 nil nil nil)))
216
217 (should (equal (iso8601-parse "1985102T235030Z")
218 '(30 50 23 12 4 1985 nil nil 0)))
219 (should (equal (iso8601-parse "1985-102T23:50:30Z")
220 '(30 50 23 12 4 1985 nil nil 0)))
221
222 (should (equal (iso8601-parse "1985W155T235030")
223 '(30 50 23 12 4 1985 nil nil nil)))
224 (should (equal (iso8601-parse "1985-W155T23:50:30")
225 '(30 50 23 12 4 1985 nil nil nil))))
226
227(ert-deftest standard-test-interval ()
228 ;; A time interval starting at 20 minutes and 50 seconds past 23
229 ;; hours on 12 April 1985 and ending at 30 minutes past 10 hours on
230 ;; 25 June 1985.
231 (should (equal (iso8601-parse-interval "19850412T232050/19850625T103000")
232 '((50 20 23 12 4 1985 nil nil nil)
233 (0 30 10 25 6 1985 nil nil nil)
234 (10 9 11 15 3 1970 0 nil 0))))
235 (should (equal (iso8601-parse-interval
236 "1985-04-12T23:20:50/1985-06-25T10:30:00")
237 '((50 20 23 12 4 1985 nil nil nil)
238 (0 30 10 25 6 1985 nil nil nil)
239 (10 9 11 15 3 1970 0 nil 0))))
240
241 ;; A time interval starting at 12 April 1985 and ending on 25 June
242 ;; 1985.
243
244 ;; This example doesn't seem valid according to the standard.
245 ;; "0625" is unambiguous, and means "the year 625". Weird.
246 ;; (should (equal (iso8601-parse-interval "19850412/0625")
247 ;; '((nil nil nil 12 4 1985 nil nil nil)
248 ;; (nil nil nil nil nil 625 nil nil nil)
249 ;; (0 17 0 22 9 609 5 nil 0))))
250
251 ;; A time interval of 2 years, 10 months, 15 days, 10 hours, 20
252 ;; minutes and 30 seconds.
253 (should (equal (iso8601-parse-duration "P2Y10M15DT10H20M30S")
254 '(30 20 10 15 10 2 nil nil nil)))
255
256 (should (equal (iso8601-parse-duration "P00021015T102030")
257 '(30 20 10 15 10 2 nil nil nil)))
258 (should (equal (iso8601-parse-duration "P0002-10-15T10:20:30")
259 '(30 20 10 15 10 2 nil nil nil)))
260
261 ;; A time interval of 1 year and 6 months.
262 (should (equal (iso8601-parse-duration "P1Y6M")
263 '(0 0 0 0 6 1 nil nil nil)))
264 (should (equal (iso8601-parse-duration "P0001-06")
265 '(nil nil nil nil 6 1 nil nil nil)))
266
267 ;; A time interval of seventy-two hours.
268 (should (equal (iso8601-parse-duration "PT72H")
269 '(0 0 72 0 0 0 nil nil nil)))
270
271 ;; Defined by start and duration
272 ;; A time interval of 1 year, 2 months, 15 days and 12 hours,
273 ;; beginning on 12 April 1985 at 20 minutes past 23 hours.
274 (should (equal (iso8601-parse-interval "19850412T232000/P1Y2M15DT12H")
275 '((0 20 23 12 4 1985 nil nil nil)
276 (0 20 11 28 6 1986 nil nil nil)
277 (0 0 12 15 2 1 nil nil nil))))
278 (should (equal (iso8601-parse-interval "1985-04-12T23:20:00/P1Y2M15DT12H")
279 '((0 20 23 12 4 1985 nil nil nil)
280 (0 20 11 28 6 1986 nil nil nil)
281 (0 0 12 15 2 1 nil nil nil))))
282
283 ;; Defined by duration and end
284 ;; A time interval of 1 year, 2 months, 15 days and 12 hours, ending
285 ;; on 12 April 1985 at 20 minutes past 23 hour.
286 (should (equal (iso8601-parse-interval "P1Y2M15DT12H/19850412T232000")
287 '((0 20 11 28 1 1984 nil nil nil)
288 (0 20 23 12 4 1985 nil nil nil)
289 (0 0 12 15 2 1 nil nil nil)))))
290
291;;; iso8601-tests.el ends here