toybox/toys/posix/date.c
<<
>>
Prefs
   1/* date.c - set/get the date
   2 *
   3 * Copyright 2012 Andre Renaud <andre@bluewatersys.com>
   4 *
   5 * See http://opengroup.org/onlinepubs/9699919799/utilities/date.html
   6 *
   7 * Note: setting a 2 year date is 50 years back/forward from today,
   8 * not posix's hardwired magic dates.
   9
  10USE_DATE(NEWTOY(date, "d:D:I(iso)(iso-8601):;r:s:u(utc)[!dr]", TOYFLAG_BIN))
  11
  12config DATE
  13  bool "date"
  14  default y
  15  help
  16    usage: date [-u] [-I RES] [-r FILE] [-d DATE] [+DISPLAY_FORMAT] [-D SET_FORMAT] [SET]
  17
  18    Set/get the current date/time. With no SET shows the current date.
  19
  20    -d  Show DATE instead of current time (convert date format)
  21    -D  +FORMAT for SET or -d (instead of MMDDhhmm[[CC]YY][.ss])
  22    -I RES      ISO 8601 with RESolution d=date/h=hours/m=minutes/s=seconds/n=ns
  23    -r  Use modification time of FILE instead of current date
  24    -s DATE     Set the system clock to DATE.
  25    -u  Use UTC instead of current timezone
  26
  27    Supported input formats:
  28
  29    MMDDhhmm[[CC]YY][.ss]     POSIX
  30    @UNIXTIME[.FRACTION]      seconds since midnight 1970-01-01
  31    YYYY-MM-DD [hh:mm[:ss]]   ISO 8601
  32    hh:mm[:ss]                24-hour time today
  33
  34    All input formats can be followed by fractional seconds, and/or a UTC
  35    offset such as -0800.
  36
  37    All input formats can be preceded by TZ="id" to set the input time zone
  38    separately from the output time zone. Otherwise $TZ sets both.
  39
  40    +FORMAT specifies display format string using strftime(3) syntax:
  41
  42    %% literal %             %n newline              %t tab
  43    %S seconds (00-60)       %M minute (00-59)       %m month (01-12)
  44    %H hour (0-23)           %I hour (01-12)         %p AM/PM
  45    %y short year (00-99)    %Y year                 %C century
  46    %a short weekday name    %A weekday name         %u day of week (1-7, 1=mon)
  47    %b short month name      %B month name           %Z timezone name
  48    %j day of year (001-366) %d day of month (01-31) %e day of month ( 1-31)
  49    %N nanosec (output only)
  50
  51    %U Week of year (0-53 start Sunday)   %W Week of year (0-53 start Monday)
  52    %V Week of year (1-53 start Monday, week < 4 days not part of this year)
  53
  54    %F "%Y-%m-%d"   %R "%H:%M"        %T "%H:%M:%S"        %z  timezone (-0800)
  55    %D "%m/%d/%y"   %r "%I:%M:%S %p"  %h "%b"              %:z timezone (-08:00)
  56    %x locale date  %X locale time    %c locale date/time  %s  unix epoch time
  57*/
  58
  59#define FOR_date
  60#include "toys.h"
  61
  62GLOBALS(
  63  char *s, *r, *I, *D, *d;
  64
  65  unsigned nano;
  66)
  67
  68// Handles any leading `TZ="blah" ` in the input string.
  69static void parse_date(char *str, time_t *t)
  70{
  71  char *new_tz = NULL, *old_tz, *s = str;
  72
  73  if (!strncmp(str, "TZ=\"", 4)) {
  74    // Extract the time zone and skip any whitespace.
  75    new_tz = str+4;
  76    if (!(str = strchr(new_tz, '"'))) xvali_date(0, s);
  77    *str++ = 0;
  78    while (isspace(*str)) str++;
  79
  80    // Switch $TZ.
  81    old_tz = getenv("TZ");
  82    setenv("TZ", new_tz, 1);
  83    tzset();
  84  }
  85  time(t);
  86  xparsedate(str, t, &TT.nano, 1);
  87  if (new_tz) {
  88    if (old_tz) setenv("TZ", old_tz, 1);
  89    else unsetenv("TZ");
  90  }
  91}
  92
  93// Print strftime plus %N and %:z escape(s). Note: modifies fmt in those cases.
  94static void puts_time(char *fmt, struct tm *tm)
  95{
  96  char *s, *snap, *out;
  97
  98  for (s = fmt;;s++) {
  99    long n = 0;
 100
 101    // Find next %N/%:z or end of format string.
 102    if (*(snap = s)) {
 103      if (*s != '%') continue;
 104      if (*++s == 'N') n = 9;
 105      else if (isdigit(*s) && s[1] == 'N') n = *s++-'0';
 106      else if (*s == ':' && s[1] == 'z') s++, n++;
 107      else continue;
 108    }
 109
 110    // Only modify input string if needed (default format is constant string).
 111    if (*s) *snap = 0;
 112    // Do we have any regular work for strftime to do?
 113    out = toybuf;
 114    if (*fmt) {
 115      if (!strftime(out, sizeof(toybuf)-12, fmt, tm))
 116        perror_exit("bad format '%s'", fmt);
 117      out += strlen(out);
 118    }
 119    // Do we have any custom formatting to append to that?
 120    if (*s == 'N') {
 121      sprintf(out, "%09u", TT.nano);
 122      out[n] = 0;
 123    } else if (*s == 'z') {
 124      strftime(out, 10, "%z", tm);
 125      memmove(out+4, out+3, strlen(out+3)+1);
 126      out[3] = ':';
 127    }
 128    xputsn(toybuf);
 129    if (!*s || !*(fmt = s+1)) break;
 130  }
 131  xputc('\n');
 132}
 133
 134void date_main(void)
 135{
 136  char *setdate = *toys.optargs, *format_string = "%a %b %e %H:%M:%S %Z %Y",
 137    *tz = NULL;
 138  time_t t;
 139
 140  if (FLAG(I)) {
 141    char *iso_formats[] = {"%F","%FT%H%:z","%FT%R%:z","%FT%T%:z","%FT%T,%N%:z"};
 142    int i = stridx("dhmsn", (TT.I && *TT.I) ? *TT.I : 'd');
 143
 144    if (i<0) help_exit("bad -I: %s", TT.I);
 145    format_string = xstrdup(iso_formats[i]);
 146  }
 147
 148  if (FLAG(u)) {
 149    tz = getenv("TZ");
 150    setenv("TZ", "UTC", 1);
 151    tzset();
 152  }
 153
 154  if (TT.d) {
 155    if (TT.D) {
 156      struct tm tm = {};
 157      char *s = strptime(TT.d, TT.D+(*TT.D=='+'), &tm);
 158
 159      t = (s && *s) ? xvali_date(&tm, s) : xvali_date(0, TT.d);
 160    } else parse_date(TT.d, &t);
 161  } else {
 162    struct timespec ts;
 163    struct stat st;
 164
 165    if (TT.r) {
 166      xstat(TT.r, &st);
 167      ts = st.st_mtim;
 168    } else clock_gettime(CLOCK_REALTIME, &ts);
 169
 170    t = ts.tv_sec;
 171    TT.nano = ts.tv_nsec;
 172  }
 173
 174  if (FLAG(s)) {
 175    if (setdate) help_exit("can't set two dates at once");
 176    setdate = TT.s;
 177  }
 178
 179  // Fall through if no arguments
 180  if (!setdate);
 181  // Display the date?
 182  else if (*setdate == '+') {
 183    format_string = toys.optargs[0]+1;
 184    setdate = toys.optargs[1];
 185
 186  // Set the date
 187  } else if (setdate) {
 188    struct timeval tv;
 189
 190    parse_date(setdate, &t);
 191    tv.tv_sec = t;
 192    tv.tv_usec = TT.nano/1000;
 193    if (settimeofday(&tv, NULL) < 0) perror_msg("cannot set date");
 194  }
 195
 196  puts_time(format_string, localtime(&t));
 197
 198  if (FLAG(u)) {
 199    if (tz) setenv("TZ", tz, 1);
 200    else unsetenv("TZ");
 201    tzset();
 202  }
 203  if (CFG_TOYBOX_FREE && FLAG(I)) free(format_string);
 204}
 205