aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/lib.h2
-rw-r--r--lib/xwrap.c108
-rw-r--r--toys/posix/date.c133
3 files changed, 120 insertions, 123 deletions
diff --git a/lib/lib.h b/lib/lib.h
index 599ade06..174c1fd6 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -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);
}