Fix more date bugs.
authorElliott Hughes <enh@google.com>
Tue, 11 Aug 2015 21:06:06 +0000 (16:06 -0500)
committerRob Landley <rob@landley.net>
Tue, 11 Aug 2015 21:06:06 +0000 (16:06 -0500)
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.

tests/date.test
toys/posix/date.c

index d72e50c..94a4157 100644 (file)
@@ -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" "" ""
index 909ca5a..a42de50 100644 (file)
@@ -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);
 }