diff options
-rw-r--r-- | lib/xwrap.c | 48 | ||||
-rw-r--r-- | tests/date.test | 10 | ||||
-rw-r--r-- | toys/posix/date.c | 3 |
3 files changed, 51 insertions, 10 deletions
diff --git a/lib/xwrap.c b/lib/xwrap.c index abf0d4da..886878cd 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. |