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