branch: externals/vcard commit f51f385e0a0a0fbe173415ffff460035c0c102e5 Author: Eric Abrahamsen <e...@ericabrahamsen.net> Commit: Eric Abrahamsen <e...@ericabrahamsen.net>
[vcard] Don't try to provide our own compat version of iso8601 * packages/vcard/vcard-iso8601.el: Too complicated. * packages/vcard/vcard-parse.el: Require Emacs 27, and load built-in iso8601 library. --- vcard-iso8601.el | 383 ------------------------------------------------------- vcard-parse.el | 9 +- 2 files changed, 3 insertions(+), 389 deletions(-) diff --git a/vcard-iso8601.el b/vcard-iso8601.el deleted file mode 100644 index 812ee45..0000000 --- a/vcard-iso8601.el +++ /dev/null @@ -1,383 +0,0 @@ -;;; vcard-iso8601.el --- compatibility library for older Emacs -*- lexical-binding:t -*- - -;; Copyright (C) 2019-2020 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: - -;; This is a copy of the iso8601.el library that exists in Emacs 27 -;; and later. It is loaded conditionally in earlier Emacs that lack -;; that library. - -;; 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--outdated-reduced-precision-date-match - "---?\\([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--outdated-reduced-precision-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]*\\)") - -(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 &optional form) - "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). - -See `decode-time' for the meaning of FORM." - (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 form))) - (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)) - (setf (decoded-time-dst date) nil)) - date))) - -(defun iso8601-parse-date (string) - "Parse STRING (in ISO 8601 format) and return a `decode-time' value." - (cond - ;; Just a year: [+-]YYYY. - ((iso8601--match iso8601--year-match string) - (iso8601--decoded-time - :year (string-to-number string))) - ;; Calendar dates: YYYY-MM-DD and variants. - ((iso8601--match iso8601--full-date-match string) - (iso8601--decoded-time - :year (string-to-number (match-string 1 string)) - :month (match-string 2 string) - :day (match-string 3 string))) - ;; Calendar date without day: YYYY-MM. - ((iso8601--match iso8601--without-day-match string) - (iso8601--decoded-time - :year (string-to-number string) - :month (match-string 2 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 (string-to-number string)) - (week (string-to-number (match-string 2 string))) - (day-of-week (and (match-string 3 string) - (string-to-number (match-string 3 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 (string-to-number (match-string 1 string))) - (ordinal (string-to-number (match-string 2 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)))) - ;; Obsolete format with implied year: --MM - ((iso8601--match "--\\([0-9][0-9]\\)" string) - (iso8601--decoded-time :month (string-to-number (match-string 1 string)))) - ;; Obsolete format with implied year and month: ---DD - ((iso8601--match "---\\([0-9][0-9]\\)" string) - (iso8601--decoded-time :day (string-to-number (match-string 1 string)))) - (t - (signal 'wrong-type-argument string)))) - -(defun iso8601-parse-time (string &optional form) - "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. - -See `decode-time' for the meaning of FORM." - (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)))) - (fraction (and (not (zerop (length (match-string 4 time)))) - (string-to-number (match-string 4 time))))) - (when (and fraction - (eq form t)) - (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) - :zone (and zone - (* 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." - (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 - ;; FIXME: Support subseconds. - ;; FIXME: It makes no sense to decode a time difference - ;; according to (decoded-time-zone end), or according to - ;; any other time zone for that matter. - (decode-time (time-subtract (iso8601--encode-time end) - (iso8601--encode-time start)) - (or (decoded-time-zone end) 0) 'integer))))) - -(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 - (if (or dst zone) dst -1) - zone)) - -(defun iso8601--encode-time (time) - "Like `encode-time', but fill in nil values in TIME." - (encode-time (decoded-time-set-defaults (copy-sequence time)))) - -(provide 'vcard-iso8601) - -;;; vcard-iso8601.el ends here diff --git a/vcard-parse.el b/vcard-parse.el index e9f7f30..3c239de 100644 --- a/vcard-parse.el +++ b/vcard-parse.el @@ -94,6 +94,7 @@ (require 'vcard) (require 'cl-lib) +(require 'iso8601) (defgroup vcard-parse nil "Customization options for vcard parsing." @@ -157,12 +158,8 @@ semicolons.") (defvar vcard-datetime-properties '(bday anniversary rev) "A list of vcard properties representing date or time values. The parsing process will make some attempt at converting these -values into lisp timestamps.") - -;; Maybe load our local version of iso8601. -(eval-when-compile - (unless (fboundp 'iso8601-parse) - (require 'vcard-iso8601))) +values into lisp time values, depending on the value of +`vcard-parse-datetime-values'.") ;;;###autoload (defun vcard-parse-file (file)