bc: convert to "G trick" - this returns bc to zero bss increase
[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  # syscall(__NR_clock_gettime)
40 //config:       depends on DATE
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 = getopt32long(argv, "^"
196                         "Rs:ud:r:"
197                         IF_FEATURE_DATE_ISOFMT("I::D:")
198                         "\0"
199                         "d--s:s--d"
200                         IF_FEATURE_DATE_ISOFMT(":R--I:I--R"),
201                         date_longopts,
202                         &date_str, &date_str, &filename
203                         IF_FEATURE_DATE_ISOFMT(, &isofmt_arg, &fmt_str2dt)
204         );
205         argv += optind;
206
207         maybe_set_utc(opt);
208
209         if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_TIMESPEC)) {
210                 ifmt = 0; /* default is date */
211                 if (isofmt_arg) {
212                         static const char isoformats[] ALIGN1 =
213                                 "date\0""hours\0""minutes\0""seconds\0"; /* ns? */
214                         ifmt = index_in_substrings(isoformats, isofmt_arg);
215                         if (ifmt < 0)
216                                 bb_show_usage();
217                 }
218         }
219
220         fmt_dt2str = NULL;
221         if (argv[0] && argv[0][0] == '+') {
222                 fmt_dt2str = &argv[0][1]; /* skip over the '+' */
223                 argv++;
224         }
225         if (!(opt & (OPT_SET | OPT_DATE))) {
226                 opt |= OPT_SET;
227                 date_str = argv[0]; /* can be NULL */
228                 if (date_str) {
229 #if ENABLE_FEATURE_DATE_COMPAT
230                         int len = strspn(date_str, "0123456789");
231                         if (date_str[len] == '\0'
232                          || (date_str[len] == '.'
233                             && isdigit(date_str[len+1])
234                             && isdigit(date_str[len+2])
235                             && date_str[len+3] == '\0'
236                             )
237                         ) {
238                                 /* Dreaded MMDDhhmm[[CC]YY][.ss] format!
239                                  * It does not match -d or -s format.
240                                  * Some users actually do use it.
241                                  */
242                                 len -= 8;
243                                 if (len < 0 || len > 4 || (len & 1))
244                                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
245                                 if (len != 0) { /* move YY or CCYY to front */
246                                         char buf[4];
247                                         memcpy(buf, date_str + 8, len);
248                                         memmove(date_str + len, date_str, 8);
249                                         memcpy(date_str, buf, len);
250                                 }
251                         }
252 #endif
253                         argv++;
254                 }
255         }
256         if (*argv)
257                 bb_show_usage();
258
259         /* Now we have parsed all the information except the date format
260          * which depends on whether the clock is being set or read */
261
262         if (opt & OPT_REFERENCE) {
263                 struct stat statbuf;
264                 xstat(filename, &statbuf);
265                 ts.tv_sec = statbuf.st_mtime;
266 #if ENABLE_FEATURE_DATE_NANO
267                 ts.tv_nsec = statbuf.st_mtim.tv_nsec;
268                 /* Some toolchains use .st_mtimensec instead of st_mtim.tv_nsec.
269                  * If you need #define _SVID_SOURCE 1 to enable st_mtim.tv_nsec,
270                  * drop a mail to project mailing list please
271                  */
272 #endif
273         } else {
274 #if ENABLE_FEATURE_DATE_NANO
275                 /* libc has incredibly messy way of doing this,
276                  * typically requiring -lrt. We just skip all this mess */
277                 syscall(__NR_clock_gettime, CLOCK_REALTIME, &ts);
278 #else
279                 time(&ts.tv_sec);
280 #endif
281         }
282         localtime_r(&ts.tv_sec, &tm_time);
283
284         /* If date string is given, update tm_time, and maybe set date */
285         if (date_str != NULL) {
286                 /* Zero out fields - take her back to midnight! */
287                 tm_time.tm_sec = 0;
288                 tm_time.tm_min = 0;
289                 tm_time.tm_hour = 0;
290
291                 /* Process any date input to UNIX time since 1 Jan 1970 */
292                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_HINT)) {
293                         if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
294                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
295                 } else {
296                         parse_datestr(date_str, &tm_time);
297                 }
298
299                 /* Correct any day of week and day of year etc. fields */
300                 /* Be sure to recheck dst (but not if date is time_t format) */
301                 if (date_str[0] != '@')
302                         tm_time.tm_isdst = -1;
303                 ts.tv_sec = validate_tm_time(date_str, &tm_time);
304
305                 /* if setting time, set it */
306                 if ((opt & OPT_SET) && stime(&ts.tv_sec) < 0) {
307                         bb_perror_msg("can't set date");
308                 }
309         }
310
311         /* Display output */
312
313         /* Deal with format string */
314         if (fmt_dt2str == NULL) {
315                 int i;
316                 fmt_dt2str = buf_fmt_dt2str;
317                 if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
318                         /* -I[SPEC]: 0:date 1:hours 2:minutes 3:seconds */
319                         strcpy(fmt_dt2str, "%Y-%m-%dT%H:%M:%S");
320                         i = 8 + 3 * ifmt;
321                         if (ifmt != 0) {
322                                 /* TODO: if (ifmt==4) i += sprintf(&fmt_dt2str[i], ",%09u", nanoseconds); */
323  format_utc:
324                                 fmt_dt2str[i++] = '%';
325                                 fmt_dt2str[i++] = (opt & OPT_UTC) ? 'Z' : 'z';
326                         }
327                         fmt_dt2str[i] = '\0';
328                 } else if (opt & OPT_RFC2822) {
329                         /* -R. undo busybox.c setlocale */
330                         if (ENABLE_LOCALE_SUPPORT)
331                                 setlocale(LC_TIME, "C");
332                         strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
333                         i = sizeof("%a, %d %b %Y %H:%M:%S ")-1;
334                         goto format_utc;
335                 } else { /* default case */
336                         fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
337                 }
338         }
339 #if ENABLE_FEATURE_DATE_NANO
340         else {
341                 /* User-specified fmt_dt2str */
342                 /* Search for and process "%N" */
343                 char *p = fmt_dt2str;
344                 while ((p = strchr(p, '%')) != NULL) {
345                         int n, m;
346                         unsigned pres, scale;
347
348                         p++;
349                         if (*p == '%') {
350                                 p++;
351                                 continue;
352                         }
353                         n = strspn(p, "0123456789");
354                         if (p[n] != 'N') {
355                                 p += n;
356                                 continue;
357                         }
358                         /* We have "%[nnn]N" */
359                         p[-1] = '\0';
360                         p[n] = '\0';
361                         scale = 1;
362                         pres = 9;
363                         if (n) {
364                                 pres = xatoi_positive(p);
365                                 if (pres == 0)
366                                         pres = 9;
367                                 m = 9 - pres;
368                                 while (--m >= 0)
369                                         scale *= 10;
370                         }
371
372                         m = p - fmt_dt2str;
373                         p += n + 1;
374                         fmt_dt2str = xasprintf("%s%0*u%s", fmt_dt2str, pres, (unsigned)ts.tv_nsec / scale, p);
375                         p = fmt_dt2str + m;
376                 }
377         }
378 #endif
379
380 #define date_buf bb_common_bufsiz1
381         setup_common_bufsiz();
382         if (*fmt_dt2str == '\0') {
383                 /* With no format string, just print a blank line */
384                 date_buf[0] = '\0';
385         } else {
386                 /* Handle special conversions */
387                 if (is_prefixed_with(fmt_dt2str, "%f")) {
388                         fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
389                 }
390                 /* Generate output string */
391                 strftime(date_buf, COMMON_BUFSIZE, fmt_dt2str, &tm_time);
392         }
393         puts(date_buf);
394
395         return EXIT_SUCCESS;
396 }