aboutsummaryrefslogtreecommitdiff
path: root/toys
diff options
context:
space:
mode:
Diffstat (limited to 'toys')
-rw-r--r--toys/posix/date.c157
1 files changed, 94 insertions, 63 deletions
diff --git a/toys/posix/date.c b/toys/posix/date.c
index 09a548f5..43b558a5 100644
--- a/toys/posix/date.c
+++ b/toys/posix/date.c
@@ -29,6 +29,9 @@ config DATE
YYYY-MM-DD [hh:mm[:ss]] ISO 8601
hh:mm[:ss] 24-hour time today
+ All input formats can be preceded by TZ="id" to set the input time zone
+ separately from the output time zone. Otherwise $TZ sets both.
+
+FORMAT specifies display format string using strftime(3) syntax:
%% literal % %n newline %t tab
@@ -41,7 +44,7 @@ config DATE
%N nanosec (output only)
%U Week of year (0-53 start sunday) %W Week of year (0-53 start monday)
- %V Week of year (1-53 start monday, week < 4 days not part of this year)
+ %V Week of year (1-53 start monday, week < 4 days not part of this year)
%D = "%m/%d/%y" %r = "%I : %M : %S %p" %T = "%H:%M:%S" %h = "%b"
%x locale date %X locale time %c locale date/time
@@ -56,10 +59,25 @@ GLOBALS(
unsigned nano;
)
-// Handle default posix date format (mmddhhmm[[cc]yy]) or @UNIX[.FRAC]
-// returns 0 success, nonzero for error
-static int parse_default(char *str, struct tm *tm)
+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[] = {
@@ -71,12 +89,9 @@ static int parse_default(char *str, struct tm *tm)
// Parse @UNIXTIME[.FRACTION]
if (*str == '@') {
long long ll;
- time_t tt;
- // Collect seconds and nanoseconds
- // Note: struct tm hasn't got a fractional seconds field, thus strptime()
- // doesn't support it, so store nanoseconds out of band (in globals).
- // tt and ll are separate because we can't guarantee time_t is 64 bit (yet).
+ // 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;
@@ -86,9 +101,7 @@ static int parse_default(char *str, struct tm *tm)
}
}
if (str[len]) return 1;
- tt = ll;
- localtime_r(&tt, tm);
-
+ *t = ll;
return 0;
}
@@ -97,25 +110,28 @@ static int parse_default(char *str, struct tm *tm)
char *p;
now = time(0);
- localtime_r(&now, tm);
- tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
- if ((p = strptime(str, formats[i], tm)) && !*p) return 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);
+ 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--;
+ tm.tm_mon--;
- // If year specified, overwrite one we fetched earlier
+ // 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;
+ if (len == 4) tm.tm_year = year - 1900;
else if (len != 2) return 1;
str += len;
@@ -123,33 +139,55 @@ static int parse_default(char *str, struct tm *tm)
// 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;
+ 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;
+ tm.tm_year = year + century;
}
}
+ // Fractional part?
if (*str == '.') {
len = 0;
- sscanf(str, ".%u%n", &tm->tm_sec, &len);
+ sscanf(str, ".%u%n", &tm.tm_sec, &len);
str += len;
- } else tm->tm_sec = 0;
+ } else tm.tm_sec = 0;
- // Fix up weekday
- now = mktime(tm);
- localtime_r(&now, tm);
+ // Does that look like a valid date?
+ check_tm(&tm);
+ if ((*t = mktime(&tm)) == -1) return 1;
- // shouldn't be any trailing garbage
+ // Shouldn't be any trailing garbage.
return *str;
}
-static void check_range(int a, int low, int high)
+// Handles any leading `TZ="blah" ` in the input string.
+static int parse_date(char *str, time_t *t)
{
- if (a<low) error_exit("%d<%d", a, low);
- if (a>high) error_exit("%d>%d", a, high);
+ char *new_tz = NULL, *old_tz;
+ int result;
+
+ 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;
+
+ // Switch $TZ.
+ old_tz = getenv("TZ");
+ setenv("TZ", new_tz, 1);
+ tzset();
+ }
+ result = parse_formats(str, t);
+ 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
@@ -183,17 +221,25 @@ static void puts_time(char *fmt, struct tm *tm)
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));
+ char *setdate = *toys.optargs, *format_string = "%a %b %e %H:%M:%S %Z %Y",
+ *tz = NULL;
+ time_t t;
+
+ if (FLAG(u)) {
+ tz = getenv("TZ");
+ setenv("TZ", "UTC", 1);
+ tzset();
+ }
if (TT.d) {
if (TT.D) {
+ struct tm tm = {};
char *s = strptime(TT.d, TT.D+(*TT.D=='+'), &tm);
if (!s || *s) goto bad_showdate;
- } else if (parse_default(TT.d, &tm)) goto bad_showdate;
+ check_tm(&tm);
+ if ((t = mktime(&tm)) == -1) goto bad_showdate;
+ } else if (parse_date(TT.d, &t)) goto bad_showdate;
} else {
struct timespec ts;
struct stat st;
@@ -203,7 +249,7 @@ void date_main(void)
ts = st.st_mtim;
} else clock_gettime(CLOCK_REALTIME, &ts);
- ((toys.optflags & FLAG_u) ? gmtime_r : localtime_r)(&ts.tv_sec, &tm);
+ t = ts.tv_sec;
TT.nano = ts.tv_nsec;
}
@@ -216,37 +262,22 @@ void date_main(void)
// Set the date
} else if (setdate) {
- char *tz;
struct timeval tv;
- int u = toys.optflags & FLAG_u;
-
- if (parse_default(setdate, &tm)) goto bad_setdate;
-
- 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);
-
- if (u) {
- tz = getenv("TZ");
- setenv("TZ", "UTC", 1);
- tzset();
- }
- errno = 0;
- tv.tv_sec = mktime(&tm);
- if (errno) goto bad_setdate;
- if (u) {
- if (tz) setenv("TZ", tz, 1);
- else unsetenv("TZ");
- tzset();
- }
+ if (parse_date(setdate, &t)) goto bad_setdate;
+ tv.tv_sec = t;
tv.tv_usec = TT.nano/1000;
if (settimeofday(&tv, NULL) < 0) perror_msg("cannot set date");
}
- puts_time(format_string, &tm);
+ puts_time(format_string, localtime(&t));
+
+ if (FLAG(u)) {
+ if (tz) setenv("TZ", tz, 1);
+ else unsetenv("TZ");
+ tzset();
+ }
+
return;
bad_showdate: