getopt_ulflags -> getopt32.
[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 Fischer
9  *
10  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
11 */
12
13 #include "busybox.h"
14
15 /* This 'date' command supports only 2 time setting formats,
16    all the GNU strftime stuff (its in libc, lets use it),
17    setting time using UTC and displaying it, as well as
18    an RFC 2822 compliant date output for shell scripting
19    mail commands */
20
21 /* Input parsing code is always bulky - used heavy duty libc stuff as
22    much as possible, missed out a lot of bounds checking */
23
24 /* Default input handling to save surprising some people */
25
26
27 #define DATE_OPT_RFC2822        0x01
28 #define DATE_OPT_SET            0x02
29 #define DATE_OPT_UTC            0x04
30 #define DATE_OPT_DATE           0x08
31 #define DATE_OPT_REFERENCE      0x10
32 #define DATE_OPT_TIMESPEC       0x20
33 #define DATE_OPT_HINT           0x40
34
35 static void maybe_set_utc(int opt)
36 {
37         if ((opt & DATE_OPT_UTC) && putenv("TZ=UTC0") != 0)
38                 bb_error_msg_and_die(bb_msg_memory_exhausted);
39 }
40
41 int date_main(int argc, char **argv)
42 {
43         time_t tm;
44         struct tm tm_time;
45         unsigned opt;
46         int ifmt = -1;
47         char *date_str = NULL;
48         char *date_fmt = NULL;
49         char *filename = NULL;
50         char *isofmt_arg;
51         char *hintfmt_arg;
52
53         opt_complementary = "?:d--s:s--d"
54                 USE_FEATURE_DATE_ISOFMT(":R--I:I--R");
55         opt = getopt32(argc, argv, "Rs:ud:r:"
56                                         USE_FEATURE_DATE_ISOFMT("I::D:"),
57                                         &date_str, &date_str, &filename
58                                         USE_FEATURE_DATE_ISOFMT(, &isofmt_arg, &hintfmt_arg));
59         maybe_set_utc(opt);
60
61         if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_TIMESPEC)) {
62                 if (!isofmt_arg) {
63                         ifmt = 0; /* default is date */
64                 } else {
65                         const char * const isoformats[] =
66                                 {"date", "hours", "minutes", "seconds"};
67
68                         for (ifmt = 0; ifmt < 4; ifmt++)
69                                 if (!strcmp(isofmt_arg, isoformats[ifmt])) {
70                                         break;
71                                 }
72                         if (ifmt == 4) /* parse error */
73                                 bb_show_usage();
74                 }
75         }
76
77         /* XXX, date_fmt == NULL from this always */
78         if ((date_fmt == NULL) && (optind < argc) && (argv[optind][0] == '+')) {
79                 date_fmt = &argv[optind][1];    /* Skip over the '+' */
80         } else if (date_str == NULL) {
81                 opt |= DATE_OPT_SET;
82                 date_str = argv[optind];
83         }
84
85         /* Now we have parsed all the information except the date format
86            which depends on whether the clock is being set or read */
87
88         if (filename) {
89                 struct stat statbuf;
90                 xstat(filename, &statbuf);
91                 tm = statbuf.st_mtime;
92         } else
93                 time(&tm);
94         memcpy(&tm_time, localtime(&tm), sizeof(tm_time));
95         /* Zero out fields - take her back to midnight! */
96         if (date_str != NULL) {
97                 tm_time.tm_sec = 0;
98                 tm_time.tm_min = 0;
99                 tm_time.tm_hour = 0;
100
101                 /* Process any date input to UNIX time since 1 Jan 1970 */
102                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_HINT)) {
103                         strptime(date_str, hintfmt_arg, &tm_time);
104                 } else if (strchr(date_str, ':') != NULL) {
105                         /* Parse input and assign appropriately to tm_time */
106
107                         if (sscanf(date_str, "%d:%d:%d", &tm_time.tm_hour, &tm_time.tm_min,
108                                                                  &tm_time.tm_sec) == 3) {
109                                 /* no adjustments needed */
110                         } else if (sscanf(date_str, "%d:%d", &tm_time.tm_hour,
111                                                                                 &tm_time.tm_min) == 2) {
112                                 /* no adjustments needed */
113                         } else if (sscanf(date_str, "%d.%d-%d:%d:%d", &tm_time.tm_mon,
114                                                                 &tm_time.tm_mday, &tm_time.tm_hour,
115                                                                 &tm_time.tm_min, &tm_time.tm_sec) == 5) {
116                                 /* Adjust dates from 1-12 to 0-11 */
117                                 tm_time.tm_mon -= 1;
118                         } else if (sscanf(date_str, "%d.%d-%d:%d", &tm_time.tm_mon,
119                                                                 &tm_time.tm_mday,
120                                                                 &tm_time.tm_hour, &tm_time.tm_min) == 4) {
121                                 /* Adjust dates from 1-12 to 0-11 */
122                                 tm_time.tm_mon -= 1;
123                         } else if (sscanf(date_str, "%d.%d.%d-%d:%d:%d", &tm_time.tm_year,
124                                                                 &tm_time.tm_mon, &tm_time.tm_mday,
125                                                                 &tm_time.tm_hour, &tm_time.tm_min,
126                                                                         &tm_time.tm_sec) == 6) {
127                                 tm_time.tm_year -= 1900;        /* Adjust years */
128                                 tm_time.tm_mon -= 1;    /* Adjust dates from 1-12 to 0-11 */
129                         } else if (sscanf(date_str, "%d.%d.%d-%d:%d", &tm_time.tm_year,
130                                                                 &tm_time.tm_mon, &tm_time.tm_mday,
131                                                                 &tm_time.tm_hour, &tm_time.tm_min) == 5) {
132                                 tm_time.tm_year -= 1900;        /* Adjust years */
133                                 tm_time.tm_mon -= 1;    /* Adjust dates from 1-12 to 0-11 */
134                         } else {
135                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
136                         }
137                 } else {
138                         int nr;
139                         char *cp;
140
141                         nr = sscanf(date_str, "%2d%2d%2d%2d%d", &tm_time.tm_mon,
142                                                 &tm_time.tm_mday, &tm_time.tm_hour, &tm_time.tm_min,
143                                                 &tm_time.tm_year);
144
145                         if (nr < 4 || nr > 5) {
146                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
147                         }
148
149                         cp = strchr(date_str, '.');
150                         if (cp) {
151                                 nr = sscanf(cp + 1, "%2d", &tm_time.tm_sec);
152                                 if (nr != 1) {
153                                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
154                                 }
155                         }
156
157                         /* correct for century  - minor Y2K problem here? */
158                         if (tm_time.tm_year >= 1900) {
159                                 tm_time.tm_year -= 1900;
160                         }
161                         /* adjust date */
162                         tm_time.tm_mon -= 1;
163                 }
164
165                 /* Correct any day of week and day of year etc. fields */
166                 tm_time.tm_isdst = -1;  /* Be sure to recheck dst. */
167                 tm = mktime(&tm_time);
168                 if (tm < 0) {
169                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
170                 }
171                 maybe_set_utc(opt);
172
173                 /* if setting time, set it */
174                 if ((opt & DATE_OPT_SET) && stime(&tm) < 0) {
175                         bb_perror_msg("cannot set date");
176                 }
177         }
178
179         /* Display output */
180
181         /* Deal with format string */
182
183         if (date_fmt == NULL) {
184                 int i;
185                 date_fmt = xzalloc(32);
186                 if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
187                         strcpy(date_fmt, "%Y-%m-%d");
188                         if (ifmt > 0) {
189                                 i = 8;
190                                 date_fmt[i++] = 'T';
191                                 date_fmt[i++] = '%';
192                                 date_fmt[i++] = 'H';
193                                 if (ifmt > 1) {
194                                         date_fmt[i++] = ':';
195                                         date_fmt[i++] = '%';
196                                         date_fmt[i++] = 'M';
197                                 }
198                                 if (ifmt > 2) {
199                                         date_fmt[i++] = ':';
200                                         date_fmt[i++] = '%';
201                                         date_fmt[i++] = 'S';
202                                 }
203 format_utc:
204                                 date_fmt[i++] = '%';
205                                 date_fmt[i] = (opt & DATE_OPT_UTC) ? 'Z' : 'z';
206                         }
207                 } else if (opt & DATE_OPT_RFC2822) {
208                         strcpy(date_fmt, "%a, %d %b %Y %H:%M:%S ");
209                         i = 22;
210                         goto format_utc;
211                 } else /* default case */
212                         date_fmt = "%a %b %e %H:%M:%S %Z %Y";
213         }
214
215         if (*date_fmt == '\0') {
216                 /* With no format string, just print a blank line */
217                 *bb_common_bufsiz1 = 0;
218         } else {
219                 /* Handle special conversions */
220
221                 if (strncmp(date_fmt, "%f", 2) == 0) {
222                         date_fmt = "%Y.%m.%d-%H:%M:%S";
223                 }
224
225                 /* Generate output string */
226                 strftime(bb_common_bufsiz1, 200, date_fmt, &tm_time);
227         }
228         puts(bb_common_bufsiz1);
229
230         return EXIT_SUCCESS;
231 }