diff options
-rw-r--r-- | lib/lib.h | 2 | ||||
-rw-r--r-- | lib/xwrap.c | 108 | ||||
-rw-r--r-- | toys/posix/date.c | 133 |
3 files changed, 120 insertions, 123 deletions
@@ -181,6 +181,8 @@ void xregcomp(regex_t *preg, char *rexec, int cflags); char *xtzset(char *new); void xsignal_flags(int signal, void *handler, int flags); void xsignal(int signal, void *handler); +time_t xvali_date(struct tm *tm, char *str); +void xparsedate(char *str, time_t *t, unsigned *nano); // lib.c void verror_msg(char *msg, int err, va_list va); diff --git a/lib/xwrap.c b/lib/xwrap.c index c133125a..ee07fda2 100644 --- a/lib/xwrap.c +++ b/lib/xwrap.c @@ -944,3 +944,111 @@ void xsignal(int signal, void *handler) { xsignal_flags(signal, handler, 0); } + + +time_t xvali_date(struct tm *tm, char *str) +{ + time_t t; + + if (tm && (unsigned)tm->tm_sec<=60 && (unsigned)tm->tm_min<=59 + && (unsigned)tm->tm_hour<=23 && tm->tm_mday && (unsigned)tm->tm_mday<=31 + && (unsigned)tm->tm_mon<=11 && (t = mktime(tm)) != -1) return t; + + error_exit("bad date %s", str); +} + +// Parse date string (relative to current *t). Sets time_t and nanoseconds. +void xparsedate(char *str, time_t *t, unsigned *nano) +{ + struct tm tm; + time_t now = *t; + int len = 0, i; + // Formats with years must come first. + char *s = str, *p, *formats[] = {"%F %T", "%FT%T", "%F %H:%M", "%F", + "%H:%M:%S", "%H:%M"}; + + *nano = 0; + + // Parse @UNIXTIME[.FRACTION] + if (*str == '@') { + long long ll; + + // Collect seconds and nanoseconds. + // &ll is not just t because we can't guarantee time_t is 64 bit (yet). + sscanf(s, "@%lld%n", &ll, &len); + if (s[len]=='.') { + s += len+1; + for (len = 0; len<9; len++) { + *nano *= 10; + if (isdigit(*s)) *nano += *s++-'0'; + } + } + if (s[len]) goto bad_dates; + *t = ll; + + return; + } + + // Is it one of the fancy formats? + for (i = 0; i<ARRAY_LEN(formats); i++) { + localtime_r(&now, &tm); + tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + tm.tm_isdst = -1; + if ((p = strptime(s, formats[i], &tm)) && !*p) { + *t = xvali_date(&tm, str); + + return; + } + } + + // Posix format? + sscanf(s, "%2u%2u%2u%2u%n", &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, + &tm.tm_min, &len); + if (len != 8) goto bad_dates; + s += len; + tm.tm_mon--; + + // If year specified, overwrite one we fetched earlier. + if (*s && *s != '.') { + unsigned year; + + len = 0; + sscanf(s, "%u%n", &year, &len); + if (len == 4) tm.tm_year = year - 1900; + else if (len != 2) goto bad_dates; + s += len; + + // 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 (len == 2) { + unsigned r1 = tm.tm_year % 100, r2 = (tm.tm_year + 50) % 100, + century = tm.tm_year - r1; + + 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; + } + } + // Fractional part? + if (*s == '.') { + len = 0; + sscanf(s, ".%2u%n", &tm.tm_sec, &len); + s += len; + for (len = 0; len<9; len++) { + *nano *= 10; + if (isdigit(*s)) *nano += *s++-'0'; + } + } else tm.tm_sec = 0; + + // Sanity check field ranges + *t = xvali_date(&tm, str); + + // Shouldn't be any trailing garbage. + if (!*s) return; + +bad_dates: + // monkey died + xvali_date(0, str); +} diff --git a/toys/posix/date.c b/toys/posix/date.c index 43b558a5..685ac8bf 100644 --- a/toys/posix/date.c +++ b/toys/posix/date.c @@ -59,135 +59,29 @@ GLOBALS( unsigned nano; ) -static void check_range(int a, int low, int high) -{ - if (a<low) error_exit("%d<%d", a, low); - if (a>high) error_exit("%d>%d", a, high); -} - -static void check_tm(struct tm *tm) -{ - check_range(tm->tm_sec, 0, 60); - check_range(tm->tm_min, 0, 59); - check_range(tm->tm_hour, 0, 23); - check_range(tm->tm_mday, 1, 31); - check_range(tm->tm_mon, 0, 11); -} - -// Returns 0 success, nonzero for error. -static int parse_formats(char *str, time_t *t) -{ - struct tm tm; - time_t now; - int len = 0, i; - char *formats[] = { - // Formats with years must come first. - "%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d", - "%H:%M:%S", "%H:%M" - }; - - // Parse @UNIXTIME[.FRACTION] - if (*str == '@') { - long long ll; - - // Collect seconds and nanoseconds. - // &ll is not just t because we can't guarantee time_t is 64 bit (yet). - sscanf(str, "@%lld%n", &ll, &len); - if (str[len]=='.') { - str += len+1; - for (len = 0; len<9; len++) { - TT.nano *= 10; - if (isdigit(str[len])) TT.nano += str[len]-'0'; - } - } - if (str[len]) return 1; - *t = ll; - return 0; - } - - // Is it one of the fancy formats? - for (i = 0; i<ARRAY_LEN(formats); i++) { - char *p; - - now = time(0); - localtime_r(&now, &tm); - tm.tm_hour = tm.tm_min = tm.tm_sec = 0; - tm.tm_isdst = -1; - if ((p = strptime(str,formats[i],&tm)) && !*p) { - if ((*t = mktime(&tm)) != -1) return 0; - } - } - - // Posix format? - sscanf(str, "%2u%2u%2u%2u%n", &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, - &tm.tm_min, &len); - if (len != 8) return 1; - str += len; - tm.tm_mon--; - - // If year specified, overwrite one we fetched earlier. - if (*str && *str != '.') { - unsigned year; - - len = 0; - sscanf(str, "%u%n", &year, &len); - if (len == 4) tm.tm_year = year - 1900; - else if (len != 2) return 1; - str += len; - - // 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 (len == 2) { - unsigned r1 = tm.tm_year % 100, r2 = (tm.tm_year + 50) % 100, - century = tm.tm_year - r1; - - 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; - } - } - // Fractional part? - if (*str == '.') { - len = 0; - sscanf(str, ".%u%n", &tm.tm_sec, &len); - str += len; - } else tm.tm_sec = 0; - - // Does that look like a valid date? - check_tm(&tm); - if ((*t = mktime(&tm)) == -1) return 1; - - // Shouldn't be any trailing garbage. - return *str; -} - // Handles any leading `TZ="blah" ` in the input string. -static int parse_date(char *str, time_t *t) +static void parse_date(char *str, time_t *t) { - char *new_tz = NULL, *old_tz; - int result; + char *new_tz = NULL, *old_tz, *s = str; if (!strncmp(str, "TZ=\"", 4)) { // Extract the time zone and skip any whitespace. new_tz = str+4; - str = strchr(new_tz, '"'); - if (!str) return 1; - *str++ = '\0'; - while (isspace(*str)) ++str; + if (!(str = strchr(new_tz, '"'))) xvali_date(0, s); + *str++ = 0; + while (isspace(*str)) str++; // Switch $TZ. old_tz = getenv("TZ"); setenv("TZ", new_tz, 1); tzset(); } - result = parse_formats(str, t); + time(t); + xparsedate(str, t, &TT.nano); if (new_tz) { if (old_tz) setenv("TZ", old_tz, 1); else unsetenv("TZ"); } - return result; } // Print strftime plus %N escape(s). note: modifies fmt for %N @@ -236,10 +130,8 @@ void date_main(void) struct tm tm = {}; char *s = strptime(TT.d, TT.D+(*TT.D=='+'), &tm); - if (!s || *s) goto bad_showdate; - check_tm(&tm); - if ((t = mktime(&tm)) == -1) goto bad_showdate; - } else if (parse_date(TT.d, &t)) goto bad_showdate; + t = (s && *s) ? xvali_date(&tm, s) : xvali_date(0, TT.d); + } else parse_date(TT.d, &t); } else { struct timespec ts; struct stat st; @@ -264,7 +156,7 @@ void date_main(void) } else if (setdate) { struct timeval tv; - if (parse_date(setdate, &t)) goto bad_setdate; + parse_date(setdate, &t); tv.tv_sec = t; tv.tv_usec = TT.nano/1000; if (settimeofday(&tv, NULL) < 0) perror_msg("cannot set date"); @@ -279,9 +171,4 @@ void date_main(void) } return; - -bad_showdate: - setdate = TT.d; -bad_setdate: - error_exit("bad date '%s'", setdate); } |