aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2020-08-03 16:59:29 -0700
committerRob Landley <rob@landley.net>2020-08-04 03:13:14 -0500
commitbabcb4bcf4c5c37e545f174db9ba99c62ebb877a (patch)
treec57469ee0dbfb12f4f85ed65bd5cdbbb28796cce
parentdc7654192c4b0c599cf5f742673a0829d6eddcd9 (diff)
downloadtoybox-babcb4bcf4c5c37e545f174db9ba99c62ebb877a.tar.gz
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).
-rw-r--r--lib/xwrap.c48
-rw-r--r--tests/date.test10
-rw-r--r--toys/posix/date.c3
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.