getopt32: remove applet_long_options
[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 source tree.
11  */
12 /* This 'date' command supports only 2 time setting formats,
13    all the GNU strftime stuff (its in libc, lets use it),
14    setting time using UTC and displaying it, as well as
15    an RFC 2822 compliant date output for shell scripting
16    mail commands */
17
18 /* Input parsing code is always bulky - used heavy duty libc stuff as
19    much as possible, missed out a lot of bounds checking */
20
21 //config:config DATE
22 //config:       bool "date (7.1 kb)"
23 //config:       default y
24 //config:       help
25 //config:       date is used to set the system date or display the
26 //config:       current time in the given format.
27 //config:
28 //config:config FEATURE_DATE_ISOFMT
29 //config:       bool "Enable ISO date format output (-I)"
30 //config:       default y
31 //config:       depends on DATE
32 //config:       help
33 //config:       Enable option (-I) to output an ISO-8601 compliant
34 //config:       date/time string.
35 //config:
36 //config:# defaults to "no": stat's nanosecond field is a bit non-portable
37 //config:config FEATURE_DATE_NANO
38 //config:       bool "Support %[num]N nanosecond format specifier"
39 //config:       default n
40 //config:       depends on DATE  # syscall(__NR_clock_gettime)
41 //config:       select PLATFORM_LINUX
42 //config:       help
43 //config:       Support %[num]N format specifier. Adds ~250 bytes of code.
44 //config:
45 //config:config FEATURE_DATE_COMPAT
46 //config:       bool "Support weird 'date MMDDhhmm[[YY]YY][.ss]' format"
47 //config:       default y
48 //config:       depends on DATE
49 //config:       help
50 //config:       System time can be set by 'date -s DATE' and simply 'date DATE',
51 //config:       but formats of DATE string are different. 'date DATE' accepts
52 //config:       a rather weird MMDDhhmm[[YY]YY][.ss] format with completely
53 //config:       unnatural placement of year between minutes and seconds.
54 //config:       date -s (and other commands like touch -d) use more sensible
55 //config:       formats (for one, ISO format YYYY-MM-DD hh:mm:ss.ssssss).
56 //config:
57 //config:       With this option off, 'date DATE' is 'date -s DATE' support
58 //config:       the same format. With it on, 'date DATE' additionally supports
59 //config:       MMDDhhmm[[YY]YY][.ss] format.
60
61 //applet:IF_DATE(APPLET_NOEXEC(date, date, BB_DIR_BIN, BB_SUID_DROP, date))
62 /* bb_common_bufsiz1 usage here is safe wrt NOEXEC: not expecting it to be zeroed. */
63
64 //kbuild:lib-$(CONFIG_DATE) += date.o
65
66 /* GNU coreutils 6.9 man page:
67  * date [OPTION]... [+FORMAT]
68  * date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
69  * -d, --date=STRING
70  *      display time described by STRING, not 'now'
71  * -f, --file=DATEFILE
72  *      like --date once for each line of DATEFILE
73  * -r, --reference=FILE
74  *      display the last modification time of FILE
75  * -R, --rfc-2822
76  *      output date and time in RFC 2822 format.
77  *      Example: Mon, 07 Aug 2006 12:34:56 -0600
78  * --rfc-3339=TIMESPEC
79  *      output date and time in RFC 3339 format.
80  *      TIMESPEC='date', 'seconds', or 'ns'
81  *      Date and time components are separated by a single space:
82  *      2006-08-07 12:34:56-06:00
83  * -s, --set=STRING
84  *      set time described by STRING
85  * -u, --utc, --universal
86  *      print or set Coordinated Universal Time
87  *
88  * Busybox:
89  * long options are not supported
90  * -f is not supported
91  * -I seems to roughly match --rfc-3339, but -I has _optional_ param
92  *    (thus "-I seconds" doesn't work, only "-Iseconds"),
93  *    and does not support -Ins
94  * -D FMT is a bbox extension for _input_ conversion of -d DATE
95  */
96
97 //usage:#define date_trivial_usage
98 //usage:       "[OPTIONS] [+FMT] [TIME]"
99 //usage:#define date_full_usage "\n\n"
100 //usage:       "Display time (using +FMT), or set time\n"
101 //usage:        IF_NOT_LONG_OPTS(
102 //usage:     "\n        [-s] TIME       Set time to TIME"
103 //usage:     "\n        -u              Work in UTC (don't convert to local time)"
104 //usage:     "\n        -R              Output RFC-2822 compliant date string"
105 //usage:        ) IF_LONG_OPTS(
106 //usage:     "\n        [-s,--set] TIME Set time to TIME"
107 //usage:     "\n        -u,--utc        Work in UTC (don't convert to local time)"
108 //usage:     "\n        -R,--rfc-2822   Output RFC-2822 compliant date string"
109 //usage:        )
110 //usage:        IF_FEATURE_DATE_ISOFMT(
111 //usage:     "\n        -I[SPEC]        Output ISO-8601 compliant date string"
112 //usage:     "\n                        SPEC='date' (default) for date only,"
113 //usage:     "\n                        'hours', 'minutes', or 'seconds' for date and"
114 //usage:     "\n                        time to the indicated precision"
115 //usage:        )
116 //usage:        IF_NOT_LONG_OPTS(
117 //usage:     "\n        -r FILE         Display last modification time of FILE"
118 //usage:     "\n        -d TIME         Display TIME, not 'now'"
119 //usage:        ) IF_LONG_OPTS(
120 //usage:     "\n        -r,--reference FILE     Display last modification time of FILE"
121 //usage:     "\n        -d,--date TIME  Display TIME, not 'now'"
122 //usage:        )
123 //usage:        IF_FEATURE_DATE_ISOFMT(
124 //usage:     "\n        -D FMT          Use FMT for -d TIME conversion"
125 //usage:        )
126 //usage:     "\n"
127 //usage:     "\nRecognized TIME formats:"
128 //usage:     "\n        hh:mm[:ss]"
129 //usage:     "\n        [YYYY.]MM.DD-hh:mm[:ss]"
130 //usage:     "\n        YYYY-MM-DD hh:mm[:ss]"
131 //usage:     "\n        [[[[[YY]YY]MM]DD]hh]mm[.ss]"
132 //usage:        IF_FEATURE_DATE_COMPAT(
133 //usage:     "\n        'date TIME' form accepts MMDDhhmm[[YY]YY][.ss] instead"
134 //usage:        )
135 //usage:
136 //usage:#define date_example_usage
137 //usage:       "$ date\n"
138 //usage:       "Wed Apr 12 18:52:41 MDT 2000\n"
139
140 #include "libbb.h"
141 #include "common_bufsiz.h"
142 #if ENABLE_FEATURE_DATE_NANO
143 # include <sys/syscall.h>
144 #endif
145
146 enum {
147         OPT_RFC2822   = (1 << 0), /* R */
148         OPT_SET       = (1 << 1), /* s */
149         OPT_UTC       = (1 << 2), /* u */
150         OPT_DATE      = (1 << 3), /* d */
151         OPT_REFERENCE = (1 << 4), /* r */
152         OPT_TIMESPEC  = (1 << 5) * ENABLE_FEATURE_DATE_ISOFMT, /* I */
153         OPT_HINT      = (1 << 6) * ENABLE_FEATURE_DATE_ISOFMT, /* D */
154 };
155
156 #if ENABLE_LONG_OPTS
157 static const char date_longopts[] ALIGN1 =
158                 "rfc-822\0"   No_argument       "R"
159                 "rfc-2822\0"  No_argument       "R"
160                 "set\0"       Required_argument "s"
161                 "utc\0"       No_argument       "u"
162         /*      "universal\0" No_argument       "u" */
163                 "date\0"      Required_argument "d"
164                 "reference\0" Required_argument "r"
165                 ;
166 #endif
167
168 /* We are a NOEXEC applet.
169  * Obstacles to NOFORK:
170  * - we change env
171  * - xasprintf result not freed
172  * - after xasprintf we use other xfuncs
173  */
174
175 static void maybe_set_utc(int opt)
176 {
177         if (opt & OPT_UTC)
178                 putenv((char*)"TZ=UTC0");
179 }
180
181 int date_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
182 int date_main(int argc UNUSED_PARAM, char **argv)
183 {
184         struct timespec ts;
185         struct tm tm_time;
186         char buf_fmt_dt2str[64];
187         unsigned opt;
188         int ifmt = -1;
189         char *date_str;
190         char *fmt_dt2str;
191         char *fmt_str2dt;
192         char *filename;
193         char *isofmt_arg = NULL;
194
195         opt_complementary = "d--s:s--d"
196                 IF_FEATURE_DATE_ISOFMT(":R--I:I--R");
197         opt = getopt32long(argv, "Rs:ud:r:"
198                         IF_FEATURE_DATE_ISOFMT("I::D:"), date_longopts,
199                         &date_str, &date_str, &filename
200                         IF_FEATURE_DATE_ISOFMT(, &isofmt_arg, &fmt_str2dt));
201         argv += optind;
202         maybe_set_utc(opt);
203
204         if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_TIMESPEC)) {
205                 ifmt = 0; /* default is date */
206                 if (isofmt_arg) {
207                         static const char isoformats[] ALIGN1 =
208                                 "date\0""hours\0""minutes\0""seconds\0"; /* ns? */
209                         ifmt = index_in_substrings(isoformats, isofmt_arg);
210                         if (ifmt < 0)
211                                 bb_show_usage();
212                 }
213         }
214
215         fmt_dt2str = NULL;
216         if (argv[0] && argv[0][0] == '+') {
217                 fmt_dt2str = &argv[0][1]; /* skip over the '+' */
218                 argv++;
219         }
220         if (!(opt & (OPT_SET | OPT_DATE))) {
221                 opt |= OPT_SET;
222                 date_str = argv[0]; /* can be NULL */
223                 if (date_str) {
224 #if ENABLE_FEATURE_DATE_COMPAT
225                         int len = strspn(date_str, "0123456789");
226                         if (date_str[len] == '\0'
227                          || (date_str[len] == '.'
228                             && isdigit(date_str[len+1])
229                             && isdigit(date_str[len+2])
230                             && date_str[len+3] == '\0'
231                             )
232                         ) {
233                                 /* Dreaded MMDDhhmm[[CC]YY][.ss] format!
234                                  * It does not match -d or -s format.
235                                  * Some users actually do use it.
236                                  */
237                                 len -= 8;
238                                 if (len < 0 || len > 4 || (len & 1))
239                                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
240                                 if (len != 0) { /* move YY or CCYY to front */
241                                         char buf[4];
242                                         memcpy(buf, date_str + 8, len);
243                                         memmove(date_str + len, date_str, 8);
244                                         memcpy(date_str, buf, len);
245                                 }
246                         }
247 #endif
248                         argv++;
249                 }
250         }
251         if (*argv)
252                 bb_show_usage();
253
254         /* Now we have parsed all the information except the date format
255          * which depends on whether the clock is being set or read */
256
257         if (opt & OPT_REFERENCE) {
258                 struct stat statbuf;
259                 xstat(filename, &statbuf);
260                 ts.tv_sec = statbuf.st_mtime;
261 #if ENABLE_FEATURE_DATE_NANO
262                 ts.tv_nsec = statbuf.st_mtim.tv_nsec;
263                 /* Some toolchains use .st_mtimensec instead of st_mtim.tv_nsec.
264                  * If you need #define _SVID_SOURCE 1 to enable st_mtim.tv_nsec,
265                  * drop a mail to project mailing list please
266                  */
267 #endif
268         } else {
269 #if ENABLE_FEATURE_DATE_NANO
270                 /* libc has incredibly messy way of doing this,
271                  * typically requiring -lrt. We just skip all this mess */
272                 syscall(__NR_clock_gettime, CLOCK_REALTIME, &ts);
273 #else
274                 time(&ts.tv_sec);
275 #endif
276         }
277         localtime_r(&ts.tv_sec, &tm_time);
278
279         /* If date string is given, update tm_time, and maybe set date */
280         if (date_str != NULL) {
281                 /* Zero out fields - take her back to midnight! */
282                 tm_time.tm_sec = 0;
283                 tm_time.tm_min = 0;
284                 tm_time.tm_hour = 0;
285
286                 /* Process any date input to UNIX time since 1 Jan 1970 */
287                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_HINT)) {
288                         if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
289                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
290                 } else {
291                         parse_datestr(date_str, &tm_time);
292                 }
293
294                 /* Correct any day of week and day of year etc. fields */
295                 /* Be sure to recheck dst (but not if date is time_t format) */
296                 if (date_str[0] != '@')
297                         tm_time.tm_isdst = -1;
298                 ts.tv_sec = validate_tm_time(date_str, &tm_time);
299
300                 maybe_set_utc(opt);
301
302                 /* if setting time, set it */
303                 if ((opt & OPT_SET) && stime(&ts.tv_sec) < 0) {
304                         bb_perror_msg("can't set date");
305                 }
306         }
307
308         /* Display output */
309
310         /* Deal with format string */
311         if (fmt_dt2str == NULL) {
312                 int i;
313                 fmt_dt2str = buf_fmt_dt2str;
314                 if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
315                         /* -I[SPEC]: 0:date 1:hours 2:minutes 3:seconds */
316                         strcpy(fmt_dt2str, "%Y-%m-%dT%H:%M:%S");
317                         i = 8 + 3 * ifmt;
318                         if (ifmt != 0) {
319                                 /* TODO: if (ifmt==4) i += sprintf(&fmt_dt2str[i], ",%09u", nanoseconds); */
320  format_utc:
321                                 fmt_dt2str[i++] = '%';
322                                 fmt_dt2str[i++] = (opt & OPT_UTC) ? 'Z' : 'z';
323                         }
324                         fmt_dt2str[i] = '\0';
325                 } else if (opt & OPT_RFC2822) {
326                         /* -R. undo busybox.c setlocale */
327                         if (ENABLE_LOCALE_SUPPORT)
328                                 setlocale(LC_TIME, "C");
329                         strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
330                         i = sizeof("%a, %d %b %Y %H:%M:%S ")-1;
331                         goto format_utc;
332                 } else { /* default case */
333                         fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
334                 }
335         }
336 #if ENABLE_FEATURE_DATE_NANO
337         else {
338                 /* User-specified fmt_dt2str */
339                 /* Search for and process "%N" */
340                 char *p = fmt_dt2str;
341                 while ((p = strchr(p, '%')) != NULL) {
342                         int n, m;
343                         unsigned pres, scale;
344
345                         p++;
346                         if (*p == '%') {
347                                 p++;
348                                 continue;
349                         }
350                         n = strspn(p, "0123456789");
351                         if (p[n] != 'N') {
352                                 p += n;
353                                 continue;
354                         }
355                         /* We have "%[nnn]N" */
356                         p[-1] = '\0';
357                         p[n] = '\0';
358                         scale = 1;
359                         pres = 9;
360                         if (n) {
361                                 pres = xatoi_positive(p);
362                                 if (pres == 0)
363                                         pres = 9;
364                                 m = 9 - pres;
365                                 while (--m >= 0)
366                                         scale *= 10;
367                         }
368
369                         m = p - fmt_dt2str;
370                         p += n + 1;
371                         fmt_dt2str = xasprintf("%s%0*u%s", fmt_dt2str, pres, (unsigned)ts.tv_nsec / scale, p);
372                         p = fmt_dt2str + m;
373                 }
374         }
375 #endif
376
377 #define date_buf bb_common_bufsiz1
378         setup_common_bufsiz();
379         if (*fmt_dt2str == '\0') {
380                 /* With no format string, just print a blank line */
381                 date_buf[0] = '\0';
382         } else {
383                 /* Handle special conversions */
384                 if (is_prefixed_with(fmt_dt2str, "%f")) {
385                         fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
386                 }
387                 /* Generate output string */
388                 strftime(date_buf, COMMON_BUFSIZE, fmt_dt2str, &tm_time);
389         }
390         puts(date_buf);
391
392         return EXIT_SUCCESS;
393 }