[Toybox] date with timezone

Rob Landley rob at landley.net
Wed May 26 04:52:30 PDT 2021


On 5/25/21 3:28 PM, Frank Liu wrote:
> date doesn't seem to recognize the format with timezone info:
> 
> bash-5.0# a="2021-05-25 19:32:36.000000000 +0000"
> bash-5.0# date -d "$a" +%s
> date: bad date 2021-05-25 19:32:36.000000000 +0000
> bash-5.0# /vol/debian/date -d "$a" +%s
> 1621971156
> 
> Removing timezone +0000 works:
> bash-5.0# a="2021-05-25 19:32:36.000000000"
> bash-5.0# date -d "$a" +%s
> 1621971156

Um, is it always 4 digits? Is there a spec on this? "man date" isn't saying.
"man asciitime" isn't saying.... Ah:

  https://en.wikipedia.org/wiki/Time_zone#ISO_8601

  https://www.youtube.com/watch?v=lg4TFV1rF0M

RIGHT, +1234 +12:34 or just +12. Are we currently handling trailing Z? I don't
see where we're handling trailing Z. Ok, modify parse_date() to add... ah, looks
like $TZ already understands this?

  https://man7.org/linux/man-pages/man3/tzset.3.html

There are timezones with SECONDS of offset? Ok...

And it's - as well as +, so... is the space required?

  $ date -d "2021-05-25 19:32:36.000000000 +0000" +%s
  1621971156
  $ date -d "2021-05-25 19:32:36.000000000+0000" +%s
  1621971156
  $ date -d "2021-05-25 19:32:36.000000000+0001" +%s
  1621971096
  $ date -d "2021-05-25 19:32:36.000000000+00:01:37" +%s
  date: invalid date ‘2021-05-25 19:32:36.000000000+00:01:37’

Dear FSF: please just stop already. The tzset man page is presumably documenting
what glibc does, and date reimplemented this code in an incompatible way for NO
READILY APPARENT REASON... I mean you have to go out of your way to break this.

  $ date -d "2021-05-25 19:32:36.000000000+000137" +%s
  1621965336

And THAT is just half-assing it. (How can my quick tests to see what the
behavior IS be more thorough than any tests they've apparently ever DONE on this
code? Don't they care?)

Anyway, since the space is NOT required I can't just NULL that out to chop the
existing string into two pieces. (But I'm totally cheating and null-ing out the
Z at the end, and no I don't currently care that I'm modifying an environment
string when I do that which will change what "ps" sees, because I'm tired and
cranky.)

Does it get confused by trailing whitespace?

  $ date -d "2021-05-25 19:32:36.000000000+000137 " +%s
  1621965336
  $ date -d "2021-05-25 19:32:36.000000000+ 000137" +%s
  1621965336
  $ date -d "2021-05-25 19:32:36.000000000+ 00 01 37" +%s
  date: invalid date ‘2021-05-25 19:32:36.000000000+ 00 01 37’

Sigh. Right... Ah. The reason gnu date is not using gnu libc's tz code for this
is the libc code DOES NOT WORK.

  $ ./date -d "2021-05-25 19:32:36 +0000" +%s
  1621971156
  $ ./date -d "2021-05-25 19:32:36 +0001" +%s
  1621971156

Elliott, do you already know this timezone stuff? My first attempt at a patch was:

diff --git a/toys/posix/date.c b/toys/posix/date.c
index 86c2d343..c6e0b498 100644
--- a/toys/posix/date.c
+++ b/toys/posix/date.c
@@ -64,27 +64,45 @@ GLOBALS(
   unsigned nano;
 )

-// Handles any leading `TZ="blah" ` in the input string.
+// Handles any leading `TZ="blah" ` or trailing +#### in the input string.
 static void parse_date(char *str, time_t *t)
 {
-  char *new_tz = NULL, *old_tz, *s = str;
+  char *new = 0, *old, *s = str;

   if (!strncmp(str, "TZ=\"", 4)) {
     // Extract the time zone and skip any whitespace.
-    new_tz = str+4;
-    if (!(str = strchr(new_tz, '"'))) xvali_date(0, s);
+    new = str+4;
+    if (!(str = strchr(new, '"'))) xvali_date(0, s); // borrow error msg
     *str++ = 0;
     while (isspace(*str)) str++;
+  } else if ((s = strrchr(str, 'Z')) && !s[1]) {
+    new = "UTC";
+    *s = 0;
+  } else {
+    for (s = str+strlen(str); --s>str;) if (strchr("+-", *s)) break;
+    if (s>str) {
+      for (new = s++; isspace(*s); s++);
+      while (isdigit(*s) || *s==':') s++;
+      while (isspace(*s)) s++;
+      if (!*s) {
+        s = xstrdup(new);
+        while (new>str && isspace(new[-1])) new--;
+        *new = 0;
+        new = s;
+      } else new = 0;
+    }
+  }

+  if (new) {
     // Switch $TZ.
-    old_tz = getenv("TZ");
-    setenv("TZ", new_tz, 1);
+    old = getenv("TZ");
+    setenv("TZ", new, 1);
     tzset();
   }
   time(t);
   xparsedate(str, t, &TT.nano, 1);
-  if (new_tz) {
-    if (old_tz) setenv("TZ", old_tz, 1);
+  if (new) {
+    if (old) setenv("TZ", old, 1);
     else unsetenv("TZ");
   }
 }

But it didn't work because libc isn't playing along. What does libc want me to
stick into tz for tzset? (And WHY can't tzset take an argument string?)

Can I maybe just write to "extern long timezone" directly? (And always zero
"daylight"?) Posix sort of implies I can:

  https://pubs.opengroup.org/onlinepubs/009695399/functions/tzset.html

But it also sort of looks like you can specify a trailing timezone and THEN say
+1234 on top of it? I'm not feeling enlightened by reading about $TZ in

  https://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html

And I already linked to the appropriate youtube video above.

Rob



More information about the Toybox mailing list