mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-02-19 19:37:58 +00:00
Add an ISO 8601 parsing library
* doc/lispref/os.texi (Time Parsing): Document it. * lisp/calendar/iso8601.el: New file. * test/lisp/calendar/iso8601-tests.el: Test ISO8601 parsing functions.
This commit is contained in:
parent
6cfda69d72
commit
fa04c8b87e
4 changed files with 683 additions and 0 deletions
|
|
@ -1622,6 +1622,19 @@ ISO 8601 string, like ``Fri, 25 Mar 2016 16:24:56 +0100'' or
|
|||
less well-formed time strings as well.
|
||||
@end defun
|
||||
|
||||
@vindex ISO 8601 date/time strings
|
||||
@defun iso8601-parse string
|
||||
For a more strict function (that will error out upon invalid input),
|
||||
this function can be used instead. It's able to parse all variants of
|
||||
the ISO 8601 standard, so in addition to the formats mentioned above,
|
||||
it also parses things like ``1998W45-3'' (week number) and
|
||||
``1998-245'' (ordinal day number). To parse durations, there's
|
||||
@code{iso8601-parse-duration}, and to parse intervals, there's
|
||||
@code{iso8601-parse-interval}. All these functions return decoded
|
||||
time structures, except the final one, which returns three of them
|
||||
(the start, the end, and the duration).
|
||||
@end defun
|
||||
|
||||
@defun format-time-string format-string &optional time zone
|
||||
|
||||
This function converts @var{time} (or the current time, if
|
||||
|
|
|
|||
9
etc/NEWS
9
etc/NEWS
|
|
@ -2055,6 +2055,15 @@ of various forms, including a new timestamp form '(TICKS . HZ)', where
|
|||
TICKS is an integer and HZ is a positive integer denoting a clock
|
||||
frequency. The old 'encode-time' API is still supported.
|
||||
|
||||
+++
|
||||
*** A new package to parse ISO 8601 time, date, durations and
|
||||
intervals has been added. The main function to use is
|
||||
'iso8601-parse', but there's also 'iso8601-parse-date',
|
||||
'iso8601-parse-time', 'iso8601-parse-duration' and
|
||||
'iso8601-parse-interval'. All these functions return decoded time
|
||||
structures, except the final one, which returns three of them (start,
|
||||
end and duration).
|
||||
|
||||
+++
|
||||
*** 'time-add', 'time-subtract', and 'time-less-p' now accept
|
||||
infinities and NaNs too, and propagate them or return nil like
|
||||
|
|
|
|||
370
lisp/calendar/iso8601.el
Normal file
370
lisp/calendar/iso8601.el
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
;;; iso8601.el --- parse ISO 8601 date/time strings -*- lexical-binding:t -*-
|
||||
|
||||
;; Copyright (C) 2019 Free Software Foundation, Inc.
|
||||
|
||||
;; Keywords: dates
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; ISO8601 times basically look like 1985-04-01T15:23:49... Or so
|
||||
;; you'd think. This is what everybody means when they say "ISO8601",
|
||||
;; but it's in reality a quite large collection of syntaxes, including
|
||||
;; week numbers, ordinal dates, durations and intervals. This package
|
||||
;; has functions for parsing them all.
|
||||
;;
|
||||
;; The interface functions are `iso8601-parse', `iso8601-parse-date',
|
||||
;; `iso8601-parse-time', `iso8601-parse-zone',
|
||||
;; `iso8601-parse-duration' and `iso8601-parse-interval'. They all
|
||||
;; return decoded time objects, except the last one, which returns a
|
||||
;; list of three of them.
|
||||
;;
|
||||
;; (iso8601-parse-interval "P1Y2M10DT2H30M/2008W32T153000-01")
|
||||
;; '((0 0 13 24 5 2007 nil nil -3600)
|
||||
;; (0 30 15 3 8 2008 nil nil -3600)
|
||||
;; (0 30 2 10 2 1 nil nil nil))
|
||||
;;
|
||||
;;
|
||||
;; The standard can be found at:
|
||||
;;
|
||||
;; http://www.loc.gov/standards/datetime/iso-tc154-wg5_n0038_iso_wd_8601-1_2016-02-16.pdf
|
||||
;;
|
||||
;; The Wikipedia page on the standard is also informative:
|
||||
;;
|
||||
;; https://en.wikipedia.org/wiki/ISO_8601
|
||||
;;
|
||||
;; RFC3339 defines the subset that everybody thinks of as "ISO8601".
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'time-date)
|
||||
(require 'cl-lib)
|
||||
|
||||
(defun iso8601--concat-regexps (regexps)
|
||||
(mapconcat (lambda (regexp)
|
||||
(concat "\\(?:"
|
||||
(replace-regexp-in-string "(" "(?:" regexp)
|
||||
"\\)"))
|
||||
regexps "\\|"))
|
||||
|
||||
(defconst iso8601--year-match
|
||||
"\\([-+]\\)?\\([0-9][0-9][0-9][0-9]\\)")
|
||||
(defconst iso8601--full-date-match
|
||||
"\\([-+]\\)?\\([0-9][0-9][0-9][0-9]\\)-?\\([0-9][0-9]\\)-?\\([0-9][0-9]\\)")
|
||||
(defconst iso8601--without-day-match
|
||||
"\\([-+]\\)?\\([0-9][0-9][0-9][0-9]\\)-\\([0-9][0-9]\\)")
|
||||
(defconst iso8601--outdated-date-match
|
||||
"--\\([0-9][0-9]\\)-?\\([0-9][0-9]\\)")
|
||||
(defconst iso8601--week-date-match
|
||||
"\\([-+]\\)?\\([0-9][0-9][0-9][0-9]\\)-?W\\([0-9][0-9]\\)-?\\([0-9]\\)?")
|
||||
(defconst iso8601--ordinal-date-match
|
||||
"\\([-+]\\)?\\([0-9][0-9][0-9][0-9]\\)-?\\([0-9][0-9][0-9]\\)")
|
||||
(defconst iso8601--date-match
|
||||
(iso8601--concat-regexps
|
||||
(list iso8601--year-match
|
||||
iso8601--full-date-match
|
||||
iso8601--without-day-match
|
||||
iso8601--outdated-date-match
|
||||
iso8601--week-date-match
|
||||
iso8601--ordinal-date-match)))
|
||||
|
||||
(defconst iso8601--time-match
|
||||
"\\([0-9][0-9]\\):?\\([0-9][0-9]\\)?:?\\([0-9][0-9]\\)?\\.?\\([0-9][0-9][0-9]\\)?")
|
||||
|
||||
(defconst iso8601--zone-match
|
||||
"\\(Z\\|\\([-+]\\)\\([0-9][0-9]\\):?\\([0-9][0-9]\\)?\\)")
|
||||
|
||||
(defconst iso8601--full-time-match
|
||||
(concat "\\(" (replace-regexp-in-string "(" "(?:" iso8601--time-match) "\\)"
|
||||
"\\(" iso8601--zone-match "\\)?"))
|
||||
|
||||
(defconst iso8601--combined-match
|
||||
(concat "\\(" iso8601--date-match "\\)"
|
||||
"\\(?:T\\("
|
||||
(replace-regexp-in-string "(" "(?:" iso8601--time-match)
|
||||
"\\)"
|
||||
"\\(" iso8601--zone-match "\\)?\\)?"))
|
||||
|
||||
(defconst iso8601--duration-full-match
|
||||
"P\\([0-9]+Y\\)?\\([0-9]+M\\)?\\([0-9]+D\\)?\\(T\\([0-9]+H\\)?\\([0-9]+M\\)?\\([0-9]+S\\)?\\)?")
|
||||
(defconst iso8601--duration-week-match
|
||||
"P\\([0-9]+\\)W")
|
||||
(defconst iso8601--duration-combined-match
|
||||
(concat "P" iso8601--combined-match))
|
||||
(defconst iso8601--duration-match
|
||||
(iso8601--concat-regexps
|
||||
(list iso8601--duration-full-match
|
||||
iso8601--duration-week-match
|
||||
iso8601--duration-combined-match)))
|
||||
|
||||
(defun iso8601-parse (string)
|
||||
"Parse an ISO 8601 date/time string and return a `decoded-time' structure.
|
||||
|
||||
The ISO 8601 date/time strings look like \"2008-03-02T13:47:30\",
|
||||
but shorter, incomplete strings like \"2008-03-02\" are valid, as
|
||||
well as variants like \"2008W32\" (week number) and
|
||||
\"2008-234\" (ordinal day number)."
|
||||
(if (not (iso8601-valid-p string))
|
||||
(signal 'wrong-type-argument string)
|
||||
(let* ((date-string (match-string 1 string))
|
||||
(time-string (match-string 2 string))
|
||||
(zone-string (match-string 3 string))
|
||||
(date (iso8601-parse-date date-string)))
|
||||
;; The time portion is optional.
|
||||
(when time-string
|
||||
(let ((time (iso8601-parse-time time-string)))
|
||||
(setf (decoded-time-hour date) (decoded-time-hour time))
|
||||
(setf (decoded-time-minute date) (decoded-time-minute time))
|
||||
(setf (decoded-time-second date) (decoded-time-second time))))
|
||||
;; The time zone is optional.
|
||||
(when zone-string
|
||||
(setf (decoded-time-zone date)
|
||||
;; The time zone in decoded times are in seconds.
|
||||
(* (iso8601-parse-zone zone-string) 60)))
|
||||
date)))
|
||||
|
||||
(defun iso8601-parse-date (string)
|
||||
"Parse STRING (which should be on ISO 8601 format) and return a time value."
|
||||
(cond
|
||||
;; Just a year: [-+]YYYY.
|
||||
((iso8601--match iso8601--year-match string)
|
||||
(iso8601--decoded-time
|
||||
:year (iso8601--adjust-year (match-string 1 string)
|
||||
(match-string 2 string))))
|
||||
;; Calendar dates: YYYY-MM-DD and variants.
|
||||
((iso8601--match iso8601--full-date-match string)
|
||||
(iso8601--decoded-time
|
||||
:year (iso8601--adjust-year (match-string 1 string)
|
||||
(match-string 2 string))
|
||||
:month (match-string 3 string)
|
||||
:day (match-string 4 string)))
|
||||
;; Calendar date without day: YYYY-MM.
|
||||
((iso8601--match iso8601--without-day-match string)
|
||||
(iso8601--decoded-time
|
||||
:year (iso8601--adjust-year (match-string 1 string)
|
||||
(match-string 2 string))
|
||||
:month (match-string 3 string)))
|
||||
;; Outdated date without year: --MM-DD
|
||||
((iso8601--match iso8601--outdated-date-match string)
|
||||
(iso8601--decoded-time
|
||||
:month (match-string 1 string)
|
||||
:day (match-string 2 string)))
|
||||
;; Week dates: YYYY-Www-D
|
||||
((iso8601--match iso8601--week-date-match string)
|
||||
(let* ((year (iso8601--adjust-year (match-string 1 string)
|
||||
(match-string 2 string)))
|
||||
(week (string-to-number (match-string 3 string)))
|
||||
(day-of-week (and (match-string 4 string)
|
||||
(string-to-number (match-string 4 string))))
|
||||
(jan-start (decoded-time-weekday
|
||||
(decode-time
|
||||
(iso8601--encode-time
|
||||
(iso8601--decoded-time :year year
|
||||
:month 1
|
||||
:day 4)))))
|
||||
(correction (+ (if (zerop jan-start) 7 jan-start)
|
||||
3))
|
||||
(ordinal (+ (* week 7) (or day-of-week 0) (- correction))))
|
||||
(cond
|
||||
;; Monday 29 December 2008 is written "2009-W01-1".
|
||||
((< ordinal 1)
|
||||
(setq year (1- year)
|
||||
ordinal (+ ordinal (if (date-leap-year-p year)
|
||||
366 365))))
|
||||
;; Sunday 3 January 2010 is written "2009-W53-7".
|
||||
((> ordinal (if (date-leap-year-p year)
|
||||
366 365))
|
||||
(setq ordinal (- ordinal (if (date-leap-year-p year)
|
||||
366 365))
|
||||
year (1+ year))))
|
||||
(let ((month-day (date-ordinal-to-time year ordinal)))
|
||||
(iso8601--decoded-time :year year
|
||||
:month (decoded-time-month month-day)
|
||||
:day (decoded-time-day month-day)))))
|
||||
;; Ordinal dates: YYYY-DDD
|
||||
((iso8601--match iso8601--ordinal-date-match string)
|
||||
(let* ((year (iso8601--adjust-year (match-string 1 string)
|
||||
(match-string 2 string)))
|
||||
(ordinal (string-to-number (match-string 3 string)))
|
||||
(month-day (date-ordinal-to-time year ordinal)))
|
||||
(iso8601--decoded-time :year year
|
||||
:month (decoded-time-month month-day)
|
||||
:day (decoded-time-day month-day))))
|
||||
(t
|
||||
(signal 'wrong-type-argument string))))
|
||||
|
||||
(defun iso8601--adjust-year (sign year)
|
||||
(save-match-data
|
||||
(let ((year (if (stringp year)
|
||||
(string-to-number year)
|
||||
year)))
|
||||
(if (string= sign "-")
|
||||
;; -0001 is 2 BCE.
|
||||
(1- (- year))
|
||||
year))))
|
||||
|
||||
(defun iso8601-parse-time (string)
|
||||
"Parse STRING, which should be an ISO 8601 time string, and return a time value."
|
||||
(if (not (iso8601--match iso8601--full-time-match string))
|
||||
(signal 'wrong-type-argument string)
|
||||
(let ((time (match-string 1 string))
|
||||
(zone (match-string 2 string)))
|
||||
(if (not (iso8601--match iso8601--time-match time))
|
||||
(signal 'wrong-type-argument string)
|
||||
(let ((hour (string-to-number (match-string 1 time)))
|
||||
(minute (and (match-string 2 time)
|
||||
(string-to-number (match-string 2 time))))
|
||||
(second (and (match-string 3 time)
|
||||
(string-to-number (match-string 3 time))))
|
||||
;; Hm...
|
||||
(_millisecond (and (match-string 4 time)
|
||||
(string-to-number (match-string 4 time)))))
|
||||
(iso8601--decoded-time :hour hour
|
||||
:minute (or minute 0)
|
||||
:second (or second 0)
|
||||
:zone (and zone
|
||||
(* 60 (iso8601-parse-zone
|
||||
zone)))))))))
|
||||
|
||||
(defun iso8601-parse-zone (string)
|
||||
"Parse STRING, which should be an ISO 8601 time zone.
|
||||
Return the number of minutes."
|
||||
(if (not (iso8601--match iso8601--zone-match string))
|
||||
(signal 'wrong-type-argument string)
|
||||
(if (match-string 2 string)
|
||||
;; HH:MM-ish.
|
||||
(let ((hour (string-to-number (match-string 3 string)))
|
||||
(minute (and (match-string 4 string)
|
||||
(string-to-number (match-string 4 string)))))
|
||||
(* (if (equal (match-string 2 string) "-")
|
||||
-1
|
||||
1)
|
||||
(+ (* hour 60)
|
||||
(or minute 0))))
|
||||
;; "Z".
|
||||
0)))
|
||||
|
||||
(defun iso8601-valid-p (string)
|
||||
"Say whether STRING is a valid ISO 8601 representation."
|
||||
(iso8601--match iso8601--combined-match string))
|
||||
|
||||
(defun iso8601-parse-duration (string)
|
||||
"Parse ISO 8601 durations on the form P3Y6M4DT12H30M5S."
|
||||
(cond
|
||||
((and (iso8601--match iso8601--duration-full-match string)
|
||||
;; Just a "P" isn't valid; there has to be at least one
|
||||
;; element, like P1M.
|
||||
(> (length (match-string 0 string)) 2))
|
||||
(iso8601--decoded-time :year (or (match-string 1 string) 0)
|
||||
:month (or (match-string 2 string) 0)
|
||||
:day (or (match-string 3 string) 0)
|
||||
:hour (or (match-string 5 string) 0)
|
||||
:minute (or (match-string 6 string) 0)
|
||||
:second (or (match-string 7 string) 0)))
|
||||
;; PnW: Weeks.
|
||||
((iso8601--match iso8601--duration-week-match string)
|
||||
(let ((weeks (string-to-number (match-string 1 string))))
|
||||
;; Does this make sense? Hm...
|
||||
(iso8601--decoded-time :day (* weeks 7))))
|
||||
;; P<date>T<time>
|
||||
((iso8601--match iso8601--duration-combined-match string)
|
||||
(iso8601-parse (substring string 1)))
|
||||
(t
|
||||
(signal 'wrong-type-argument string))))
|
||||
|
||||
(defun iso8601-parse-interval (string)
|
||||
"Parse ISO 8601 intervals."
|
||||
(let ((bits (split-string string "/"))
|
||||
start end duration)
|
||||
(if (not (= (length bits) 2))
|
||||
(signal 'wrong-type-argument string)
|
||||
;; The intervals may be an explicit start/end times, or either a
|
||||
;; start or an end, and an accompanying duration.
|
||||
(cond
|
||||
((and (string-match "\\`P" (car bits))
|
||||
(iso8601-valid-p (cadr bits)))
|
||||
(setq duration (iso8601-parse-duration (car bits))
|
||||
end (iso8601-parse (cadr bits))))
|
||||
((and (string-match "\\`P" (cadr bits))
|
||||
(iso8601-valid-p (car bits)))
|
||||
(setq duration (iso8601-parse-duration (cadr bits))
|
||||
start (iso8601-parse (car bits))))
|
||||
((and (iso8601-valid-p (car bits))
|
||||
(iso8601-valid-p (cadr bits)))
|
||||
(setq start (iso8601-parse (car bits))
|
||||
end (iso8601-parse (cadr bits))))
|
||||
(t
|
||||
(signal 'wrong-type-argument string))))
|
||||
(unless end
|
||||
(setq end (decoded-time-add start duration)))
|
||||
(unless start
|
||||
(setq start (decoded-time-add end
|
||||
;; We negate the duration so that
|
||||
;; we get a subtraction.
|
||||
(mapcar (lambda (elem)
|
||||
(if (numberp elem)
|
||||
(- elem)
|
||||
elem))
|
||||
duration))))
|
||||
(list start end
|
||||
(or duration
|
||||
(decode-time (time-subtract (iso8601--encode-time end)
|
||||
(iso8601--encode-time start))
|
||||
(or (decoded-time-zone end) 0))))))
|
||||
|
||||
(defun iso8601--match (regexp string)
|
||||
(string-match (concat "\\`" regexp "\\'") string))
|
||||
|
||||
(defun iso8601--value (elem &optional default)
|
||||
(if (stringp elem)
|
||||
(string-to-number elem)
|
||||
(or elem default)))
|
||||
|
||||
(cl-defun iso8601--decoded-time (&key second minute hour
|
||||
day month year
|
||||
dst zone)
|
||||
(list (iso8601--value second)
|
||||
(iso8601--value minute)
|
||||
(iso8601--value hour)
|
||||
(iso8601--value day)
|
||||
(iso8601--value month)
|
||||
(iso8601--value year)
|
||||
nil
|
||||
dst
|
||||
zone))
|
||||
|
||||
(defun iso8601--encode-time (time)
|
||||
"Like `encode-time', but fill in nil values in TIME."
|
||||
(setq time (copy-sequence time))
|
||||
(unless (decoded-time-second time)
|
||||
(setf (decoded-time-second time) 0))
|
||||
(unless (decoded-time-minute time)
|
||||
(setf (decoded-time-minute time) 0))
|
||||
(unless (decoded-time-hour time)
|
||||
(setf (decoded-time-hour time) 0))
|
||||
|
||||
(unless (decoded-time-day time)
|
||||
(setf (decoded-time-day time) 1))
|
||||
(unless (decoded-time-month time)
|
||||
(setf (decoded-time-month time) 1))
|
||||
(unless (decoded-time-year time)
|
||||
(setf (decoded-time-year time) 0))
|
||||
(encode-time time))
|
||||
|
||||
(provide 'iso8601)
|
||||
|
||||
;;; iso8601.el ends here
|
||||
291
test/lisp/calendar/iso8601-tests.el
Normal file
291
test/lisp/calendar/iso8601-tests.el
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
;;; iso8601-tests.el --- tests for calendar/iso8601.el -*- lexical-binding:t -*-
|
||||
|
||||
;; Copyright (C) 2019 Free Software Foundation, Inc.
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; GNU Emacs is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; GNU Emacs is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ert)
|
||||
(require 'iso8601)
|
||||
|
||||
(ert-deftest test-iso8601-date-years ()
|
||||
(should (equal (iso8601-parse-date "1985")
|
||||
'(nil nil nil nil nil 1985 nil nil nil)))
|
||||
(should (equal (iso8601-parse-date "-0003")
|
||||
'(nil nil nil nil nil -4 nil nil nil)))
|
||||
(should (equal (iso8601-parse-date "+1985")
|
||||
'(nil nil nil nil nil 1985 nil nil nil))))
|
||||
|
||||
(ert-deftest test-iso8601-date-dates ()
|
||||
(should (equal (iso8601-parse-date "1985-03-14")
|
||||
'(nil nil nil 14 3 1985 nil nil nil)))
|
||||
(should (equal (iso8601-parse-date "19850314")
|
||||
'(nil nil nil 14 3 1985 nil nil nil)))
|
||||
(should (equal (iso8601-parse-date "1985-02")
|
||||
'(nil nil nil nil 2 1985 nil nil nil))))
|
||||
|
||||
(ert-deftest test-iso8601-date-obsolete ()
|
||||
(should (equal (iso8601-parse-date "--02-01")
|
||||
'(nil nil nil 1 2 nil nil nil nil)))
|
||||
(should (equal (iso8601-parse-date "--0201")
|
||||
'(nil nil nil 1 2 nil nil nil nil))))
|
||||
|
||||
(ert-deftest test-iso8601-date-weeks ()
|
||||
(should (equal (iso8601-parse-date "2008W39-6")
|
||||
'(nil nil nil 27 9 2008 nil nil nil)))
|
||||
(should (equal (iso8601-parse-date "2009W01-1")
|
||||
'(nil nil nil 29 12 2008 nil nil nil)))
|
||||
(should (equal (iso8601-parse-date "2009W53-7")
|
||||
'(nil nil nil 3 1 2010 nil nil nil))))
|
||||
|
||||
(ert-deftest test-iso8601-date-ordinals ()
|
||||
(should (equal (iso8601-parse-date "1981-095")
|
||||
'(nil nil nil 5 4 1981 nil nil nil))))
|
||||
|
||||
(ert-deftest test-iso8601-time ()
|
||||
(should (equal (iso8601-parse-time "13:47:30")
|
||||
'(30 47 13 nil nil nil nil nil nil)))
|
||||
(should (equal (iso8601-parse-time "134730")
|
||||
'(30 47 13 nil nil nil nil nil nil)))
|
||||
(should (equal (iso8601-parse-time "1347")
|
||||
'(0 47 13 nil nil nil nil nil nil))))
|
||||
|
||||
(ert-deftest test-iso8601-combined ()
|
||||
(should (equal (iso8601-parse "2008-03-02T13:47:30")
|
||||
'(30 47 13 2 3 2008 nil nil nil)))
|
||||
(should (equal (iso8601-parse "2008-03-02T13:47:30Z")
|
||||
'(30 47 13 2 3 2008 nil nil 0)))
|
||||
(should (equal (iso8601-parse "2008-03-02T13:47:30+01:00")
|
||||
'(30 47 13 2 3 2008 nil nil 3600)))
|
||||
(should (equal (iso8601-parse "2008-03-02T13:47:30-01")
|
||||
'(30 47 13 2 3 2008 nil nil -3600))))
|
||||
|
||||
(ert-deftest test-iso8601-duration ()
|
||||
(should (equal (iso8601-parse-duration "P3Y6M4DT12H30M5S")
|
||||
'(5 30 12 4 6 3 nil nil nil)))
|
||||
(should (equal (iso8601-parse-duration "P1M")
|
||||
'(0 0 0 0 1 0 nil nil nil)))
|
||||
(should (equal (iso8601-parse-duration "PT1M")
|
||||
'(0 1 0 0 0 0 nil nil nil)))
|
||||
(should (equal (iso8601-parse-duration "P0003-06-04T12:30:05")
|
||||
'(5 30 12 4 6 3 nil nil nil))))
|
||||
|
||||
(ert-deftest test-iso8601-invalid ()
|
||||
(should-not (iso8601-valid-p " 2008-03-02T13:47:30-01"))
|
||||
(should-not (iso8601-valid-p "2008-03-02T13:47:30-01:200"))
|
||||
(should-not (iso8601-valid-p "2008-03-02T13:47:30-01 "))
|
||||
(should-not (iso8601-valid-p "2008-03-02 T 13:47:30-01 "))
|
||||
(should-not (iso8601-valid-p "20008-03-02T13:47:30-01")))
|
||||
|
||||
(ert-deftest test-iso8601-intervals ()
|
||||
(should (equal
|
||||
(iso8601-parse-interval "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z")
|
||||
'((0 0 13 1 3 2007 nil nil 0)
|
||||
(0 30 15 11 5 2008 nil nil 0)
|
||||
;; Hm... can't really use decode-time for time differences...
|
||||
(0 30 2 14 3 1971 0 nil 0))))
|
||||
(should (equal (iso8601-parse-interval "2007-03-01T13:00:00Z/P1Y2M10DT2H30M")
|
||||
'((0 0 13 1 3 2007 nil nil 0)
|
||||
(0 30 15 11 5 2008 nil nil 0)
|
||||
(0 30 2 10 2 1 nil nil nil))))
|
||||
(should (equal (iso8601-parse-interval "P1Y2M10DT2H30M/2008-05-11T15:30:00Z")
|
||||
'((0 0 13 1 3 2007 nil nil 0)
|
||||
(0 30 15 11 5 2008 nil nil 0)
|
||||
(0 30 2 10 2 1 nil nil nil)))))
|
||||
|
||||
(ert-deftest standard-test-dates ()
|
||||
(should (equal (iso8601-parse-date "19850412")
|
||||
'(nil nil nil 12 4 1985 nil nil nil)))
|
||||
(should (equal (iso8601-parse-date "1985-04-12")
|
||||
'(nil nil nil 12 4 1985 nil nil nil)))
|
||||
|
||||
(should (equal (iso8601-parse-date "1985102")
|
||||
'(nil nil nil 12 4 1985 nil nil nil)))
|
||||
(should (equal (iso8601-parse-date "1985-102")
|
||||
'(nil nil nil 12 4 1985 nil nil nil)))
|
||||
|
||||
(should (equal (iso8601-parse-date "1985W155")
|
||||
'(nil nil nil 12 4 1985 nil nil nil)))
|
||||
(should (equal (iso8601-parse-date "1985-W15-5")
|
||||
'(nil nil nil 12 4 1985 nil nil nil)))
|
||||
|
||||
(should (equal (iso8601-parse-date "1985W15")
|
||||
'(nil nil nil 7 4 1985 nil nil nil)))
|
||||
(should (equal (iso8601-parse-date "1985-W15")
|
||||
'(nil nil nil 7 4 1985 nil nil nil)))
|
||||
|
||||
(should (equal (iso8601-parse-date "1985-04")
|
||||
'(nil nil nil nil 4 1985 nil nil nil)))
|
||||
|
||||
(should (equal (iso8601-parse-date "1985")
|
||||
'(nil nil nil nil nil 1985 nil nil nil)))
|
||||
|
||||
(should (equal (iso8601-parse-date "+1985-04-12")
|
||||
'(nil nil nil 12 4 1985 nil nil nil)))
|
||||
(should (equal (iso8601-parse-date "+19850412")
|
||||
'(nil nil nil 12 4 1985 nil nil nil))))
|
||||
|
||||
(ert-deftest standard-test-time-of-day-local-time ()
|
||||
(should (equal (iso8601-parse-time "152746")
|
||||
'(46 27 15 nil nil nil nil nil nil)))
|
||||
(should (equal (iso8601-parse-time "15:27:46")
|
||||
'(46 27 15 nil nil nil nil nil nil)))
|
||||
|
||||
(should (equal (iso8601-parse-time "1528")
|
||||
'(0 28 15 nil nil nil nil nil nil)))
|
||||
(should (equal (iso8601-parse-time "15:28")
|
||||
'(0 28 15 nil nil nil nil nil nil)))
|
||||
|
||||
(should (equal (iso8601-parse-time "15")
|
||||
'(0 0 15 nil nil nil nil nil nil))))
|
||||
|
||||
(ert-deftest standard-test-time-of-day-fractions ()
|
||||
;; decoded-time doesn't support sub-second times.
|
||||
;; (should (equal (iso8601-parse-time "152735,5")
|
||||
;; '(46 27 15 nil nil nil nil nil nil)))
|
||||
;; (should (equal (iso8601-parse-time "15:27:35,5")
|
||||
;; '(46 27 15 nil nil nil nil nil nil)))
|
||||
)
|
||||
|
||||
(ert-deftest standard-test-time-of-day-beginning-of-day ()
|
||||
(should (equal (iso8601-parse-time "000000")
|
||||
'(0 0 0 nil nil nil nil nil nil)))
|
||||
(should (equal (iso8601-parse-time "00:00:00")
|
||||
'(0 0 0 nil nil nil nil nil nil)))
|
||||
|
||||
(should (equal (iso8601-parse-time "0000")
|
||||
'(0 0 0 nil nil nil nil nil nil)))
|
||||
(should (equal (iso8601-parse-time "00:00")
|
||||
'(0 0 0 nil nil nil nil nil nil))))
|
||||
|
||||
(ert-deftest standard-test-time-of-day-utc ()
|
||||
(should (equal (iso8601-parse-time "232030Z")
|
||||
'(30 20 23 nil nil nil nil nil 0)))
|
||||
(should (equal (iso8601-parse-time "23:20:30Z")
|
||||
'(30 20 23 nil nil nil nil nil 0)))
|
||||
|
||||
(should (equal (iso8601-parse-time "2320Z")
|
||||
'(0 20 23 nil nil nil nil nil 0)))
|
||||
(should (equal (iso8601-parse-time "23:20Z")
|
||||
'(0 20 23 nil nil nil nil nil 0)))
|
||||
|
||||
(should (equal (iso8601-parse-time "23Z")
|
||||
'(0 0 23 nil nil nil nil nil 0))))
|
||||
|
||||
|
||||
(ert-deftest standard-test-time-of-day-zone ()
|
||||
(should (equal (iso8601-parse-time "152746+0100")
|
||||
'(46 27 15 nil nil nil nil nil 3600)))
|
||||
(should (equal (iso8601-parse-time "15:27:46+0100")
|
||||
'(46 27 15 nil nil nil nil nil 3600)))
|
||||
|
||||
(should (equal (iso8601-parse-time "152746+01")
|
||||
'(46 27 15 nil nil nil nil nil 3600)))
|
||||
(should (equal (iso8601-parse-time "15:27:46+01")
|
||||
'(46 27 15 nil nil nil nil nil 3600)))
|
||||
|
||||
(should (equal (iso8601-parse-time "152746-0500")
|
||||
'(46 27 15 nil nil nil nil nil -18000)))
|
||||
(should (equal (iso8601-parse-time "15:27:46-0500")
|
||||
'(46 27 15 nil nil nil nil nil -18000)))
|
||||
|
||||
(should (equal (iso8601-parse-time "152746-05")
|
||||
'(46 27 15 nil nil nil nil nil -18000)))
|
||||
(should (equal (iso8601-parse-time "15:27:46-05")
|
||||
'(46 27 15 nil nil nil nil nil -18000))))
|
||||
|
||||
(ert-deftest standard-test-date-and-time-of-day ()
|
||||
(should (equal (iso8601-parse "19850412T101530")
|
||||
'(30 15 10 12 4 1985 nil nil nil)))
|
||||
(should (equal (iso8601-parse "1985-04-12T10:15:30")
|
||||
'(30 15 10 12 4 1985 nil nil nil)))
|
||||
|
||||
(should (equal (iso8601-parse "1985102T235030Z")
|
||||
'(30 50 23 12 4 1985 nil nil 0)))
|
||||
(should (equal (iso8601-parse "1985-102T23:50:30Z")
|
||||
'(30 50 23 12 4 1985 nil nil 0)))
|
||||
|
||||
(should (equal (iso8601-parse "1985W155T235030")
|
||||
'(30 50 23 12 4 1985 nil nil nil)))
|
||||
(should (equal (iso8601-parse "1985-W155T23:50:30")
|
||||
'(30 50 23 12 4 1985 nil nil nil))))
|
||||
|
||||
(ert-deftest standard-test-interval ()
|
||||
;; A time interval starting at 20 minutes and 50 seconds past 23
|
||||
;; hours on 12 April 1985 and ending at 30 minutes past 10 hours on
|
||||
;; 25 June 1985.
|
||||
(should (equal (iso8601-parse-interval "19850412T232050/19850625T103000")
|
||||
'((50 20 23 12 4 1985 nil nil nil)
|
||||
(0 30 10 25 6 1985 nil nil nil)
|
||||
(10 9 11 15 3 1970 0 nil 0))))
|
||||
(should (equal (iso8601-parse-interval
|
||||
"1985-04-12T23:20:50/1985-06-25T10:30:00")
|
||||
'((50 20 23 12 4 1985 nil nil nil)
|
||||
(0 30 10 25 6 1985 nil nil nil)
|
||||
(10 9 11 15 3 1970 0 nil 0))))
|
||||
|
||||
;; A time interval starting at 12 April 1985 and ending on 25 June
|
||||
;; 1985.
|
||||
|
||||
;; This example doesn't seem valid according to the standard.
|
||||
;; "0625" is unambiguous, and means "the year 625". Weird.
|
||||
;; (should (equal (iso8601-parse-interval "19850412/0625")
|
||||
;; '((nil nil nil 12 4 1985 nil nil nil)
|
||||
;; (nil nil nil nil nil 625 nil nil nil)
|
||||
;; (0 17 0 22 9 609 5 nil 0))))
|
||||
|
||||
;; A time interval of 2 years, 10 months, 15 days, 10 hours, 20
|
||||
;; minutes and 30 seconds.
|
||||
(should (equal (iso8601-parse-duration "P2Y10M15DT10H20M30S")
|
||||
'(30 20 10 15 10 2 nil nil nil)))
|
||||
|
||||
(should (equal (iso8601-parse-duration "P00021015T102030")
|
||||
'(30 20 10 15 10 2 nil nil nil)))
|
||||
(should (equal (iso8601-parse-duration "P0002-10-15T10:20:30")
|
||||
'(30 20 10 15 10 2 nil nil nil)))
|
||||
|
||||
;; A time interval of 1 year and 6 months.
|
||||
(should (equal (iso8601-parse-duration "P1Y6M")
|
||||
'(0 0 0 0 6 1 nil nil nil)))
|
||||
(should (equal (iso8601-parse-duration "P0001-06")
|
||||
'(nil nil nil nil 6 1 nil nil nil)))
|
||||
|
||||
;; A time interval of seventy-two hours.
|
||||
(should (equal (iso8601-parse-duration "PT72H")
|
||||
'(0 0 72 0 0 0 nil nil nil)))
|
||||
|
||||
;; Defined by start and duration
|
||||
;; A time interval of 1 year, 2 months, 15 days and 12 hours,
|
||||
;; beginning on 12 April 1985 at 20 minutes past 23 hours.
|
||||
(should (equal (iso8601-parse-interval "19850412T232000/P1Y2M15DT12H")
|
||||
'((0 20 23 12 4 1985 nil nil nil)
|
||||
(0 20 11 28 6 1986 nil nil nil)
|
||||
(0 0 12 15 2 1 nil nil nil))))
|
||||
(should (equal (iso8601-parse-interval "1985-04-12T23:20:00/P1Y2M15DT12H")
|
||||
'((0 20 23 12 4 1985 nil nil nil)
|
||||
(0 20 11 28 6 1986 nil nil nil)
|
||||
(0 0 12 15 2 1 nil nil nil))))
|
||||
|
||||
;; Defined by duration and end
|
||||
;; A time interval of 1 year, 2 months, 15 days and 12 hours, ending
|
||||
;; on 12 April 1985 at 20 minutes past 23 hour.
|
||||
(should (equal (iso8601-parse-interval "P1Y2M15DT12H/19850412T232000")
|
||||
'((0 20 11 28 1 1984 nil nil nil)
|
||||
(0 20 23 12 4 1985 nil nil nil)
|
||||
(0 0 12 15 2 1 nil nil nil)))))
|
||||
|
||||
;;; iso8601-tests.el ends here
|
||||
Loading…
Reference in a new issue