Subject: Re: parse-time-string fails to parse Info node's example

From: Paul Eggert <[email-masked]>
Date: Wed, 15 Jan 2020 17:45:02 -0800

Thanks for the bug report. I fixed the code so that it matches the 
documentation by installing the attached patches into Emacs master. The 
first patch is the fix, the second is merely refactoring.
>From 33d9ca387824e050650b49a60083e4e4cb4e3d11 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Wed, 15 Jan 2020 17:36:59 -0800
Subject: [PATCH 1/2] parse-time-string now parses ISO 8601 format strings

* lisp/calendar/parse-time.el (parse-time-string):
Parse strings in ISO 8601 format too (Bug#39001).
(parse-time--rfc-822ish): New internal function,
containing most of the old parse-time-string implementation.
(parse-iso8601-time-string): Simplify, now that
parse-time-string groks ISO 8601.
---
 etc/NEWS                    |  3 +++
 lisp/calendar/parse-time.el | 25 ++++++++++++++-----------
 2 files changed, 17 insertions(+), 11 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 0e43c321d8..3d5915a377 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -70,6 +70,9 @@ called when the function object is garbage-collected.  Use
 'set_function_finalizer' to set the finalizer and
 'get_function_finalizer' to retrieve it.
 
+** 'parse-time-string' can now parse ISO 8601 format strings,
+such as "2020-01-15T16:12:21-08:00".
+
 
 * Changes in Emacs 28.1 on Non-Free Operating Systems
 
diff --git a/lisp/calendar/parse-time.el b/lisp/calendar/parse-time.el
index 7110a81f0d..4d4f88efff 100644
--- a/lisp/calendar/parse-time.el
+++ b/lisp/calendar/parse-time.el
@@ -149,13 +149,20 @@ parse-time-rules
 ;;;###autoload
 (defun parse-time-string (string)
   "Parse the time in STRING into (SEC MIN HOUR DAY MON YEAR DOW DST TZ).
-STRING should be something resembling an RFC 822 (or later) date-time, e.g.,
-\"Fri, 25 Mar 2016 16:24:56 +0100\", but this function is
+STRING should be an ISO 8601 time string, e.g., \"2020-01-15T16:12:21-08:00\",
+or something resembling an RFC 822 (or later) date-time, e.g.,
+\"Wed, 15 Jan 2020 16:12:21 -0800\".  This function is
 somewhat liberal in what format it accepts, and will attempt to
 return a \"likely\" value even for somewhat malformed strings.
 The values returned are identical to those of `decode-time', but
 any unknown values other than DST are returned as nil, and an
 unknown DST value is returned as -1."
+  (condition-case ()
+      (decoded-time-set-defaults (iso8601-parse string))
+    (wrong-type-argument
+     (parse-time--rfc-822ish string))))
+
+(defun parse-time--rfc-822ish (string)
   (let ((time (list nil nil nil nil nil nil nil -1 nil))
 	(temp (parse-time-tokenize (downcase string))))
     (while temp
@@ -196,15 +203,11 @@ parse-time-string
     time))
 
 (defun parse-iso8601-time-string (date-string)
-  "Parse an ISO 8601 time string, such as 2016-12-01T23:35:06-05:00.
-If DATE-STRING cannot be parsed, it falls back to
-`parse-time-string'."
-  (when-let ((time
-              (if (iso8601-valid-p date-string)
-                  (decoded-time-set-defaults (iso8601-parse date-string))
-                ;; Fall back to having `parse-time-string' do fancy
-                ;; things for us.
-                (parse-time-string date-string))))
+  "Parse an ISO 8601 time string, such as \"2020-01-15T16:12:21-08:00\".
+Fall back on parsing something resembling an RFC 822 (or later) date-time.
+This function is like `parse-time-string' except that it returns
+a Lisp timestamp when successful."
+  (when-let ((time (parse-time-string date-string)))
     (encode-time time)))
 
 (provide 'parse-time)
-- 
2.24.1

>From d3db908ffbfa17ff74080455ac0520c663f920ee Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Wed, 15 Jan 2020 17:41:42 -0800
Subject: [PATCH 2/2] Refactor parse-time-string

* lisp/calendar/parse-time.el (parse-time--rfc-822ish):
Remove, and fold its body into its only caller.
---
 lisp/calendar/parse-time.el | 79 ++++++++++++++++++-------------------
 1 file changed, 38 insertions(+), 41 deletions(-)

diff --git a/lisp/calendar/parse-time.el b/lisp/calendar/parse-time.el
index 4d4f88efff..6a4612297c 100644
--- a/lisp/calendar/parse-time.el
+++ b/lisp/calendar/parse-time.el
@@ -160,47 +160,44 @@ parse-time-string
   (condition-case ()
       (decoded-time-set-defaults (iso8601-parse string))
     (wrong-type-argument
-     (parse-time--rfc-822ish string))))
-
-(defun parse-time--rfc-822ish (string)
-  (let ((time (list nil nil nil nil nil nil nil -1 nil))
-	(temp (parse-time-tokenize (downcase string))))
-    (while temp
-      (let ((parse-time-elt (pop temp))
-	    (rules parse-time-rules)
-	    (exit nil))
-	(while (and rules (not exit))
-	  (let* ((rule (pop rules))
-		 (slots (pop rule))
-		 (predicate (pop rule))
-		 (parse-time-val))
-	    (when (and (not (nth (car slots) time)) ;not already set
-		       (setq parse-time-val
-			     (cond ((and (consp predicate)
-					 (not (functionp predicate)))
-				    (and (numberp parse-time-elt)
-					 (<= (car predicate) parse-time-elt)
-					 (or (not (cdr predicate))
-					     (<= parse-time-elt
-						 (cadr predicate)))
-					 parse-time-elt))
-				   ((symbolp predicate)
-				    (cdr (assoc parse-time-elt
-						(symbol-value predicate))))
-				   ((funcall predicate)))))
-	      (setq exit t)
-	      (while slots
-		(let ((new-val (if rule
-				   (let ((this (pop rule)))
-				     (if (vectorp this)
-					 (cl-parse-integer
-					  parse-time-elt
-					  :start (aref this 0)
-					  :end (aref this 1))
-				       (funcall this)))
-				 parse-time-val)))
-		  (setf (nth (pop slots) time) new-val))))))))
-    time))
+     (let ((time (list nil nil nil nil nil nil nil -1 nil))
+	   (temp (parse-time-tokenize (downcase string))))
+       (while temp
+	 (let ((parse-time-elt (pop temp))
+	       (rules parse-time-rules)
+	       (exit nil))
+	   (while (and rules (not exit))
+	     (let* ((rule (pop rules))
+		    (slots (pop rule))
+		    (predicate (pop rule))
+		    (parse-time-val))
+	       (when (and (not (nth (car slots) time)) ;not already set
+			  (setq parse-time-val
+				(cond ((and (consp predicate)
+					    (not (functionp predicate)))
+				       (and (numberp parse-time-elt)
+					    (<= (car predicate) parse-time-elt)
+					    (or (not (cdr predicate))
+						(<= parse-time-elt
+						    (cadr predicate)))
+					    parse-time-elt))
+				      ((symbolp predicate)
+				       (cdr (assoc parse-time-elt
+						   (symbol-value predicate))))
+				      ((funcall predicate)))))
+		 (setq exit t)
+		 (while slots
+		   (let ((new-val (if rule
+				      (let ((this (pop rule)))
+					(if (vectorp this)
+					    (cl-parse-integer
+					     parse-time-elt
+					     :start (aref this 0)
+					     :end (aref this 1))
+					  (funcall this)))
+				    parse-time-val)))
+		     (setf (nth (pop slots) time) new-val))))))))
+       time))))
 
 (defun parse-iso8601-time-string (date-string)
   "Parse an ISO 8601 time string, such as \"2020-01-15T16:12:21-08:00\".
-- 
2.24.1