partially migrate coreutils to Config.src and Kbuild.src
[oweals/busybox.git] / coreutils / date.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini date implementation for busybox
4  *
5  * by Matthew Grant <grantma@anathoth.gen.nz>
6  *
7  * iso-format handling added by Robert Griebl <griebl@gmx.de>
8  * bugfixes and cleanup by Bernhard Reutner-Fischer
9  *
10  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
11 */
12
13 /* This 'date' command supports only 2 time setting formats,
14    all the GNU strftime stuff (its in libc, lets use it),
15    setting time using UTC and displaying it, as well as
16    an RFC 2822 compliant date output for shell scripting
17    mail commands */
18
19 /* Input parsing code is always bulky - used heavy duty libc stuff as
20    much as possible, missed out a lot of bounds checking */
21
22 /* Default input handling to save surprising some people */
23
24 /* GNU coreutils 6.9 man page:
25  * date [OPTION]... [+FORMAT]
26  * date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
27  * -d, --date=STRING
28  *      display time described by STRING, not `now'
29  * -f, --file=DATEFILE
30  *      like --date once for each line of DATEFILE
31  * -r, --reference=FILE
32  *      display the last modification time of FILE
33  * -R, --rfc-2822
34  *      output date and time in RFC 2822 format.
35  *      Example: Mon, 07 Aug 2006 12:34:56 -0600
36  * --rfc-3339=TIMESPEC
37  *      output date and time in RFC 3339 format.
38  *      TIMESPEC='date', 'seconds', or 'ns'
39  *      Date and time components are separated by a single space:
40  *      2006-08-07 12:34:56-06:00
41  * -s, --set=STRING
42  *      set time described by STRING
43  * -u, --utc, --universal
44  *      print or set Coordinated Universal Time
45  *
46  * Busybox:
47  * long options are not supported
48  * -f is not supported
49  * -I seems to roughly match --rfc-3339, but -I has _optional_ param
50  *    (thus "-I seconds" doesn't work, only "-Iseconds"),
51  *    and does not support -Ins
52  * -D FMT is a bbox extension for _input_ conversion of -d DATE
53  */
54
55 //kbuild:lib-$(CONFIG_DATE) += date.o
56
57 //config:config DATE
58 //config:       bool "date"
59 //config:       default n
60 //config:       help
61 //config:         date is used to set the system date or display the
62 //config:         current time in the given format.
63 //config:
64 //config:config FEATURE_DATE_ISOFMT
65 //config:       bool "Enable ISO date format output (-I)"
66 //config:       default y
67 //config:       depends on DATE
68 //config:       help
69 //config:         Enable option (-I) to output an ISO-8601 compliant
70 //config:         date/time string.
71 //config:
72 //config:config FEATURE_DATE_NANO
73 //config:       bool "Support %[num]N nanosecond format specifier"
74 //config:       default y
75 //config:       depends on DATE
76 //config:       help
77 //config:         Support %[num]N format specifier. Adds ~250 bytes of code.
78 //config:
79 //config:config FEATURE_DATE_COMPAT
80 //config:       bool "Support weird 'date MMDDhhmm[[YY]YY][.ss]' format"
81 //config:       default y
82 //config:       depends on DATE
83 //config:       help
84 //config:         System time can be set by 'date -s DATE' and simply 'date DATE',
85 //config:         but formats of DATE string are different. 'date DATE' accepts
86 //config:         a rather weird MMDDhhmm[[YY]YY][.ss] format with completely
87 //config:         unnatural placement of year between minutes and seconds.
88 //config:         date -s (and other commands like touch -d) use more sensible
89 //config:         formats (for one, ISO format YYYY-MM-DD hh:mm:ss.ssssss).
90 //config:
91 //config:         With this option off, 'date DATE' is 'date -s DATE' support
92 //config:         the same format. With it on, 'date DATE' additionally supports
93 //config:         MMDDhhmm[[YY]YY][.ss] format.
94
95 #include "libbb.h"
96
97 enum {
98         OPT_RFC2822   = (1 << 0), /* R */
99         OPT_SET       = (1 << 1), /* s */
100         OPT_UTC       = (1 << 2), /* u */
101         OPT_DATE      = (1 << 3), /* d */
102         OPT_REFERENCE = (1 << 4), /* r */
103         OPT_TIMESPEC  = (1 << 5) * ENABLE_FEATURE_DATE_ISOFMT, /* I */
104         OPT_HINT      = (1 << 6) * ENABLE_FEATURE_DATE_ISOFMT, /* D */
105 };
106
107 static void maybe_set_utc(int opt)
108 {
109         if (opt & OPT_UTC)
110                 putenv((char*)"TZ=UTC0");
111 }
112
113 #if ENABLE_LONG_OPTS
114 static const char date_longopts[] ALIGN1 =
115                 "rfc-822\0"   No_argument       "R"
116                 "rfc-2822\0"  No_argument       "R"
117                 "set\0"       Required_argument "s"
118                 "utc\0"       No_argument       "u"
119         /*      "universal\0" No_argument       "u" */
120                 "date\0"      Required_argument "d"
121                 "reference\0" Required_argument "r"
122                 ;
123 #endif
124
125 int date_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
126 int date_main(int argc UNUSED_PARAM, char **argv)
127 {
128         struct timespec ts;
129         struct tm tm_time;
130         char buf_fmt_dt2str[64];
131         unsigned opt;
132         int ifmt = -1;
133         char *date_str;
134         char *fmt_dt2str;
135         char *fmt_str2dt;
136         char *filename;
137         char *isofmt_arg = NULL;
138
139         opt_complementary = "d--s:s--d"
140                 IF_FEATURE_DATE_ISOFMT(":R--I:I--R");
141         IF_LONG_OPTS(applet_long_options = date_longopts;)
142         opt = getopt32(argv, "Rs:ud:r:"
143                         IF_FEATURE_DATE_ISOFMT("I::D:"),
144                         &date_str, &date_str, &filename
145                         IF_FEATURE_DATE_ISOFMT(, &isofmt_arg, &fmt_str2dt));
146         argv += optind;
147         maybe_set_utc(opt);
148
149         if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_TIMESPEC)) {
150                 ifmt = 0; /* default is date */
151                 if (isofmt_arg) {
152                         static const char isoformats[] ALIGN1 =
153                                 "date\0""hours\0""minutes\0""seconds\0"; /* ns? */
154                         ifmt = index_in_substrings(isoformats, isofmt_arg);
155                         if (ifmt < 0)
156                                 bb_show_usage();
157                 }
158         }
159
160         fmt_dt2str = NULL;
161         if (argv[0] && argv[0][0] == '+') {
162                 fmt_dt2str = &argv[0][1]; /* skip over the '+' */
163                 argv++;
164         }
165         if (!(opt & (OPT_SET | OPT_DATE))) {
166                 opt |= OPT_SET;
167                 date_str = argv[0]; /* can be NULL */
168                 if (date_str) {
169 #if ENABLE_FEATURE_DATE_COMPAT
170                         int len = strspn(date_str, "0123456789");
171                         if (date_str[len] == '\0'
172                          || (date_str[len] == '.'
173                             && isdigit(date_str[len+1])
174                             && isdigit(date_str[len+2])
175                             && date_str[len+3] == '\0'
176                             )
177                         ) {
178                                 /* Dreaded MMDDhhmm[[CC]YY][.ss] format!
179                                  * It does not match -d or -s format.
180                                  * Some users actually do use it.
181                                  */
182                                 len -= 8;
183                                 if (len < 0 || len > 4 || (len & 1))
184                                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
185                                 if (len != 0) { /* move YY or CCYY to front */
186                                         char buf[4];
187                                         memcpy(buf, date_str + 8, len);
188                                         memmove(date_str + len, date_str, 8);
189                                         memcpy(date_str, buf, len);
190                                 }
191                         }
192 #endif
193                         argv++;
194                 }
195         }
196         if (*argv)
197                 bb_show_usage();
198
199         /* Now we have parsed all the information except the date format
200          * which depends on whether the clock is being set or read */
201
202         if (opt & OPT_REFERENCE) {
203                 struct stat statbuf;
204                 xstat(filename, &statbuf);
205                 ts.tv_sec = statbuf.st_mtime;
206 #if ENABLE_FEATURE_DATE_NANO
207                 ts.tv_nsec = statbuf.st_mtimensec; //or st_atim.tv_nsec?
208 #endif
209         } else {
210 #if ENABLE_FEATURE_DATE_NANO
211                 clock_gettime(CLOCK_REALTIME, &ts);
212 #else
213                 time(&ts.tv_nsec);
214 #endif
215         }
216         localtime_r(&ts.tv_sec, &tm_time);
217
218         /* If date string is given, update tm_time, and maybe set date */
219         if (date_str != NULL) {
220                 /* Zero out fields - take her back to midnight! */
221                 tm_time.tm_sec = 0;
222                 tm_time.tm_min = 0;
223                 tm_time.tm_hour = 0;
224
225                 /* Process any date input to UNIX time since 1 Jan 1970 */
226                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_HINT)) {
227                         if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
228                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
229                 } else {
230                         parse_datestr(date_str, &tm_time);
231                 }
232
233                 /* Correct any day of week and day of year etc. fields */
234                 tm_time.tm_isdst = -1;  /* Be sure to recheck dst */
235                 ts.tv_sec = validate_tm_time(date_str, &tm_time);
236
237                 maybe_set_utc(opt);
238
239                 /* if setting time, set it */
240                 if ((opt & OPT_SET) && stime(&ts.tv_sec) < 0) {
241                         bb_perror_msg("can't set date");
242                 }
243         }
244
245         /* Display output */
246
247         /* Deal with format string */
248         if (fmt_dt2str == NULL) {
249                 int i;
250                 fmt_dt2str = buf_fmt_dt2str;
251                 if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
252                         /* -I[SPEC]: 0:date 1:hours 2:minutes 3:seconds */
253                         strcpy(fmt_dt2str, "%Y-%m-%dT%H:%M:%S");
254                         i = 8 + 3 * ifmt;
255                         if (ifmt != 0) {
256                                 /* TODO: if (ifmt==4) i += sprintf(&fmt_dt2str[i], ",%09u", nanoseconds); */
257  format_utc:
258                                 fmt_dt2str[i++] = '%';
259                                 fmt_dt2str[i++] = (opt & OPT_UTC) ? 'Z' : 'z';
260                         }
261                         fmt_dt2str[i] = '\0';
262                 } else if (opt & OPT_RFC2822) {
263                         /* -R. undo busybox.c setlocale */
264                         if (ENABLE_LOCALE_SUPPORT)
265                                 setlocale(LC_TIME, "C");
266                         strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
267                         i = sizeof("%a, %d %b %Y %H:%M:%S ")-1;
268                         goto format_utc;
269                 } else { /* default case */
270                         fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
271                 }
272         }
273 #if ENABLE_FEATURE_DATE_NANO
274         else {
275                 /* User-specified fmt_dt2str */
276                 /* Search for and process "%N" */
277                 char *p = fmt_dt2str;
278                 while ((p = strchr(p, '%')) != NULL) {
279                         int n, m;
280                         unsigned pres, scale;
281
282                         p++;
283                         if (*p == '%') {
284                                 p++;
285                                 continue;
286                         }
287                         n = strspn(p, "0123456789");
288                         if (p[n] != 'N') {
289                                 p += n;
290                                 continue;
291                         }
292                         /* We have "%[nnn]N" */
293                         p[-1] = '\0';
294                         p[n] = '\0';
295                         scale = 1;
296                         pres = 9;
297                         if (n) {
298                                 pres = xatoi_u(p);
299                                 if (pres == 0)
300                                         pres = 9;
301                                 m = 9 - pres;
302                                 while (--m >= 0)
303                                         scale *= 10;
304                         }
305
306                         m = p - fmt_dt2str;
307                         p += n + 1;
308                         fmt_dt2str = xasprintf("%s%0*u%s", fmt_dt2str, pres, (unsigned)ts.tv_nsec / scale, p);
309                         p = fmt_dt2str + m;
310                 }
311         }
312 #endif
313
314 #define date_buf bb_common_bufsiz1
315         if (*fmt_dt2str == '\0') {
316                 /* With no format string, just print a blank line */
317                 date_buf[0] = '\0';
318         } else {
319                 /* Handle special conversions */
320                 if (strncmp(fmt_dt2str, "%f", 2) == 0) {
321                         fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
322                 }
323                 /* Generate output string */
324                 strftime(date_buf, sizeof(date_buf), fmt_dt2str, &tm_time);
325         }
326         puts(date_buf);
327
328         return EXIT_SUCCESS;
329 }