Requested in https://github.com/landley/toybox/issues/130, quoting an
old version of the toybox help. This is also supported by coreutils.

Set $LANG to C in the date tests so that they pass with TEST_HOST=1
(they were already failing for me, presumably related to a newer glibc).
---
 lib/xwrap.c       | 48 +++++++++++++++++++++++++++++++++++++----------
 tests/date.test   | 10 ++++++++++
 toys/posix/date.c |  3 +++
 3 files changed, 51 insertions(+), 10 deletions(-)
From 3c13e911c8365e6da029c487330506abefe439f8 Mon Sep 17 00:00:00 2001
From: Elliott Hughes <e...@google.com>
Date: Mon, 3 Aug 2020 16:59:29 -0700
Subject: [PATCH] xparsedate: support UTC offsets.

Requested in https://github.com/landley/toybox/issues/130, quoting an
old version of the toybox help. This is also supported by coreutils.

Set $LANG to C in the date tests so that they pass with TEST_HOST=1
(they were already failing for me, presumably related to a newer glibc).
---
 lib/xwrap.c       | 48 +++++++++++++++++++++++++++++++++++++----------
 tests/date.test   | 10 ++++++++++
 toys/posix/date.c |  3 +++
 3 files changed, 51 insertions(+), 10 deletions(-)

diff --git a/lib/xwrap.c b/lib/xwrap.c
index b144b2f4..435f554f 100644
--- a/lib/xwrap.c
+++ b/lib/xwrap.c
@@ -964,6 +964,35 @@ time_t xvali_date(struct tm *tm, char *str)
   error_exit("bad date %s", str);
 }
 
+// Turn a timezone specified as +HH[:MM] or Z into a value for $TZ.
+static int convert_tz(char *tz, char **pp)
+{
+  char sign, *p = *pp;
+  unsigned h_off, m_off, len;
+
+  if (*p == 'Z') {
+    strcpy(tz, "UTC0");
+    *pp = p+1;
+    return 1;
+  }
+
+  sign = *p++;
+  if (sign != '+' && sign != '-') return 0;
+
+  if (sscanf(p, "%2u%n", &h_off, &len) != 1) return 0;
+  p += len;
+
+  if (*p == ':') p++;
+
+  if (sscanf(p, "%2u%n", &m_off, &len) != 1) return 0;
+  p += len;
+
+  // We have to flip the sign because POSIX UTC offsets are backwards!
+  sprintf(tz, "UTC%c%02d:%02d", sign == '-' ? '+' : '-', h_off, m_off);
+  *pp = p;
+  return 1;
+}
+
 // Parse date string (relative to current *t). Sets time_t and nanoseconds.
 void xparsedate(char *str, time_t *t, unsigned *nano, int endian)
 {
@@ -975,7 +1004,7 @@ void xparsedate(char *str, time_t *t, unsigned *nano, int endian)
   char *s = str, *p, *oldtz = 0, *formats[] = {"%Y-%m-%d %T", "%Y-%m-%dT%T",
     "%H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d", "%H:%M", "%m%d%H%M",
     endian ? "%m%d%H%M%y" : "%y%m%d%H%M",
-    endian ? "%m%d%H%M%C%y" : "%C%y%m%d%H%M"};
+    endian ? "%m%d%H%M%C%y" : "%C%y%m%d%H%M"}, tz[10];
 
   *nano = 0;
 
@@ -998,15 +1027,6 @@ void xparsedate(char *str, time_t *t, unsigned *nano, int endian)
     xvali_date(0, str);
   }
 
-  // Trailing Z means UTC timezone, don't expect libc to know this.
-  // (Trimming it off here means it won't show up in error messages.)
-  if ((i = strlen(str)) && toupper(str[i-1])=='Z') {
-    str[--i] = 0;
-    oldtz = getenv("TZ");
-    if (oldtz) oldtz = xstrdup(oldtz);
-    setenv("TZ", "UTC0", 1);
-  }
-
   // Try each format
   for (i = 0; i<ARRAY_LEN(formats); i++) {
     localtime_r(&now, &tm);
@@ -1014,6 +1034,7 @@ void xparsedate(char *str, time_t *t, unsigned *nano, int endian)
     tm.tm_isdst = -endian;
 
     if ((p = strptime(s, formats[i], &tm))) {
+      // Handle optional fractional seconds.
       if (*p == '.') {
         p++;
         // If format didn't already specify seconds, grab seconds
@@ -1029,6 +1050,13 @@ void xparsedate(char *str, time_t *t, unsigned *nano, int endian)
         }
       }
 
+      // Handle optional timezone.
+      if (convert_tz(tz, &p)) {
+        oldtz = getenv("TZ");
+        if (oldtz) oldtz = xstrdup(oldtz);
+        setenv("TZ", tz, 1);
+      }
+
       if (!*p) break;
     }
   }
diff --git a/tests/date.test b/tests/date.test
index 2b865349..5cb9f038 100644
--- a/tests/date.test
+++ b/tests/date.test
@@ -9,6 +9,9 @@
 tz=Europe/Berlin
 this_year=$(TZ=$tz date +%Y)
 
+# Use a consistent locale too.
+export LANG=C
+
 # Unix date parsing.
 testing "-d @0" "TZ=$tz date -d @0" "Thu Jan  1 01:00:00 CET 1970\n" "" ""
 testing "-d @0x123 invalid" "TZ=$tz date -d @0x123 2>/dev/null || echo expected error" "expected error\n" "" ""
@@ -54,3 +57,10 @@ rm -f f
 testing "TZ=" "TZ='America/Los_Angeles' date -d 'TZ=\"Europe/Berlin\" 2018-01-04 08:00'" "Wed Jan  3 23:00:00 PST 2018\n" "" ""
 testing "TZ=" "TZ='America/Los_Angeles' date -d 'TZ=\"Europe/Berlin\" 2018-10-04 08:00'" "Wed Oct  3 23:00:00 PDT 2018\n" "" ""
 testing "TZ= @" "TZ='America/Los_Angeles' date -d 'TZ=\"GMT\" @1533427200'" "Sat Aug  4 17:00:00 PDT 2018\n" "" ""
+
+testing "tz Z" \
+  "date -u -d 2020-08-01T12:34:56Z" "Sat Aug  1 12:34:56 UTC 2020\n" "" ""
+testing "tz +0800" \
+  "date -u -d 2020-08-01T12:34:56+0800" "Sat Aug  1 04:34:56 UTC 2020\n" "" ""
+testing "tz +08:00" \
+  "date -u -d 2020-08-01T12:34:56+08:00" "Sat Aug  1 04:34:56 UTC 2020\n" "" ""
diff --git a/toys/posix/date.c b/toys/posix/date.c
index 7fe0388d..f34c347f 100644
--- a/toys/posix/date.c
+++ b/toys/posix/date.c
@@ -29,6 +29,9 @@ config DATE
     YYYY-MM-DD [hh:mm[:ss]]   ISO 8601
     hh:mm[:ss]                24-hour time today
 
+    All input formats can be followed by fractional seconds, and/or a UTC
+    offset such as -0800.
+
     All input formats can be preceded by TZ="id" to set the input time zone
     separately from the output time zone. Otherwise $TZ sets both.
 
-- 
2.28.0.163.g6104cc2f0b6-goog

_______________________________________________
Toybox mailing list
Toybox@lists.landley.net
http://lists.landley.net/listinfo.cgi/toybox-landley.net

Reply via email to