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