preparatory patch for -Wwrite-strings #1
[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 xputenv(char *s)
36 {
37         if (putenv(s) != 0)
38                 bb_error_msg_and_die(bb_msg_memory_exhausted);
39 }
40
41 static void maybe_set_utc(int opt)
42 {
43         if (opt & DATE_OPT_UTC)
44                 xputenv((char*)"TZ=UTC0");
45 }
46
47 int date_main(int argc, char **argv)
48 {
49         time_t tm;
50         struct tm tm_time;
51         unsigned opt;
52         int ifmt = -1;
53         char *date_str = NULL;
54         char *date_fmt = NULL;
55         char *filename = NULL;
56         char *isofmt_arg;
57         char *hintfmt_arg;
58
59         opt_complementary = "?:d--s:s--d"
60                 USE_FEATURE_DATE_ISOFMT(":R--I:I--R");
61         opt = getopt32(argc, argv, "Rs:ud:r:"
62                         USE_FEATURE_DATE_ISOFMT("I::D:"),
63                         &date_str, &date_str, &filename
64                         USE_FEATURE_DATE_ISOFMT(, &isofmt_arg, &hintfmt_arg));
65         maybe_set_utc(opt);
66
67         if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_TIMESPEC)) {
68                 if (!isofmt_arg) {
69                         ifmt = 0; /* default is date */
70                 } else {
71                         const char * const isoformats[] =
72                                 {"date", "hours", "minutes", "seconds"};
73
74                         for (ifmt = 0; ifmt < 4; ifmt++)
75                                 if (!strcmp(isofmt_arg, isoformats[ifmt])) {
76                                         break;
77                                 }
78                         if (ifmt == 4) /* parse error */
79                                 bb_show_usage();
80                 }
81         }
82
83         /* XXX, date_fmt == NULL from this always */
84         if ((date_fmt == NULL) && (optind < argc) && (argv[optind][0] == '+')) {
85                 date_fmt = &argv[optind][1];    /* Skip over the '+' */
86         } else if (date_str == NULL) {
87                 opt |= DATE_OPT_SET;
88                 date_str = argv[optind];
89         }
90
91         /* Now we have parsed all the information except the date format
92            which depends on whether the clock is being set or read */
93
94         if (filename) {
95                 struct stat statbuf;
96                 xstat(filename, &statbuf);
97                 tm = statbuf.st_mtime;
98         } else
99                 time(&tm);
100         memcpy(&tm_time, localtime(&tm), sizeof(tm_time));
101         /* Zero out fields - take her back to midnight! */
102         if (date_str != NULL) {
103                 tm_time.tm_sec = 0;
104                 tm_time.tm_min = 0;
105                 tm_time.tm_hour = 0;
106
107                 /* Process any date input to UNIX time since 1 Jan 1970 */
108                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_HINT)) {
109                         strptime(date_str, hintfmt_arg, &tm_time);
110                 } else if (strchr(date_str, ':') != NULL) {
111                         /* Parse input and assign appropriately to tm_time */
112
113                         if (sscanf(date_str, "%d:%d:%d", &tm_time.tm_hour, &tm_time.tm_min,
114                                                                  &tm_time.tm_sec) == 3) {
115                                 /* no adjustments needed */
116                         } else if (sscanf(date_str, "%d:%d", &tm_time.tm_hour,
117                                                                                 &tm_time.tm_min) == 2) {
118                                 /* no adjustments needed */
119                         } else if (sscanf(date_str, "%d.%d-%d:%d:%d", &tm_time.tm_mon,
120                                                                 &tm_time.tm_mday, &tm_time.tm_hour,
121                                                                 &tm_time.tm_min, &tm_time.tm_sec) == 5) {
122                                 /* Adjust dates from 1-12 to 0-11 */
123                                 tm_time.tm_mon -= 1;
124                         } else if (sscanf(date_str, "%d.%d-%d:%d", &tm_time.tm_mon,
125                                                                 &tm_time.tm_mday,
126                                                                 &tm_time.tm_hour, &tm_time.tm_min) == 4) {
127                                 /* Adjust dates from 1-12 to 0-11 */
128                                 tm_time.tm_mon -= 1;
129                         } else if (sscanf(date_str, "%d.%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,
132                                                                         &tm_time.tm_sec) == 6) {
133                                 tm_time.tm_year -= 1900;        /* Adjust years */
134                                 tm_time.tm_mon -= 1;    /* Adjust dates from 1-12 to 0-11 */
135                         } else if (sscanf(date_str, "%d.%d.%d-%d:%d", &tm_time.tm_year,
136                                                                 &tm_time.tm_mon, &tm_time.tm_mday,
137                                                                 &tm_time.tm_hour, &tm_time.tm_min) == 5) {
138                                 tm_time.tm_year -= 1900;        /* Adjust years */
139                                 tm_time.tm_mon -= 1;    /* Adjust dates from 1-12 to 0-11 */
140                         } else {
141                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
142                         }
143                 } else {
144                         int nr;
145                         char *cp;
146
147                         nr = sscanf(date_str, "%2d%2d%2d%2d%d", &tm_time.tm_mon,
148                                                 &tm_time.tm_mday, &tm_time.tm_hour, &tm_time.tm_min,
149                                                 &tm_time.tm_year);
150
151                         if (nr < 4 || nr > 5) {
152                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
153                         }
154
155                         cp = strchr(date_str, '.');
156                         if (cp) {
157                                 nr = sscanf(cp + 1, "%2d", &tm_time.tm_sec);
158                                 if (nr != 1) {
159                                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
160                                 }
161                         }
162
163                         /* correct for century  - minor Y2K problem here? */
164                         if (tm_time.tm_year >= 1900) {
165                                 tm_time.tm_year -= 1900;
166                         }
167                         /* adjust date */
168                         tm_time.tm_mon -= 1;
169                 }
170
171                 /* Correct any day of week and day of year etc. fields */
172                 tm_time.tm_isdst = -1;  /* Be sure to recheck dst. */
173                 tm = mktime(&tm_time);
174                 if (tm < 0) {
175                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
176                 }
177                 maybe_set_utc(opt);
178
179                 /* if setting time, set it */
180                 if ((opt & DATE_OPT_SET) && stime(&tm) < 0) {
181                         bb_perror_msg("cannot set date");
182                 }
183         }
184
185         /* Display output */
186
187         /* Deal with format string */
188
189         if (date_fmt == NULL) {
190                 int i;
191                 date_fmt = xzalloc(32);
192                 if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
193                         strcpy(date_fmt, "%Y-%m-%d");
194                         if (ifmt > 0) {
195                                 i = 8;
196                                 date_fmt[i++] = 'T';
197                                 date_fmt[i++] = '%';
198                                 date_fmt[i++] = 'H';
199                                 if (ifmt > 1) {
200                                         date_fmt[i++] = ':';
201                                         date_fmt[i++] = '%';
202                                         date_fmt[i++] = 'M';
203                                 }
204                                 if (ifmt > 2) {
205                                         date_fmt[i++] = ':';
206                                         date_fmt[i++] = '%';
207                                         date_fmt[i++] = 'S';
208                                 }
209 format_utc:
210                                 date_fmt[i++] = '%';
211                                 date_fmt[i] = (opt & DATE_OPT_UTC) ? 'Z' : 'z';
212                         }
213                 } else if (opt & DATE_OPT_RFC2822) {
214                         /* Undo busybox.c for date -R */
215                         if (ENABLE_LOCALE_SUPPORT)
216                                 setlocale(LC_TIME, "C");
217                         strcpy(date_fmt, "%a, %d %b %Y %H:%M:%S ");
218                         i = 22;
219                         goto format_utc;
220                 } else /* default case */
221                         date_fmt = (char*)"%a %b %e %H:%M:%S %Z %Y";
222         }
223
224         if (*date_fmt == '\0') {
225                 /* With no format string, just print a blank line */
226                 *bb_common_bufsiz1 = 0;
227         } else {
228                 /* Handle special conversions */
229
230                 if (strncmp(date_fmt, "%f", 2) == 0) {
231                         date_fmt = (char*)"%Y.%m.%d-%H:%M:%S";
232                 }
233
234                 /* Generate output string */
235                 strftime(bb_common_bufsiz1, 200, date_fmt, &tm_time);
236         }
237         puts(bb_common_bufsiz1);
238
239         return EXIT_SUCCESS;
240 }