*: mass renaming of USE_XXXX to IF_XXXX
[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 #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 maybe_set_utc(int opt)
36 {
37         if (opt & DATE_OPT_UTC)
38                 putenv((char*)"TZ=UTC0");
39 }
40
41 int date_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
42 int date_main(int argc UNUSED_PARAM, char **argv)
43 {
44         struct tm tm_time;
45         time_t tm;
46         unsigned opt;
47         int ifmt = -1;
48         char *date_str;
49         char *fmt_dt2str;
50         char *fmt_str2dt;
51         char *filename;
52         char *isofmt_arg = NULL;
53
54         opt_complementary = "d--s:s--d"
55                 IF_FEATURE_DATE_ISOFMT(":R--I:I--R");
56         opt = getopt32(argv, "Rs:ud:r:"
57                         IF_FEATURE_DATE_ISOFMT("I::D:"),
58                         &date_str, &date_str, &filename
59                         IF_FEATURE_DATE_ISOFMT(, &isofmt_arg, &fmt_str2dt));
60         argv += optind;
61         maybe_set_utc(opt);
62
63         if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_TIMESPEC)) {
64                 ifmt = 0; /* default is date */
65                 if (isofmt_arg) {
66                         static const char isoformats[] ALIGN1 =
67                                 "date\0""hours\0""minutes\0""seconds\0";
68                         ifmt = index_in_strings(isoformats, isofmt_arg);
69                         if (ifmt < 0)
70                                 bb_show_usage();
71                 }
72         }
73
74         fmt_dt2str = NULL;
75         if (argv[0] && argv[0][0] == '+') {
76                 fmt_dt2str = &argv[0][1];       /* Skip over the '+' */
77                 argv++;
78         }
79         if (!(opt & (DATE_OPT_SET | DATE_OPT_DATE))) {
80                 opt |= DATE_OPT_SET;
81                 date_str = argv[0]; /* can be NULL */
82                 if (date_str)
83                         argv++;
84         }
85         if (*argv)
86                 bb_show_usage();
87
88         /* Now we have parsed all the information except the date format
89            which depends on whether the clock is being set or read */
90
91         if (opt & DATE_OPT_REFERENCE) {
92                 struct stat statbuf;
93                 xstat(filename, &statbuf);
94                 tm = statbuf.st_mtime;
95         } else
96                 time(&tm);
97         memcpy(&tm_time, localtime(&tm), sizeof(tm_time));
98
99         /* If date string is given, update tm_time, and maybe set date */
100         if (date_str != NULL) {
101                 /* Zero out fields - take her back to midnight! */
102                 tm_time.tm_sec = 0;
103                 tm_time.tm_min = 0;
104                 tm_time.tm_hour = 0;
105
106                 /* Process any date input to UNIX time since 1 Jan 1970 */
107                 if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_HINT)) {
108                         if (strptime(date_str, fmt_str2dt, &tm_time) == NULL)
109                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
110                 } else {
111                         char end = '\0';
112                         const char *last_colon = strrchr(date_str, ':');
113
114                         if (last_colon != NULL) {
115                                 /* Parse input and assign appropriately to tm_time */
116
117                                 if (sscanf(date_str, "%u:%u%c",
118                                                                 &tm_time.tm_hour,
119                                                                 &tm_time.tm_min,
120                                                                 &end) >= 2) {
121                                         /* no adjustments needed */
122                                 } else if (sscanf(date_str, "%u.%u-%u:%u%c",
123                                                                 &tm_time.tm_mon, &tm_time.tm_mday,
124                                                                 &tm_time.tm_hour, &tm_time.tm_min,
125                                                                 &end) >= 4) {
126                                         /* Adjust dates from 1-12 to 0-11 */
127                                         tm_time.tm_mon -= 1;
128                                 } else if (sscanf(date_str, "%u.%u.%u-%u:%u%c", &tm_time.tm_year,
129                                                                 &tm_time.tm_mon, &tm_time.tm_mday,
130                                                                 &tm_time.tm_hour, &tm_time.tm_min,
131                                                                 &end) >= 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 if (sscanf(date_str, "%u-%u-%u %u:%u%c", &tm_time.tm_year,
135                                                                 &tm_time.tm_mon, &tm_time.tm_mday,
136                                                                 &tm_time.tm_hour, &tm_time.tm_min,
137                                                                 &end) >= 5) {
138                                         tm_time.tm_year -= 1900; /* Adjust years */
139                                         tm_time.tm_mon -= 1; /* Adjust dates from 1-12 to 0-11 */
140 //TODO: coreutils 6.9 also accepts "YYYY-MM-DD HH" (no minutes)
141                                 } else {
142                                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
143                                 }
144                                 if (end == ':') {
145                                         if (sscanf(last_colon + 1, "%u%c", &tm_time.tm_sec, &end) == 1)
146                                                 end = '\0';
147                                         /* else end != NUL and we error out */
148                                 }
149                         } else {
150                                 if (sscanf(date_str, "%2u%2u%2u%2u%u%c", &tm_time.tm_mon,
151                                                         &tm_time.tm_mday, &tm_time.tm_hour, &tm_time.tm_min,
152                                                         &tm_time.tm_year, &end) < 4)
153                                         bb_error_msg_and_die(bb_msg_invalid_date, date_str);
154                                 /* correct for century  - minor Y2K problem here? */
155                                 if (tm_time.tm_year >= 1900) {
156                                         tm_time.tm_year -= 1900;
157                                 }
158                                 /* adjust date */
159                                 tm_time.tm_mon -= 1;
160                                 if (end == '.') {
161                                         if (sscanf(strchr(date_str, '.') + 1, "%u%c",
162                                                         &tm_time.tm_sec, &end) == 1)
163                                                 end = '\0';
164                                         /* else end != NUL and we error out */
165                                 }
166                         }
167                         if (end != '\0') {
168                                 bb_error_msg_and_die(bb_msg_invalid_date, date_str);
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         if (fmt_dt2str == NULL) {
189                 int i;
190                 fmt_dt2str = xzalloc(32);
191                 if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) {
192                         strcpy(fmt_dt2str, "%Y-%m-%d");
193                         if (ifmt > 0) {
194                                 i = 8;
195                                 fmt_dt2str[i++] = 'T';
196                                 fmt_dt2str[i++] = '%';
197                                 fmt_dt2str[i++] = 'H';
198                                 if (ifmt > 1) {
199                                         fmt_dt2str[i++] = ':';
200                                         fmt_dt2str[i++] = '%';
201                                         fmt_dt2str[i++] = 'M';
202                                         if (ifmt > 2) {
203                                                 fmt_dt2str[i++] = ':';
204                                                 fmt_dt2str[i++] = '%';
205                                                 fmt_dt2str[i++] = 'S';
206                                         }
207                                 }
208  format_utc:
209                                 fmt_dt2str[i++] = '%';
210                                 fmt_dt2str[i] = (opt & DATE_OPT_UTC) ? 'Z' : 'z';
211                         }
212                 } else if (opt & DATE_OPT_RFC2822) {
213                         /* Undo busybox.c for date -R */
214                         if (ENABLE_LOCALE_SUPPORT)
215                                 setlocale(LC_TIME, "C");
216                         strcpy(fmt_dt2str, "%a, %d %b %Y %H:%M:%S ");
217                         i = 22;
218                         goto format_utc;
219                 } else /* default case */
220                         fmt_dt2str = (char*)"%a %b %e %H:%M:%S %Z %Y";
221         }
222
223 #define date_buf bb_common_bufsiz1
224         if (*fmt_dt2str == '\0') {
225                 /* With no format string, just print a blank line */
226                 date_buf[0] = '\0';
227         } else {
228                 /* Handle special conversions */
229                 if (strncmp(fmt_dt2str, "%f", 2) == 0) {
230                         fmt_dt2str = (char*)"%Y.%m.%d-%H:%M:%S";
231                 }
232
233                 /* Generate output string */
234                 strftime(date_buf, sizeof(date_buf), fmt_dt2str, &tm_time);
235         }
236         puts(date_buf);
237
238         return EXIT_SUCCESS;
239 }