date: optional support for %N. Closes bug 1861.
[platform/upstream/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 #include "libbb.h"
55
56 enum {
57         OPT_RFC2822   = (1 << 0), /* R */
58         OPT_SET       = (1 << 1), /* s */
59         OPT_UTC       = (1 << 2), /* u */
60         OPT_DATE      = (1 << 3), /* d */
61         OPT_REFERENCE = (1 << 4), /* r */
62         OPT_TIMESPEC  = (1 << 5) * ENABLE_FEATURE_DATE_ISOFMT, /* I */
63         OPT_HINT      = (1 << 6) * ENABLE_FEATURE_DATE_ISOFMT, /* D */
64 };
65
66 static void maybe_set_utc(int opt)
67 {
68         if (opt & OPT_UTC)
69                 putenv((char*)"TZ=UTC0");
70 }
71
72 #if ENABLE_LONG_OPTS
73 static const char date_longopts[] ALIGN1 =
74                 "rfc-822\0"   No_argument       "R"
75                 "rfc-2822\0"  No_argument       "R"
76                 "set\0"       Required_argument "s"
77                 "utc\0"       No_argument       "u"
78         /*      "universal\0" No_argument       "u" */
79                 "date\0"      Required_argument "d"
80                 "reference\0" Required_argument "r"
81                 ;
82 #endif
83
84 int date_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
85 int date_main(int argc UNUSED_PARAM, char **argv)
86 {
87         struct timespec ts;
88         struct tm tm_time;
89         char buf_fmt_dt2str[64];
90         unsigned opt;
91         int ifmt = -1;
92         char *date_str;
93         char *fmt_dt2str;
94         char *fmt_str2dt;
95         char *filename;
96         char *isofmt_arg = NULL;
97
98         opt_complementary = "d--s:s--d"
99                 IF_FEATURE_DATE_ISOFMT(":R--I:I--R");
100         IF_LONG_OPTS(applet_long_options = date_longopts;)
101         opt = getopt32(argv, "Rs:ud:r:"
102                         IF_FEATURE_DATE_ISOFMT("I::D:"),
103                         &date_str, &date_str, &filename
104                         IF_FEATURE_DATE_ISOFMT(, &isofmt_arg, &fmt_str2dt));
105         argv += optind;
106         maybe_set_utc(opt);
107
108         if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_TIMESPEC)) {
109                 ifmt = 0; /* default is date */
110                 if (isofmt_arg) {
111                         static const char isoformats[] ALIGN1 =
112                                 "date\0""hours\0""minutes\0""seconds\0"; /* ns? */
113                         ifmt = index_in_substrings(isoformats, isofmt_arg);
114                         if (ifmt < 0)
115                                 bb_show_usage();
116                 }
117         }
118
119         fmt_dt2str = NULL;
120         if (argv[0] && argv[0][0] == '+') {
121                 fmt_dt2str = &argv[0][1]; /* skip over the '+' */
122                 argv++;
123         }
124         if (!(opt & (OPT_SET | OPT_DATE))) {
125                 opt |= OPT_SET;
126                 date_str = argv[0]; /* can be NULL */
127                 if (date_str) {
128 #if ENABLE_FEATURE_DATE_COMPAT
129                         int len = strspn(date_str, "0123456789");
130                         if (date_str[len] == '\0'
131                          || (date_str[len] == '.'
132                             && isdigit(date_str[len+1])
133                             && isdigit(date_str[len+2])
134                             && date_str[len+3] == '\0'
135                             )
136                         ) {
137                                 /* Dreaded MMDDhhmm[[CC]YY][.ss] format!
138                                  * It does not match -d or -s format.
139                                  * Some users actually do use it.
140                                  */
141                                 len -= 8;
142                                 if (len < 0 || len > 4 || (len & 1))
143                                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
144                                 if (len != 0) { /* move YY or CCYY to front */
145                                         char buf[4];
146                                         memcpy(buf, date_str + 8, len);
147                                         memmove(date_str + len, date_str, 8);
148                                         memcpy(date_str, buf, len);
149                                 }
150                         }
151 #endif
152                         argv++;
153                 }
154         }
155         if (*argv)
156                 bb_show_usage();
157
158         /* Now we have parsed all the information except the date format
159          * which depends on whether the clock is being set or read */
160
161         if (opt & OPT_REFERENCE) {
162                 struct stat statbuf;
163                 xstat(filename, &statbuf);
164                 ts.tv_sec = statbuf.st_mtime;
165 #if ENABLE_FEATURE_DATE_NANO
166                 ts.tv_nsec = statbuf.st_mtimensec; //or st_atim.tv_nsec?
167 #endif
168         } else {
169 #if ENABLE_FEATURE_DATE_NANO
170                 clock_gettime(CLOCK_REALTIME, &ts);
171 #else
172                 time(&ts.tv_nsec);
173 #endif
174         }
175         localtime_r(&ts.tv_sec, &tm_time);
176
177         /* If date string is given, update tm_time, and maybe set date */
178         if (date_str != NULL) {
179                 /* Zero out fields - take her back to midnight! */
180                 tm_time.tm_sec = 0;
181                 tm_time.tm_min = 0;
182                 tm_time.tm_hour = 0;
183
184                 /* Process any date input to UNIX time since 1 Jan 1970 */
185                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_HINT)) {
186                         if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
187                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
188                 } else {
189                         parse_datestr(date_str, &tm_time);
190                 }
191
192                 /* Correct any day of week and day of year etc. fields */
193                 tm_time.tm_isdst = -1;  /* Be sure to recheck dst */
194                 ts.tv_sec = validate_tm_time(date_str, &tm_time);
195
196                 maybe_set_utc(opt);
197
198                 /* if setting time, set it */
199                 if ((opt & OPT_SET) && stime(&ts.tv_sec) < 0) {
200                         bb_perror_msg("can't set date");
201                 }
202         }
203
204         /* Display output */
205
206         /* Deal with format string */
207         if (fmt_dt2str == NULL) {
208                 int i;
209                 fmt_dt2str = buf_fmt_dt2str;
210                 if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
211                         /* -I[SPEC]: 0:date 1:hours 2:minutes 3:seconds */
212                         strcpy(fmt_dt2str, "%Y-%m-%dT%H:%M:%S");
213                         i = 8 + 3 * ifmt;
214                         if (ifmt != 0) {
215                                 /* TODO: if (ifmt==4) i += sprintf(&fmt_dt2str[i], ",%09u", nanoseconds); */
216  format_utc:
217                                 fmt_dt2str[i++] = '%';
218                                 fmt_dt2str[i++] = (opt & OPT_UTC) ? 'Z' : 'z';
219                         }
220                         fmt_dt2str[i] = '\0';
221                 } else if (opt & OPT_RFC2822) {
222                         /* -R. undo busybox.c setlocale */
223                         if (ENABLE_LOCALE_SUPPORT)
224                                 setlocale(LC_TIME, "C");
225                         strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
226                         i = sizeof("%a, %d %b %Y %H:%M:%S ")-1;
227                         goto format_utc;
228                 } else { /* default case */
229                         fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
230                 }
231         }
232 #if ENABLE_FEATURE_DATE_NANO
233         else {
234                 /* User-specified fmt_dt2str */
235                 /* Search for and process "%N" */
236                 char *p = fmt_dt2str;
237                 while ((p = strchr(p, '%')) != NULL) {
238                         int n, m;
239                         unsigned pres, scale;
240
241                         p++;
242                         if (*p == '%') {
243                                 p++;
244                                 continue;
245                         }
246                         n = strspn(p, "0123456789");
247                         if (p[n] != 'N') {
248                                 p += n;
249                                 continue;
250                         }
251                         /* We have "%[nnn]N" */
252                         p[-1] = '\0';
253                         p[n] = '\0';
254                         scale = 1;
255                         pres = 9;
256                         if (n) {
257                                 pres = xatoi_u(p);
258                                 if (pres == 0)
259                                         pres = 9;
260                                 m = 9 - pres;
261                                 while (--m >= 0)
262                                         scale *= 10;
263                         }
264
265                         m = p - fmt_dt2str;
266                         p += n + 1;
267                         fmt_dt2str = xasprintf("%s%0*u%s", fmt_dt2str, pres, (unsigned)ts.tv_nsec / scale, p);
268                         p = fmt_dt2str + m;
269                 }
270         }
271 #endif
272
273 #define date_buf bb_common_bufsiz1
274         if (*fmt_dt2str == '\0') {
275                 /* With no format string, just print a blank line */
276                 date_buf[0] = '\0';
277         } else {
278                 /* Handle special conversions */
279                 if (strncmp(fmt_dt2str, "%f", 2) == 0) {
280                         fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
281                 }
282                 /* Generate output string */
283                 strftime(date_buf, sizeof(date_buf), fmt_dt2str, &tm_time);
284         }
285         puts(date_buf);
286
287         return EXIT_SUCCESS;
288 }