| /* date.c - set/get the date |
| * |
| * Copyright 2012 Andre Renaud <andre@bluewatersys.com> |
| * |
| * See http://opengroup.org/onlinepubs/9699919799/utilities/date.html |
| * |
| * Note: setting a 2 year date is 50 years back/forward from today, |
| * not posix's hardwired magic dates. |
| |
| USE_DATE(NEWTOY(date, "d:D:I(iso)(iso-8601):;r:s:u(utc)[!dr]", TOYFLAG_BIN)) |
| |
| config DATE |
| bool "date" |
| default y |
| help |
| usage: date [-u] [-I RES] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET] |
| |
| Set/get the current date/time. With no SET shows the current date. |
| |
| -d Show DATE instead of current time (convert date format) |
| -D +FORMAT for SET or -d (instead of MMDDhhmm[[CC]YY][.ss]) |
| -I RES ISO 8601 with RESolution d=date/h=hours/m=minutes/s=seconds/n=ns |
| -r Use modification time of FILE instead of current date |
| -s DATE Set the system clock to DATE. |
| -u Use UTC instead of current timezone |
| |
| Supported input formats: |
| |
| MMDDhhmm[[CC]YY][.ss] POSIX |
| @UNIXTIME[.FRACTION] seconds since midnight 1970-01-01 |
| YYYY-MM-DD [hh:mm[:ss]] ISO 8601 |
| hh:mm[:ss] 24-hour time today |
| |
| All input formats can be followed by fractional seconds, and/or a UTC |
| offset such as -0800. |
| |
| 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 |
| %S seconds (00-60) %M minute (00-59) %m month (01-12) |
| %H hour (0-23) %I hour (01-12) %p AM/PM |
| %y short year (00-99) %Y year %C century |
| %a short weekday name %A weekday name %u day of week (1-7, 1=mon) |
| %b short month name %B month name %Z timezone name |
| %j day of year (001-366) %d day of month (01-31) %e day of month ( 1-31) |
| %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) |
| |
| %F "%Y-%m-%d" %R "%H:%M" %T "%H:%M:%S" %z timezone (-0800) |
| %D "%m/%d/%y" %r "%I:%M:%S %p" %h "%b" %:z timezone (-08:00) |
| %x locale date %X locale time %c locale date/time %s unix epoch time |
| */ |
| |
| #define FOR_date |
| #include "toys.h" |
| |
| GLOBALS( |
| char *s, *r, *I, *D, *d; |
| |
| unsigned nano; |
| ) |
| |
| // Handles any leading `TZ="blah" ` in the input string. |
| static void parse_date(char *str, time_t *t) |
| { |
| 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; |
| 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(); |
| } |
| time(t); |
| xparsedate(str, t, &TT.nano, 1); |
| if (new_tz) { |
| if (old_tz) setenv("TZ", old_tz, 1); |
| else unsetenv("TZ"); |
| } |
| } |
| |
| // Print strftime plus %N and %:z escape(s). Note: modifies fmt in those cases. |
| static void puts_time(char *fmt, struct tm *tm) |
| { |
| char *s, *snap, *out; |
| |
| for (s = fmt;;s++) { |
| long n = 0; |
| |
| // Find next %N/%:z or end of format string. |
| if (*(snap = s)) { |
| if (*s != '%') continue; |
| if (*++s == 'N') n = 9; |
| else if (isdigit(*s) && s[1] == 'N') n = *s++-'0'; |
| else if (*s == ':' && s[1] == 'z') s++, n++; |
| else continue; |
| } |
| |
| // Only modify input string if needed (default format is constant string). |
| if (*s) *snap = 0; |
| // Do we have any regular work for strftime to do? |
| out = toybuf; |
| if (*fmt) { |
| if (!strftime(out, sizeof(toybuf)-12, fmt, tm)) |
| perror_exit("bad format '%s'", fmt); |
| out += strlen(out); |
| } |
| // Do we have any custom formatting to append to that? |
| if (*s == 'N') { |
| sprintf(out, "%09u", TT.nano); |
| out[n] = 0; |
| } else if (*s == 'z') { |
| strftime(out, 10, "%z", tm); |
| memmove(out+4, out+3, strlen(out+3)+1); |
| out[3] = ':'; |
| } |
| xputsn(toybuf); |
| if (!*s || !*(fmt = s+1)) break; |
| } |
| xputc('\n'); |
| } |
| |
| void date_main(void) |
| { |
| char *setdate = *toys.optargs, *format_string = "%a %b %e %H:%M:%S %Z %Y", |
| *tz = NULL; |
| time_t t; |
| |
| if (FLAG(I)) { |
| char *iso_formats[] = {"%F","%FT%H%:z","%FT%R%:z","%FT%T%:z","%FT%T,%N%:z"}; |
| int i = stridx("dhmsn", (TT.I && *TT.I) ? *TT.I : 'd'); |
| |
| if (i<0) help_exit("bad -I: %s", TT.I); |
| format_string = xstrdup(iso_formats[i]); |
| } |
| |
| 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); |
| |
| 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; |
| |
| if (TT.r) { |
| xstat(TT.r, &st); |
| ts = st.st_mtim; |
| } else clock_gettime(CLOCK_REALTIME, &ts); |
| |
| t = ts.tv_sec; |
| TT.nano = ts.tv_nsec; |
| } |
| |
| if (FLAG(s)) { |
| if (setdate) help_exit("can't set two dates at once"); |
| setdate = TT.s; |
| } |
| |
| // Fall through if no arguments |
| if (!setdate); |
| // Display the date? |
| else if (*setdate == '+') { |
| format_string = toys.optargs[0]+1; |
| setdate = toys.optargs[1]; |
| |
| // Set the date |
| } else if (setdate) { |
| struct timeval tv; |
| |
| parse_date(setdate, &t); |
| tv.tv_sec = t; |
| tv.tv_usec = TT.nano/1000; |
| if (settimeofday(&tv, NULL) < 0) perror_msg("cannot set date"); |
| } |
| |
| puts_time(format_string, localtime(&t)); |
| |
| if (FLAG(u)) { |
| if (tz) setenv("TZ", tz, 1); |
| else unsetenv("TZ"); |
| tzset(); |
| } |
| if (CFG_TOYBOX_FREE && FLAG(I)) free(format_string); |
| } |