mkfs_ext2: fix bad comment. no code changes
[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 #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 tm tm_time;
88         char buf_fmt_dt2str[64];
89         time_t tm;
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                 tm = statbuf.st_mtime;
165         } else {
166                 time(&tm);
167         }
168         localtime_r(&tm, &tm_time);
169
170         /* If date string is given, update tm_time, and maybe set date */
171         if (date_str != NULL) {
172                 /* Zero out fields - take her back to midnight! */
173                 tm_time.tm_sec = 0;
174                 tm_time.tm_min = 0;
175                 tm_time.tm_hour = 0;
176
177                 /* Process any date input to UNIX time since 1 Jan 1970 */
178                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_HINT)) {
179                         if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
180                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
181                 } else {
182                         parse_datestr(date_str, &tm_time);
183                 }
184
185                 /* Correct any day of week and day of year etc. fields */
186                 tm_time.tm_isdst = -1;  /* Be sure to recheck dst */
187                 tm = validate_tm_time(date_str, &tm_time);
188
189                 maybe_set_utc(opt);
190
191                 /* if setting time, set it */
192                 if ((opt & OPT_SET) && stime(&tm) < 0) {
193                         bb_perror_msg("can't set date");
194                 }
195         }
196
197         /* Display output */
198
199         /* Deal with format string */
200         if (fmt_dt2str == NULL) {
201                 int i;
202                 fmt_dt2str = buf_fmt_dt2str;
203                 if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
204                         /* -I[SPEC]: 0:date 1:hours 2:minutes 3:seconds */
205                         strcpy(fmt_dt2str, "%Y-%m-%dT%H:%M:%S");
206                         i = 8 + 3 * ifmt;
207                         if (ifmt != 0) {
208                                 /* TODO: if (ifmt==4) i += sprintf(&fmt_dt2str[i], ",%09u", nanoseconds); */
209  format_utc:
210                                 fmt_dt2str[i++] = '%';
211                                 fmt_dt2str[i++] = (opt & OPT_UTC) ? 'Z' : 'z';
212                         }
213                         fmt_dt2str[i] = '\0';
214                 } else if (opt & OPT_RFC2822) {
215                         /* -R. undo busybox.c setlocale */
216                         if (ENABLE_LOCALE_SUPPORT)
217                                 setlocale(LC_TIME, "C");
218                         strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
219                         i = sizeof("%a, %d %b %Y %H:%M:%S ")-1;
220                         goto format_utc;
221                 } else { /* default case */
222                         fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
223                 }
224         }
225
226 #define date_buf bb_common_bufsiz1
227         if (*fmt_dt2str == '\0') {
228                 /* With no format string, just print a blank line */
229                 date_buf[0] = '\0';
230         } else {
231                 /* Handle special conversions */
232                 if (strncmp(fmt_dt2str, "%f", 2) == 0) {
233                         fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
234                 }
235                 /* Generate output string */
236                 strftime(date_buf, sizeof(date_buf), fmt_dt2str, &tm_time);
237         }
238         puts(date_buf);
239
240         return EXIT_SUCCESS;
241 }