date: handle 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 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         time_t tm;
89         unsigned opt;
90         int ifmt = -1;
91         char *date_str;
92         char *fmt_dt2str;
93         char *fmt_str2dt;
94         char *filename;
95         char *isofmt_arg = NULL;
96
97         opt_complementary = "d--s:s--d"
98                 IF_FEATURE_DATE_ISOFMT(":R--I:I--R");
99         IF_LONG_OPTS(applet_long_options = date_longopts;)
100         opt = getopt32(argv, "Rs:ud:r:"
101                         IF_FEATURE_DATE_ISOFMT("I::D:"),
102                         &date_str, &date_str, &filename
103                         IF_FEATURE_DATE_ISOFMT(, &isofmt_arg, &fmt_str2dt));
104         argv += optind;
105         maybe_set_utc(opt);
106
107         if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_TIMESPEC)) {
108                 ifmt = 0; /* default is date */
109                 if (isofmt_arg) {
110                         static const char isoformats[] ALIGN1 =
111                                 "date\0""hours\0""minutes\0""seconds\0";
112                         ifmt = index_in_strings(isoformats, isofmt_arg);
113                         if (ifmt < 0)
114                                 bb_show_usage();
115                 }
116         }
117
118         fmt_dt2str = NULL;
119         if (argv[0] && argv[0][0] == '+') {
120                 fmt_dt2str = &argv[0][1];       /* Skip over the '+' */
121                 argv++;
122         }
123         if (!(opt & (OPT_SET | OPT_DATE))) {
124                 opt |= OPT_SET;
125                 date_str = argv[0]; /* can be NULL */
126                 if (date_str)
127                         argv++;
128         }
129         if (*argv)
130                 bb_show_usage();
131
132         /* Now we have parsed all the information except the date format
133            which depends on whether the clock is being set or read */
134
135         if (opt & OPT_REFERENCE) {
136                 struct stat statbuf;
137                 xstat(filename, &statbuf);
138                 tm = statbuf.st_mtime;
139         } else {
140                 time(&tm);
141         }
142         localtime_r(&tm, &tm_time);
143
144         /* If date string is given, update tm_time, and maybe set date */
145         if (date_str != NULL) {
146                 /* Zero out fields - take her back to midnight! */
147                 tm_time.tm_sec = 0;
148                 tm_time.tm_min = 0;
149                 tm_time.tm_hour = 0;
150
151                 /* Process any date input to UNIX time since 1 Jan 1970 */
152                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & OPT_HINT)) {
153                         if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
154                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
155                 } else {
156                         parse_datestr(date_str, &tm_time);
157                 }
158
159                 /* Correct any day of week and day of year etc. fields */
160                 tm_time.tm_isdst = -1;  /* Be sure to recheck dst */
161                 tm = validate_tm_time(date_str, &tm_time);
162
163                 maybe_set_utc(opt);
164
165                 /* if setting time, set it */
166                 if ((opt & OPT_SET) && stime(&tm) < 0) {
167                         bb_perror_msg("cannot set date");
168                 }
169         }
170
171         /* Display output */
172
173         /* Deal with format string */
174         if (fmt_dt2str == NULL) {
175                 int i;
176                 fmt_dt2str = xzalloc(32);
177                 if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
178                         strcpy(fmt_dt2str, "%Y-%m-%d");
179                         if (ifmt > 0) {
180                                 i = 8;
181                                 fmt_dt2str[i++] = 'T';
182                                 fmt_dt2str[i++] = '%';
183                                 fmt_dt2str[i++] = 'H';
184                                 if (ifmt > 1) {
185                                         fmt_dt2str[i++] = ':';
186                                         fmt_dt2str[i++] = '%';
187                                         fmt_dt2str[i++] = 'M';
188                                         if (ifmt > 2) {
189                                                 fmt_dt2str[i++] = ':';
190                                                 fmt_dt2str[i++] = '%';
191                                                 fmt_dt2str[i++] = 'S';
192                                         }
193                                 }
194  format_utc:
195                                 fmt_dt2str[i++] = '%';
196                                 fmt_dt2str[i] = (opt & OPT_UTC) ? 'Z' : 'z';
197                         }
198                 } else if (opt & OPT_RFC2822) {
199                         /* Undo busybox.c for date -R */
200                         if (ENABLE_LOCALE_SUPPORT)
201                                 setlocale(LC_TIME, "C");
202                         strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
203                         i = 22;
204                         goto format_utc;
205                 } else /* default case */
206                         fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
207         }
208
209 #define date_buf bb_common_bufsiz1
210         if (*fmt_dt2str == '\0') {
211                 /* With no format string, just print a blank line */
212                 date_buf[0] = '\0';
213         } else {
214                 /* Handle special conversions */
215                 if (strncmp(fmt_dt2str, "%f", 2) == 0) {
216                         fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
217                 }
218
219                 /* Generate output string */
220                 strftime(date_buf, sizeof(date_buf), fmt_dt2str, &tm_time);
221         }
222         puts(date_buf);
223
224         return EXIT_SUCCESS;
225 }