summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2015-08-11 16:06:06 -0500
committerRob Landley <rob@landley.net>2015-08-11 16:06:06 -0500
commit05499787ca89fa4b017c2441e89020799f02e4c1 (patch)
treec8e48c77d96b47fd710238f82240126e71798981
parent7f6bb3dae7ffe5ffdf10cc2e73a803ff820ebca8 (diff)
downloadtoybox-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.test16
-rw-r--r--toys/posix/date.c51
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);
}