1 /* vi: set sw=4 ts=4: */
3 * Mini date implementation for busybox
5 * by Matthew Grant <grantma@anathoth.gen.nz>
7 * iso-format handling added by Robert Griebl <griebl@gmx.de>
8 * bugfixes and cleanup by Bernhard Reutner-Fischer
10 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
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
19 /* Input parsing code is always bulky - used heavy duty libc stuff as
20 much as possible, missed out a lot of bounds checking */
22 /* Default input handling to save surprising some people */
24 /* GNU coreutils 6.9 man page:
25 * date [OPTION]... [+FORMAT]
26 * date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
28 * display time described by STRING, not `now'
30 * like --date once for each line of DATEFILE
31 * -r, --reference=FILE
32 * display the last modification time of FILE
34 * output date and time in RFC 2822 format.
35 * Example: Mon, 07 Aug 2006 12:34:56 -0600
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
42 * set time described by STRING
43 * -u, --utc, --universal
44 * print or set Coordinated Universal Time
47 * long options are 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
55 //kbuild:lib-$(CONFIG_DATE) += date.o
61 //config: date is used to set the system date or display the
62 //config: current time in the given format.
64 //config:config FEATURE_DATE_ISOFMT
65 //config: bool "Enable ISO date format output (-I)"
67 //config: depends on DATE
69 //config: Enable option (-I) to output an ISO-8601 compliant
70 //config: date/time string.
72 //config:config FEATURE_DATE_NANO
73 //config: bool "Support %[num]N nanosecond format specifier"
75 //config: depends on DATE
77 //config: Support %[num]N format specifier. Adds ~250 bytes of code.
79 //config:config FEATURE_DATE_COMPAT
80 //config: bool "Support weird 'date MMDDhhmm[[YY]YY][.ss]' format"
82 //config: depends on DATE
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).
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.
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 */
107 static void maybe_set_utc(int opt)
110 putenv((char*)"TZ=UTC0");
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"
125 int date_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
126 int date_main(int argc UNUSED_PARAM, char **argv)
130 char buf_fmt_dt2str[64];
137 char *isofmt_arg = NULL;
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));
149 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_TIMESPEC)) {
150 ifmt = 0; /* default is date */
152 static const char isoformats[] ALIGN1 =
153 "date\0""hours\0""minutes\0""seconds\0"; /* ns? */
154 ifmt = index_in_substrings(isoformats, isofmt_arg);
161 if (argv[0] && argv[0][0] == '+') {
162 fmt_dt2str = &argv[0][1]; /* skip over the '+' */
165 if (!(opt & (OPT_SET | OPT_DATE))) {
167 date_str = argv[0]; /* can be NULL */
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'
178 /* Dreaded MMDDhhmm[[CC]YY][.ss] format!
179 * It does not match -d or -s format.
180 * Some users actually do use it.
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 */
187 memcpy(buf, date_str + 8, len);
188 memmove(date_str + len, date_str, 8);
189 memcpy(date_str, buf, len);
199 /* Now we have parsed all the information except the date format
200 * which depends on whether the clock is being set or read */
202 if (opt & OPT_REFERENCE) {
204 xstat(filename, &statbuf);
205 ts.tv_sec = statbuf.st_mtime;
206 #if ENABLE_FEATURE_DATE_NANO
207 ts.tv_nsec = statbuf.st_mtim.tv_nsec;
210 #if ENABLE_FEATURE_DATE_NANO
211 clock_gettime(CLOCK_REALTIME, &ts);
216 localtime_r(&ts.tv_sec, &tm_time);
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! */
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);
230 parse_datestr(date_str, &tm_time);
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);
239 /* if setting time, set it */
240 if ((opt & OPT_SET) && stime(&ts.tv_sec) < 0) {
241 bb_perror_msg("can't set date");
247 /* Deal with format string */
248 if (fmt_dt2str == NULL) {
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");
256 /* TODO: if (ifmt==4) i += sprintf(&fmt_dt2str[i], ",%09u", nanoseconds); */
258 fmt_dt2str[i++] = '%';
259 fmt_dt2str[i++] = (opt & OPT_UTC) ? 'Z' : 'z';
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;
269 } else { /* default case */
270 fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
273 #if ENABLE_FEATURE_DATE_NANO
275 /* User-specified fmt_dt2str */
276 /* Search for and process "%N" */
277 char *p = fmt_dt2str;
278 while ((p = strchr(p, '%')) != NULL) {
280 unsigned pres, scale;
287 n = strspn(p, "0123456789");
292 /* We have "%[nnn]N" */
308 fmt_dt2str = xasprintf("%s%0*u%s", fmt_dt2str, pres, (unsigned)ts.tv_nsec / scale, p);
314 #define date_buf bb_common_bufsiz1
315 if (*fmt_dt2str == '\0') {
316 /* With no format string, just print a blank line */
319 /* Handle special conversions */
320 if (strncmp(fmt_dt2str, "%f", 2) == 0) {
321 fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
323 /* Generate output string */
324 strftime(date_buf, sizeof(date_buf), fmt_dt2str, &tm_time);