Subject: bug#52209: 28.0.60; [PATCH] date-to-time fails on pure dates

From: Paul Eggert <[email-masked]>
Date: Sat, 4 Dec 2021 10:58:37 -0800

Unfortunately the latest change to time-date.el reintroduced Bug#52209. 
I installed the attached patch to fix this, and to add some test cases 
mentioned in this bug report to help prevent the problem recurring. 
Also, this patch documents the new feature, and avoids 
overenthusiastically guessing the year to be 1970 when the date string 
lacks a year.

> (These functions were never really intended to support parsing dates
> like that -- only strict RFC822 date strings were originally supported,
> but it's become more DWIM as time has passed.

Yes, date-to-time has definitely ... evolved.

My understanding is that date-to-time's RFC822 parsing is present only 
for backward compatibility, and that we shouldn't attempt to enhance it 
(here, the enhancement would be pointless as the RFC822 parsing fills in 
the blanks anyway). So the patch I just installed adds the new feature 
only for the normal path taken, when not doing the RFC822 hack.

PS. Internet RFC 822 has been obsolete since 2001, and the Emacs code 
should be talking about RFC 5322 everywhere except when Emacs is 
explicitly supporting the obsolete standard instead of the current 
standard. And we should rename functions like rfc822-goto-eoh to 
rfc-email-goto-eoh, to help avoid confusion or further function 
renaming. But I digress....

From cb0f4f00b328a561e49538bbf0f90050eac1ba20 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Sat, 4 Dec 2021 10:33:32 -0800
Subject: [PATCH] Fix (date-to-time "2021-12-04")

This should complete the fix for Bug#52209.
* lisp/calendar/time-date.el (date-to-time): Apply
decoded-time-set-defaults only to the output of (parse-time-string
date), and only when the output has a year (to avoid confusion
when dates lack years).  There is no point applying it after
timezone-make-date-arpa-standard since the latter fills in all the
blanks.  And the former code mistakenly called encode-time on an
already-encoded time.  This goes back to the code a couple of days
ago, except with changed behavior (to fix Bug#52209) only when
timezone-make-date-arpa-standard is not called.
* test/lisp/calendar/time-date-tests.el (test-date-to-time)
(test-days-between): New tests.
---
 doc/lispref/os.texi                   |  3 ++-
 etc/NEWS                              |  4 +++
 lisp/calendar/time-date.el            | 38 +++++++++++----------------
 test/lisp/calendar/time-date-tests.el |  7 +++++
 4 files changed, 29 insertions(+), 23 deletions(-)

diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index e420644cd8..b4efc44b03 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -1724,7 +1724,8 @@ Time Parsing
 corresponding Lisp timestamp.  The argument @var{string} should represent
 a date-time, and should be in one of the forms recognized by
 @code{parse-time-string} (see below).  This function assumes Universal
-Time if @var{string} lacks explicit time zone information.
+Time if @var{string} lacks explicit time zone information,
+and assumes earliest values if @var{string} lacks month, day, or time.
 The operating system limits the range of time and zone values.
 @end defun
 
diff --git a/etc/NEWS b/etc/NEWS
index ac1787d7f8..2b4eaaf8a1 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1084,6 +1084,10 @@ cookies set by web pages on disk.
 ** New variable 'help-buffer-under-preparation'.
 This variable is bound to t during the preparation of a "*Help*" buffer.
 
++++
+** 'date-to-time' now assumes earliest values if its argument lacks
+month, day, or time.  For example, (date-to-time "2021-12-04") now
+assumes a time of 00:00 instead of signaling an error.
 
 * Changes in Emacs 29.1 on Non-Free Operating Systems
 
diff --git a/lisp/calendar/time-date.el b/lisp/calendar/time-date.el
index 8a6ee0f270..37a16d3b98 100644
--- a/lisp/calendar/time-date.el
+++ b/lisp/calendar/time-date.el
@@ -153,28 +153,22 @@ date-to-time
   "Parse a string DATE that represents a date-time and return a time value.
 DATE should be in one of the forms recognized by `parse-time-string'.
 If DATE lacks timezone information, GMT is assumed."
-  ;; Pass the result of parsing through decoded-time-set-defaults
-  ;; because encode-time signals if HH:MM:SS are not filled in.
-  (encode-time
-    (decoded-time-set-defaults
-      (condition-case err
-          (let ((time (parse-time-string date)))
-            (prog1 time
-              ;; Cause an error if data `parse-time-string' returns is invalid.
-              (setq time (encode-time time))))
-        (error
-         (let ((overflow-error '(error "Specified time is not representable")))
-           (if (or (equal err overflow-error)
-                   ;; timezone-make-date-arpa-standard misbehaves if
-                   ;; not given at least HH:MM as part of the date.
-                   (not (string-match ":" date)))
-               (signal (car err) (cdr err))
-             (condition-case err
-                 (parse-time-string (timezone-make-date-arpa-standard date))
-               (error
-                (if (equal err overflow-error)
-                    (signal (car err) (cdr err))
-                  (error "Invalid date: %s" date)))))))))))
+  (condition-case err
+      (let ((parsed (parse-time-string date)))
+	(when (decoded-time-year parsed)
+	  (decoded-time-set-defaults parsed))
+	(encode-time parsed))
+    (error
+     (let ((overflow-error '(error "Specified time is not representable")))
+       (if (equal err overflow-error)
+	   (signal (car err) (cdr err))
+	 (condition-case err
+	     (encode-time (parse-time-string
+			   (timezone-make-date-arpa-standard date)))
+	   (error
+	    (if (equal err overflow-error)
+		(signal (car err) (cdr err))
+	      (error "Invalid date: %s" date)))))))))
 
 ;;;###autoload
 (defalias 'time-to-seconds 'float-time)
diff --git a/test/lisp/calendar/time-date-tests.el b/test/lisp/calendar/time-date-tests.el
index 4568947c0b..d5269804ad 100644
--- a/test/lisp/calendar/time-date-tests.el
+++ b/test/lisp/calendar/time-date-tests.el
@@ -41,6 +41,13 @@ test-obsolete-encode-time-value
                    (encode-time-value 1 2 3 4 3))
                  '(1 2 3 4))))
 
+(ert-deftest test-date-to-time ()
+  (should (equal (format-time-string "%F %T" (date-to-time "2021-12-04"))
+                 "2021-12-04 00:00:00")))
+
+(ert-deftest test-days-between ()
+  (should (equal (days-between "2021-10-22" "2020-09-29") 388)))
+
 (ert-deftest test-leap-year ()
   (should-not (date-leap-year-p 1999))
   (should-not (date-leap-year-p 1900))
-- 
2.32.0