diff options
author | Elliott Hughes <enh@google.com> | 2015-08-11 16:06:06 -0500 |
---|---|---|
committer | Rob Landley <rob@landley.net> | 2015-08-11 16:06:06 -0500 |
commit | 05499787ca89fa4b017c2441e89020799f02e4c1 (patch) | |
tree | c8e48c77d96b47fd710238f82240126e71798981 | |
parent | 7f6bb3dae7ffe5ffdf10cc2e73a803ff820ebca8 (diff) | |
download | toybox-05499787ca89fa4b017c2441e89020799f02e4c1.tar.gz toybox-05499787ca89fa4b017c2441e89020799f02e4c1.tar.bz2 toybox-05499787ca89fa4b017c2441e89020799f02e4c1.zip |
Fix more date bugs.
Correctly and portably check for non-normal dates, and explicitly show
the "before" and "after" dates (in the format of the user's choosing).
Clear the struct tm in date_main rather than parse_default because on
one path the struct tm is actually initialized. Explicitly clear the
tm_sec field in parse_default because -- experiment shows -- that
should not be preserved. Only do the "what does this 2-digit year
mean?" dance if we actually parsed a 2-digit year. Show the right
string in the error message if strptime fails.
Also add more tests, and use UTC in the tests to avoid flakiness.
-rw-r--r-- | tests/date.test | 16 | ||||
-rw-r--r-- | toys/posix/date.c | 51 |
2 files changed, 43 insertions, 24 deletions
diff --git a/tests/date.test b/tests/date.test index d72e50c..94a4157 100644 --- a/tests/date.test +++ b/tests/date.test @@ -4,7 +4,19 @@ #testing "name" "command" "result" "infile" "stdin" +# Test Unix date parsing. +testing "date -d @0" "TZ=UTC date -d @0 2>&1" "Thu Jan 1 00:00:00 GMT 1970\n" "" "" +testing "date -d @0x123" "TZ=UTC date -d @0x123 2>&1" "date: bad date '@0x123'\n" "" "" + +# Test basic date parsing. +# Note that toybox's -d format is not the same as coreutils'. +testing "date -d 06021234" "TZ=UTC date -d 06021234 2>&1" "Sun Jun 2 12:34:00 UTC 1900\n" "" "" +testing "date -d 060212341982" "TZ=UTC date -d 060212341982 2>&1" "Sun Jun 2 12:34:00 UTC 1982\n" "" "" +testing "date -d 123" "TZ=UTC date -d 123 2>&1" "date: bad date '123'\n" "" "" + # Accidentally given a Unix time, we should trivially reject that. -testing "date Unix time" "date 1438053157 2>&1" "date: bad date '1438053157'\n" "" "" +testing "date Unix time missing @" "TZ=UTC date 1438053157 2>&1" \ + "date: bad date '1438053157'; Tue February 38 05:31:00 UTC 2057 != Sun Mar 10 05:31:00 UTC 2058\n" "" "" # But some invalid dates are more subtle, like Febuary 29th in a non-leap year. -testing "date Feb 29th" "date 022900001975 2>&1" "date: bad date +testing "date Feb 29th" "TZ=UTC date 022900001975 2>&1" \ + "date: bad date '022900001975'; Tue Feb 29 00:00:00 UTC 2075 != Fri Mar 1 00:00:00 UTC 2075\n" "" "" diff --git a/toys/posix/date.c b/toys/posix/date.c index 909ca5a..a42de50 100644 --- a/toys/posix/date.c +++ b/toys/posix/date.c @@ -56,18 +56,24 @@ GLOBALS( ) // mktime(3) normalizes the struct tm fields, but date(1) shouldn't. -static time_t chkmktime(struct tm *tm) +static time_t chkmktime(struct tm *tm, const char *str, const char* fmt) { - struct tm tm2; + struct tm tm0 = *tm; + struct tm tm1; time_t t = mktime(tm); - int *tt1 = (void *)tm, *tt2=(void *)&tm2, i; - if (t != -1 && localtime_r(&t, &tm2)) { - for (i=0; i<6; i++) if (tt1[i] != tt2[i]) break; - if (i == 5) return t; - } + if (t == -1 || !localtime_r(&t, &tm1) || + tm0.tm_sec != tm1.tm_sec || tm0.tm_min != tm1.tm_min || + tm0.tm_hour != tm1.tm_hour || tm0.tm_mday != tm1.tm_mday || + tm0.tm_mon != tm1.tm_mon) { + int len; - return -1; + strftime(toybuf, sizeof(toybuf), fmt, &tm0); + len = strlen(toybuf) + 1; + strftime(toybuf + len, sizeof(toybuf) - len, fmt, &tm1); + error_exit("bad date '%s'; %s != %s", str, toybuf, toybuf + len); + } + return t; } static void utzset(void) @@ -92,8 +98,6 @@ static int parse_default(char *str, struct tm *tm) { int len = 0; - memset(tm, 0, sizeof(struct tm)); - // Parse @UNIXTIME[.FRACTION] if (*str == '@') { long long ll; @@ -139,16 +143,18 @@ static int parse_default(char *str, struct tm *tm) // 2 digit years, next 50 years are "future", last 50 years are "past". // A "future" date in past is a century ahead. // A non-future date in the future is a century behind. - if ((r1 < r2) ? (r1 < year && year < r2) : (year < r1 || year > r2)) { - if (year < r1) year += 100; - } else if (year > r1) year -= 100; + if (len == 2) { + if ((r1 < r2) ? (r1 < year && year < r2) : (year < r1 || year > r2)) { + if (year < r1) year += 100; + } else if (year > r1) year -= 100; + } tm->tm_year = year + century; } if (*str == '.') { len = 0; sscanf(str, ".%u%n", &tm->tm_sec, &len); str += len; - } + } else tm->tm_sec = 0; return *str; } @@ -158,6 +164,8 @@ void date_main(void) char *setdate = *toys.optargs, *format_string = "%a %b %e %H:%M:%S %Z %Y"; struct tm tm; + memset(&tm, 0, sizeof(struct tm)); + // We can't just pass a timezone to mktime because posix. if (toys.optflags & FLAG_u) utzset(); @@ -165,8 +173,8 @@ void date_main(void) if (TT.setfmt) { char *s = strptime(TT.showdate, TT.setfmt+(*TT.setfmt=='+'), &tm); - if (!s || *s) goto bad_date; - } else if (parse_default(TT.showdate, &tm)) goto bad_date; + if (!s || *s) goto bad_showdate; + } else if (parse_default(TT.showdate, &tm)) goto bad_showdate; } else { time_t now; @@ -191,15 +199,14 @@ void date_main(void) } else if (setdate) { struct timeval tv; - if (parse_default(setdate, &tm)) goto bad_date; + if (parse_default(setdate, &tm)) error_exit("bad date '%s'", setdate); if (toys.optflags & FLAG_u) { // We can't just pass a timezone to mktime because posix. utzset(); - tv.tv_sec = chkmktime(&tm); + tv.tv_sec = chkmktime(&tm, setdate, format_string); utzreset(); - } else tv.tv_sec = chkmktime(&tm); - if (tv.tv_sec == (time_t)-1) goto bad_date; + } else tv.tv_sec = chkmktime(&tm, setdate, format_string); tv.tv_usec = TT.nano/1000; if (settimeofday(&tv, NULL) < 0) perror_msg("cannot set date"); @@ -212,6 +219,6 @@ void date_main(void) return; -bad_date: - error_exit("bad date '%s'", setdate); +bad_showdate: + error_exit("bad date '%s'", TT.showdate); } |