123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- /* 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);
- }
|