From 0df01e3aa5f8372995bdc39be36c444c54a52f7e Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Sun, 29 Sep 2019 21:22:29 +0200 Subject: [PATCH] Add support for sub-second ISO8601 strings * lisp/calendar/iso8601.el (iso8601--decimalize): New function. (iso8601-parse-time): Support sub-second ISO8601 times. --- lisp/calendar/iso8601.el | 35 +++++++++++++++++++++----- test/lisp/calendar/iso8601-tests.el | 38 ++++++++++++++--------------- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/lisp/calendar/iso8601.el b/lisp/calendar/iso8601.el index f8949914f78..66446dafb96 100644 --- a/lisp/calendar/iso8601.el +++ b/lisp/calendar/iso8601.el @@ -112,12 +112,16 @@ iso8601--duration-combined-match))) (defun iso8601-parse (string) - "Parse an ISO 8601 date/time string and return a `decoded-time' structure. + "Parse an ISO 8601 date/time string and return a `decode-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)." +\"2008-234\" (ordinal day number). + +The `decode-time' value returned will have the same precision as +STRING, so if a sub-second STRING is passed in, the `decode-time' +seconds field will be on the (SECONDS . HZ) format." (if (not (iso8601-valid-p string)) (signal 'wrong-type-argument string) (let* ((date-string (match-string 1 string)) @@ -138,7 +142,7 @@ well as variants like \"2008W32\" (week number) and date))) (defun iso8601-parse-date (string) - "Parse STRING (in ISO 8601 format) and return a decoded time value." + "Parse STRING (in ISO 8601 format) and return a `decode-time' value." (cond ;; Just a year: [-+]YYYY. ((iso8601--match iso8601--year-match string) @@ -218,7 +222,9 @@ well as variants like \"2008W32\" (week number) and year)))) (defun iso8601-parse-time (string) - "Parse STRING, which should be an ISO 8601 time string, and return a time value." + "Parse STRING, which should be an ISO 8601 time string. +The return value will be a `decode-time' structure with just the +hour/minute/seconds/zone fields filled in." (if (not (iso8601--match iso8601--full-time-match string)) (signal 'wrong-type-argument string) (let ((time (match-string 1 string)) @@ -230,9 +236,22 @@ well as variants like \"2008W32\" (week number) and (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) + (fraction (and (match-string 4 time) (string-to-number (match-string 4 time))))) + (when fraction + (cond + ;; Sub-second time. + (second + (let ((digits (1+ (truncate (log fraction 10))))) + (setq second (cons (+ (* second (expt 10 digits)) + fraction) + (expt 10 digits))))) + ;; Fractional minute. + (minute + (setq second (iso8601--decimalize fraction 60))) + (hour + ;; Fractional hour. + (setq minute (iso8601--decimalize fraction 60))))) (iso8601--decoded-time :hour hour :minute (or minute 0) :second (or second 0) @@ -240,6 +259,10 @@ well as variants like \"2008W32\" (week number) and (* 60 (iso8601-parse-zone zone))))))))) +(defun iso8601--decimalize (fraction base) + (round (* base (/ (float fraction) + (expt 10 (1+ (truncate (log fraction 10)))))))) + (defun iso8601-parse-zone (string) "Parse STRING, which should be an ISO 8601 time zone. Return the number of minutes." diff --git a/test/lisp/calendar/iso8601-tests.el b/test/lisp/calendar/iso8601-tests.el index 8d2aec3de5a..1d44e947a77 100644 --- a/test/lisp/calendar/iso8601-tests.el +++ b/test/lisp/calendar/iso8601-tests.el @@ -153,25 +153,25 @@ (should (equal (iso8601-parse-time "15") '(0 0 15 nil nil nil nil nil nil)))) -;; Not implemented yet. - -;; (ert-deftest standard-test-time-of-day-fractions () -;; (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))) - -;; (should (equal (iso8601-parse-time "2320,8") -;; '(46 27 15 nil nil nil nil nil nil))) -;; (should (equal (iso8601-parse-time "23:20,8") -;; '(46 27 15 nil nil nil nil nil nil))) - -;; (should (equal (iso8601-parse-time "23,3") -;; '(46 27 15 nil nil nil nil nil nil)))) - -;; (ert-deftest nonstandard-test-time-of-day-decimals () -;; (should (equal (iso8601-parse-time "15:27:35.123") -;; '(46 27 15 nil nil nil nil nil nil)))) +(ert-deftest standard-test-time-of-day-fractions () + (should (equal (iso8601-parse-time "152735,5") + '((355 . 10) 27 15 nil nil nil nil nil nil))) + (should (equal (iso8601-parse-time "15:27:35,5") + '((355 . 10) 27 15 nil nil nil nil nil nil))) + + (should (equal (iso8601-parse-time "2320,5") + '(30 20 23 nil nil nil nil nil nil))) + (should (equal (iso8601-parse-time "23:20,8") + '(48 20 23 nil nil nil nil nil nil))) + + (should (equal (iso8601-parse-time "23,3") + '(0 18 23 nil nil nil nil nil nil)))) + +(ert-deftest nonstandard-test-time-of-day-decimals () + (should (equal (iso8601-parse-time "15:27:35.123") + '((35123 . 1000) 27 15 nil nil nil nil nil nil))) + (should (equal (iso8601-parse-time "15:27:35.123456789") + '((35123456789 . 1000000000) 27 15 nil nil nil nil nil nil)))) (ert-deftest standard-test-time-of-day-beginning-of-day () (should (equal (iso8601-parse-time "000000") -- 2.39.5