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