Subject: Re: bug#50115: date command arithmetic involving the epoch produces "invalid date"
From: Paul Eggert <[email-masked]>
To: Jeremy Cantrell <[email-masked]>
Cc: Gnulib bugs <[email-masked]>, [email-masked]
Date: Sat, 5 Feb 2022 13:55:26 -0800
List-Id: <debbugs-submit.debbugs.gnu.org>

Thanks for the bug report. I installed the attached patches to Gnulib 
and to Coreutils, and the fix should be in the next Coreutils release.

From aa0d1e7800903f2d75432d78aa64a0e9770e83f2 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Sat, 5 Feb 2022 11:05:44 -0800
Subject: [PATCH] parse-datetime: allow calculations to yield -1

Problem reported by Jeremy Cantrell <https://bugs.gnu.org/50115>.
* lib/parse-datetime.y (parse_datetime_body): When calling mktime,
use an unmodifed and negative tm_wday or tm_yday to detect an error,
as a (time_t) -1 return value is valid on most hosts.
* tests/test-parse-datetime.c (main): Add a test for the bug.
---
 ChangeLog                   |  9 +++++++++
 lib/parse-datetime.y        | 22 +++++++++++-----------
 tests/test-parse-datetime.c |  8 ++++++++
 3 files changed, 28 insertions(+), 11 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 5445802ea2..18dcb3fe3f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2022-02-05  Paul Eggert  <[email-masked]>
+
+	parse-datetime: allow calculations to yield -1
+	Problem reported by Jeremy Cantrell <https://bugs.gnu.org/50115>.
+	* lib/parse-datetime.y (parse_datetime_body): When calling mktime,
+	use an unmodifed and negative tm_wday or tm_yday to detect an error,
+	as a (time_t) -1 return value is valid on most hosts.
+	* tests/test-parse-datetime.c (main): Add a test for the bug.
+
 2022-02-04  Paul Eggert  <[email-masked]>
 
 	userspec: help fix GNU ‘id’ incompatibility
diff --git a/lib/parse-datetime.y b/lib/parse-datetime.y
index c40fdcef7f..9fc14c9d46 100644
--- a/lib/parse-datetime.y
+++ b/lib/parse-datetime.y
@@ -2076,21 +2076,20 @@ parse_datetime_body (struct timespec *result, char const *p,
       if (pc.days_seen && ! pc.dates_seen)
         {
           intmax_t dayincr;
-          if (INT_MULTIPLY_WRAPV ((pc.day_ordinal
-                                   - (0 < pc.day_ordinal
-                                      && tm.tm_wday != pc.day_number)),
-                                  7, &dayincr)
-              || INT_ADD_WRAPV ((pc.day_number - tm.tm_wday + 7) % 7,
-                                dayincr, &dayincr)
-              || INT_ADD_WRAPV (dayincr, tm.tm_mday, &tm.tm_mday))
-            Start = -1;
-          else
+          tm.tm_yday = -1;
+          if (! (INT_MULTIPLY_WRAPV ((pc.day_ordinal
+                                      - (0 < pc.day_ordinal
+                                         && tm.tm_wday != pc.day_number)),
+                                     7, &dayincr)
+                 || INT_ADD_WRAPV ((pc.day_number - tm.tm_wday + 7) % 7,
+                                   dayincr, &dayincr)
+                 || INT_ADD_WRAPV (dayincr, tm.tm_mday, &tm.tm_mday)))
             {
               tm.tm_isdst = -1;
               Start = mktime_z (tz, &tm);
             }
 
-          if (Start == (time_t) -1)
+          if (tm.tm_yday < 0)
             {
               if (debugging (&pc))
                 dbg_printf (_("error: day '%s' "
@@ -2156,8 +2155,9 @@ parse_datetime_body (struct timespec *result, char const *p,
           tm.tm_min = tm0.tm_min;
           tm.tm_sec = tm0.tm_sec;
           tm.tm_isdst = tm0.tm_isdst;
+          tm.tm_wday = -1;
           Start = mktime_z (tz, &tm);
-          if (Start == (time_t) -1)
+          if (tm.tm_wday < 0)
             {
               if (debugging (&pc))
                 dbg_printf (_("error: adding relative date resulted "
diff --git a/tests/test-parse-datetime.c b/tests/test-parse-datetime.c
index 059c810cd1..1e7955bc96 100644
--- a/tests/test-parse-datetime.c
+++ b/tests/test-parse-datetime.c
@@ -398,6 +398,14 @@ main (_GL_UNUSED int argc, char **argv)
       ASSERT (result.tv_sec == thur2 + ((i + 3) % 7 - 7) * 24 * 3600);
     }
 
+  p = "1970-12-31T23:59:59+00:00 - 1 year";  /* Bug#50115 */
+  now.tv_sec = -1;
+  now.tv_nsec = 0;
+  ASSERT (parse_datetime (&result, p, &now));
+  LOG (p, now, result);
+  ASSERT (result.tv_sec == now.tv_sec
+          && result.tv_nsec == now.tv_nsec);
+
   p = "THURSDAY UTC+00";  /* The epoch was on Thursday.  */
   now.tv_sec = 0;
   now.tv_nsec = 0;
-- 
2.32.0

From cf6c84989968c5081c683bbef77825fc35e03c9d Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Sat, 5 Feb 2022 11:08:45 -0800
Subject: [PATCH 1/2] build: update gnulib submodule to latest

---
 gnulib | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gnulib b/gnulib
index ff208d546..aa0d1e780 160000
--- a/gnulib
+++ b/gnulib
@@ -1 +1 @@
-Subproject commit ff208d546a26fee39a0191297c11560da74b5dee
+Subproject commit aa0d1e7800903f2d75432d78aa64a0e9770e83f2
-- 
2.32.0

From 8a3dedfef9479c53cd9016139ce00d58a6006ba2 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Sat, 5 Feb 2022 13:46:44 -0800
Subject: [PATCH 2/2] date: test against bug#50115

* tests/misc/date.pl: Add test.
---
 tests/misc/date.pl | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/tests/misc/date.pl b/tests/misc/date.pl
index e9de8e453..ef7080e33 100755
--- a/tests/misc/date.pl
+++ b/tests/misc/date.pl
@@ -301,6 +301,9 @@ my @Tests =
      # https://bugs.gnu.org/34608
      ['date-century-plus', '-d @0 +.%+4C.', {OUT => '.+019.'}],
 
+     # https://bugs.gnu.org/50115
+     ['date-epoch-minus-1', '-u -d "1970-12-31T23:59:59+00:00 - 1 year"',
+      {OUT => 'Wed Dec 31 23:59:59 UTC 1969'}],
 
      # Military time zones, new behavior (since 8.32)
      # https://lists.gnu.org/r/bug-gnulib/2019-08/msg00005.html
-- 
2.32.0