mirror of
git://git.sv.gnu.org/emacs.git
synced 2025-12-15 10:30:25 -08:00
* lisp/auth-source-pass.el (auth-source-pass--find-match-many): * lisp/calendar/time-date.el (decoded-time-add) (decoded-time--alter-month, decoded-time--alter-day): * lisp/dired.el (dired--move-to-next-line): * lisp/dom.el (dom-pp): * lisp/emacs-lisp/cl-macs.el (cl--transform-lambda): * lisp/emacs-lisp/cl-print.el (cl-print-to-string-with-limit): * lisp/emacs-lisp/cl-seq.el (cl-fill, cl-replace, cl-substitute): * lisp/emacs-lisp/comp-cstr.el (comp--range-union) (comp--range-intersection): * lisp/emacs-lisp/comp.el (comp-vec-prepend, comp--emit-narg-prologue): * lisp/emacs-lisp/edebug.el (edebug--strip-instrumentation): * lisp/emacs-lisp/eldoc.el (eldoc--invoke-strategy): * lisp/emacs-lisp/pp.el (pp--format-definition): * lisp/emacs-lisp/smie.el (smie-config--guess-1): * lisp/eshell/esh-io.el (eshell-close-handle, eshell-set-output-handle): * lisp/gnus/gnus-async.el (gnus-async-prefetch-article): * lisp/gnus/gnus-group.el (gnus-group-mark-group) (gnus-group-yank-group): * lisp/gnus/gnus-salt.el (gnus-tree-forward-line): * lisp/gnus/gnus-score.el (gnus-decay-scores): * lisp/gnus/gnus-srvr.el (gnus-browse-toggle-subscription-at-point): * lisp/gnus/gnus-sum.el (gnus-build-sparse-threads, gnus-parent-headers) (gnus-update-marks, gnus-summary-work-articles) (gnus-summary-refer-parent-article, gnus-summary-next-thread) (gnus-read-header, gnus-summary-insert-new-articles): * lisp/gnus/gnus-topic.el (gnus-topic-forward-topic): * lisp/gnus/gnus.el (gnus-short-group-name): * lisp/gnus/message.el (message-remove-first-header) (message-shorten-references, message-insert-screenshot): * lisp/gnus/mm-url.el (mm-url-insert): * lisp/gnus/nnselect.el (nnselect-push-info): * lisp/ibuffer.el (ibuffer-backward-line, ibuffer-forward-line) (ibuffer-map-lines): * lisp/icomplete.el (icomplete--render-vertical): * lisp/image/image-dired-external.el (image-dired-create-thumb-1): * lisp/image/image-dired.el (image-dired-delete-char): * lisp/mail/ietf-drums-date.el (ietf-drums-date--tokenize-string): * lisp/mh-e/mh-utils.el (mh-sub-folders-parse): * lisp/minibuffer.el (minibuffer-completion-help): * lisp/mpc.el (mpc-cmd-move, mpc-songpointer-refresh-hairy): * lisp/net/eww.el (eww-process-text-input): * lisp/net/pop3.el (pop3-wait-for-messages, pop3-uidl-stat) (pop3-uidl-dele): * lisp/net/shr-color.el (shr-color-hue-to-rgb): * lisp/play/5x5.el (5x5-up, 5x5-left): * lisp/play/decipher.el (decipher-read-alphabet, decipher--digram-total) (decipher-analyze-buffer): * lisp/play/hanoi.el (hanoi-insert-ring, hanoi-move-ring): * lisp/profiler.el (profiler-format-number) (profiler-calltree-build-unified): * lisp/progmodes/antlr-mode.el (antlr-next-rule, antlr-indent-line): * lisp/progmodes/c-ts-common.el (c-ts-common-statement-offset): * lisp/progmodes/ebrowse.el (ebrowse-cyclic-display-next/previous-member-list): * lisp/progmodes/hideif.el (hif-backward-comment): * lisp/progmodes/js.el (js-beginning-of-defun, js-end-of-defun) (js-ts--syntax-propertize): * lisp/progmodes/typescript-ts-mode.el (tsx-ts--syntax-propertize-captures): * lisp/rect.el (rectangle--*-char): * lisp/term.el (term-emulate-terminal): * lisp/textmodes/reftex-cite.el (reftex-do-citation): * lisp/textmodes/reftex-index.el (reftex-index-next-phrase): * lisp/textmodes/reftex-parse.el (reftex-init-section-numbers): * lisp/textmodes/reftex-sel.el (reftex-select-unmark): * lisp/textmodes/reftex.el (reftex-silence-toc-markers): * lisp/treesit.el (treesit-navigate-thing): * lisp/vc/diff-mode.el (diff-sanity-check-context-hunk-half, (diff-sanity-check-hunk): * lisp/vc/pcvs-util.el (cvs-first): * lisp/vc/smerge-mode.el (smerge-get-current): * lisp/vc/vc-hg.el (vc-hg--glob-to-pcre): * test/lisp/net/socks-tests.el (socks-tests-perform-hello-world-http-request): * test/src/buffer-tests.el (test-overlay-randomly): Prefer decf to cl-defc in all code where we can.
275 lines
12 KiB
EmacsLisp
275 lines
12 KiB
EmacsLisp
;;; ietf-drums-date.el --- parse time/date for ietf-drums.el -*- lexical-binding: t -*-
|
|
|
|
;; Copyright (C) 2022-2025 Free Software Foundation, Inc.
|
|
|
|
;; Author: Bob Rogers <rogers@rgrjr.com>
|
|
;; Package: ietf-drums
|
|
;; Keywords: mail, util
|
|
|
|
;; 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:
|
|
|
|
;; 'ietf-drums-parse-date-string' parses a time and/or date in a
|
|
;; string and returns a list of values, just like `decode-time', where
|
|
;; unspecified elements in the string are returned as nil (except
|
|
;; unspecified DST is returned as -1). `encode-time' may be applied
|
|
;; on these values to obtain an internal time value.
|
|
|
|
;; Historically, `parse-time-string' was used for this purpose, but it
|
|
;; was gradually but imperfectly extended to handle other date
|
|
;; formats. 'ietf-drums-parse-date-string' is compatible in that it
|
|
;; uses the same return value format and parses the same email date
|
|
;; formats by default, but can be made stricter if desired.
|
|
|
|
;;; Code:
|
|
|
|
(require 'cl-lib)
|
|
(require 'parse-time)
|
|
|
|
(define-error 'date-parse-error "Date/time parse error" 'error)
|
|
|
|
(defconst ietf-drums-date--slot-names
|
|
'(second minute hour day month year weekday dst zone)
|
|
"Names of return value slots, for better error messages
|
|
See the decoded-time defstruct.")
|
|
|
|
(defconst ietf-drums-date--slot-ranges
|
|
'((0 60) (0 59) (0 23) (1 31) (1 12) (1 9999))
|
|
"Numeric slot ranges, for bounds checking.
|
|
Note that RFC5322 explicitly requires that seconds go up to 60,
|
|
to allow for leap seconds (see Mills, D., \"Network Time
|
|
Protocol\", STD 12, RFC 1119, September 1989).")
|
|
|
|
(defsubst ietf-drums-date--ignore-char-p (char)
|
|
;; Ignore whitespace and commas.
|
|
(memq char '(?\s ?\t ?\r ?\n ?,)))
|
|
|
|
(defun ietf-drums-date--tokenize-string (string &optional comment-eof)
|
|
"Turn STRING into tokens, separated only by whitespace and commas.
|
|
Multiple commas are ignored. Pure digit sequences are turned
|
|
into integers. If COMMENT-EOF is true, then a comment as
|
|
defined by RFC5322 (strictly, the CFWS production that also
|
|
accepts comments) is treated as an end-of-file, and no further
|
|
tokens are recognized, otherwise we strip out all comments and
|
|
treat them as whitespace (per RFC822)."
|
|
(let ((index 0)
|
|
(end (length string))
|
|
(list ()))
|
|
(cl-flet ((skip-ignored ()
|
|
;; Skip ignored characters at index (the scan
|
|
;; position). Skip RFC822 comments in matched parens,
|
|
;; but do not complain about unterminated comments.
|
|
(let ((char nil)
|
|
(nest 0))
|
|
(while (and (< index end)
|
|
(setq char (aref string index))
|
|
(or (> nest 0)
|
|
(ietf-drums-date--ignore-char-p char)
|
|
(and (not comment-eof) (eql char ?\())))
|
|
(cl-incf index)
|
|
;; FWS bookkeeping.
|
|
(cond ((and (eq char ?\\)
|
|
(< (1+ index) end))
|
|
;; Move to the next char but don't check
|
|
;; it to see if it might be a paren.
|
|
(cl-incf index))
|
|
((eq char ?\() (cl-incf nest))
|
|
((eq char ?\)) (decf nest)))))))
|
|
(skip-ignored) ;; Skip leading whitespace.
|
|
(while (and (< index end)
|
|
(not (and comment-eof
|
|
(eq (aref string index) ?\())))
|
|
(let* ((start index)
|
|
(char (aref string index))
|
|
(all-digits (<= ?0 char ?9)))
|
|
;; char is valid; look for more valid characters.
|
|
(when (and (eq char ?\\)
|
|
(< (1+ index) end))
|
|
;; Escaped character, which might be a "(". If so, we are
|
|
;; correct to include it in the token, even though the
|
|
;; caller is sure to barf. If not, we violate RFC2?822 by
|
|
;; not removing the backslash, but no characters in valid
|
|
;; RFC2?822 dates need escaping anyway, so it shouldn't
|
|
;; matter that this is not done strictly correctly. --
|
|
;; rgr, 24-Dec-21.
|
|
(cl-incf index))
|
|
(while (and (< (cl-incf index) end)
|
|
(setq char (aref string index))
|
|
(not (or (ietf-drums-date--ignore-char-p char)
|
|
(eq char ?\())))
|
|
(unless (<= ?0 char ?9)
|
|
(setq all-digits nil))
|
|
(when (and (eq char ?\\)
|
|
(< (1+ index) end))
|
|
;; Escaped character, see above.
|
|
(cl-incf index)))
|
|
(push (if all-digits
|
|
(cl-parse-integer string :start start :end index)
|
|
(substring string start index))
|
|
list)
|
|
(skip-ignored)))
|
|
(nreverse list))))
|
|
|
|
(defun ietf-drums-parse-date-string (time-string &optional error no-822)
|
|
"Parse an RFC5322 or RFC822 date, passed as TIME-STRING.
|
|
The optional ERROR parameter causes syntax errors to be flagged
|
|
by signaling an instance of the date-parse-error condition. The
|
|
optional NO-822 parameter disables the more lax RFC822 syntax,
|
|
which is permitted by default.
|
|
|
|
The result is a list of (SEC MIN HOUR DAY MON YEAR DOW DST TZ),
|
|
which can be accessed as a decoded-time defstruct (q.v.),
|
|
e.g. `decoded-time-year' to extract the year, and turned into an
|
|
Emacs timestamp by `encode-time'.
|
|
|
|
The strict syntax for RFC5322 is as follows:
|
|
|
|
[ day-of-week \",\" ] day FWS month-name FWS year FWS time [CFWS]
|
|
|
|
where the \"time\" production is:
|
|
|
|
2DIGIT \":\" 2DIGIT [ \":\" 2DIGIT ] FWS ( \"+\" / \"-\" ) 4DIGIT
|
|
|
|
and FWS is \"folding white space,\" and CFWS is \"comments and/or
|
|
folding white space\", where comments are included in nesting
|
|
parentheses and are equivalent to white space. RFC822 also
|
|
accepts comments in random places (all of which is handled by
|
|
ietf-drums-date--tokenize-string) and two-digit years. For
|
|
two-digit years, 50 and up are interpreted as 1950 through 1999
|
|
and 00 through 49 as 200 through 2049.
|
|
|
|
We are somewhat more lax in what we accept (specifically, the
|
|
hours don't have to be two digits, and the TZ and the comma after
|
|
the DOW are optional), but we do insist that the items that are
|
|
present do appear in this order. Unspecified/unrecognized
|
|
elements in the string are returned as nil (except unspecified
|
|
DST is returned as -1)."
|
|
(let ((tokens (ietf-drums-date--tokenize-string (downcase time-string)
|
|
no-822))
|
|
(time (list nil nil nil nil nil nil nil -1 nil)))
|
|
(cl-labels ((set-matched-slot (slot index token)
|
|
;; Assign a slot value from match data if index is
|
|
;; non-nil, else from token, signaling an error if
|
|
;; enabled and it's out of range.
|
|
(let ((value (if index
|
|
(cl-parse-integer (match-string index token))
|
|
token)))
|
|
(when error
|
|
(let ((range (nth slot ietf-drums-date--slot-ranges)))
|
|
(when (and range
|
|
(not (<= (car range) value (cadr range))))
|
|
(signal 'date-parse-error
|
|
(list "Slot out of range"
|
|
(nth slot ietf-drums-date--slot-names)
|
|
token (car range) (cadr range))))))
|
|
(setf (nth slot time) value)))
|
|
(set-numeric (slot token)
|
|
;; Only assign the slot if the token is a number.
|
|
(cond ((natnump token)
|
|
(set-matched-slot slot nil token))
|
|
(error
|
|
(signal 'date-parse-error
|
|
(list "Not a number"
|
|
(nth slot ietf-drums-date--slot-names)
|
|
token))))))
|
|
;; Check for weekday.
|
|
(let ((dow (assoc (car tokens) parse-time-weekdays)))
|
|
(when dow
|
|
;; Day of the week.
|
|
(set-matched-slot 6 nil (cdr dow))
|
|
(pop tokens)))
|
|
;; Day.
|
|
(set-numeric 3 (pop tokens))
|
|
;; Alphabetic month.
|
|
(let* ((month (pop tokens))
|
|
(match (assoc month parse-time-months)))
|
|
(cond (match
|
|
(set-matched-slot 4 nil (cdr match)))
|
|
(error
|
|
(signal 'date-parse-error
|
|
(list "Expected an alphabetic month" month)))
|
|
(t
|
|
(push month tokens))))
|
|
;; Year.
|
|
(let ((year (pop tokens)))
|
|
;; Check the year for the right number of digits.
|
|
(cond ((not (natnump year))
|
|
(when error
|
|
(signal 'date-parse-error
|
|
(list "Expected a year" year)))
|
|
(push year tokens))
|
|
((>= year 1000)
|
|
(set-numeric 5 year))
|
|
((or no-822
|
|
(>= year 100))
|
|
(when error
|
|
(signal 'date-parse-error
|
|
(list "Four-digit years are required" year)))
|
|
(push year tokens))
|
|
((>= year 50)
|
|
;; second half of the 20th century.
|
|
(set-numeric 5 (+ 1900 year)))
|
|
(t
|
|
;; first half of the 21st century.
|
|
(set-numeric 5 (+ 2000 year)))))
|
|
;; Time.
|
|
(let ((time (pop tokens)))
|
|
(cond ((or (null time) (natnump time))
|
|
(when error
|
|
(signal 'date-parse-error
|
|
(list "Expected a time" time)))
|
|
(push time tokens))
|
|
((string-match
|
|
"^\\([0-9][0-9]?\\):\\([0-9][0-9]\\):\\([0-9][0-9]\\)$"
|
|
time)
|
|
(set-matched-slot 2 1 time)
|
|
(set-matched-slot 1 2 time)
|
|
(set-matched-slot 0 3 time))
|
|
((string-match "^\\([0-9][0-9]?\\):\\([0-9][0-9]\\)$" time)
|
|
;; Time without seconds.
|
|
(set-matched-slot 2 1 time)
|
|
(set-matched-slot 1 2 time)
|
|
(set-matched-slot 0 nil 0))
|
|
(error
|
|
(signal 'date-parse-error
|
|
(list "Expected a time" time)))))
|
|
;; Timezone.
|
|
(let* ((zone (pop tokens))
|
|
(match (assoc zone parse-time-zoneinfo)))
|
|
(cond (match
|
|
(set-matched-slot 8 nil (cadr match))
|
|
(set-matched-slot 7 nil (caddr match)))
|
|
((and (stringp zone)
|
|
(string-match "^[-+][0-9][0-9][0-9][0-9]$" zone))
|
|
;; Numeric time zone.
|
|
(set-matched-slot
|
|
8 nil
|
|
(* 60
|
|
(+ (cl-parse-integer zone :start 3 :end 5)
|
|
(* 60 (cl-parse-integer zone :start 1 :end 3)))
|
|
(if (= (aref zone 0) ?-) -1 1))))
|
|
((and zone error)
|
|
(signal 'date-parse-error
|
|
(list "Expected a timezone" zone)))))
|
|
(when (and tokens error)
|
|
(signal 'date-parse-error
|
|
(list "Extra token(s)" (car tokens)))))
|
|
time))
|
|
|
|
(provide 'ietf-drums-date)
|
|
|
|
;;; ietf-drums-date.el ends here
|