xref: /netbsd-src/bin/date/date.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /* $NetBSD: date.c,v 1.61 2014/09/01 21:42:21 dholland Exp $ */
2 
3 /*
4  * Copyright (c) 1985, 1987, 1988, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #ifndef lint
34 __COPYRIGHT(
35 "@(#) Copyright (c) 1985, 1987, 1988, 1993\
36  The Regents of the University of California.  All rights reserved.");
37 #endif /* not lint */
38 
39 #ifndef lint
40 #if 0
41 static char sccsid[] = "@(#)date.c	8.2 (Berkeley) 4/28/95";
42 #else
43 __RCSID("$NetBSD: date.c,v 1.61 2014/09/01 21:42:21 dholland Exp $");
44 #endif
45 #endif /* not lint */
46 
47 #include <sys/param.h>
48 #include <sys/time.h>
49 
50 #include <ctype.h>
51 #include <err.h>
52 #include <fcntl.h>
53 #include <errno.h>
54 #include <locale.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <syslog.h>
59 #include <time.h>
60 #include <tzfile.h>
61 #include <unistd.h>
62 #include <util.h>
63 
64 #include "extern.h"
65 
66 static time_t tval;
67 static int aflag, jflag, rflag, nflag;
68 
69 __dead static void badcanotime(const char *, const char *, size_t);
70 static void setthetime(const char *);
71 __dead static void usage(void);
72 
73 int
74 main(int argc, char *argv[])
75 {
76 	char *buf;
77 	size_t bufsiz;
78 	const char *format;
79 	int ch;
80 	long long val;
81 	struct tm *tm;
82 
83 	setprogname(argv[0]);
84 	(void)setlocale(LC_ALL, "");
85 
86 	while ((ch = getopt(argc, argv, "ad:jnr:u")) != -1) {
87 		switch (ch) {
88 		case 'a':		/* adjust time slowly */
89 			aflag = 1;
90 			nflag = 1;
91 			break;
92 		case 'd':
93 			rflag = 1;
94 			tval = parsedate(optarg, NULL, NULL);
95 			if (tval == -1) {
96 				errx(EXIT_FAILURE,
97 				    "%s: Unrecognized date format", optarg);
98 			}
99 			break;
100 		case 'j':		/* don't set time */
101 			jflag = 1;
102 			break;
103 		case 'n':		/* don't set network */
104 			nflag = 1;
105 			break;
106 		case 'r':		/* user specified seconds */
107 			if (optarg[0] == '\0') {
108 				errx(EXIT_FAILURE, "<empty>: Invalid number");
109 			}
110 			errno = 0;
111 			val = strtoll(optarg, &buf, 0);
112 			if (errno) {
113 				err(EXIT_FAILURE, "%s", optarg);
114 			}
115 			if (optarg[0] == '\0' || *buf != '\0') {
116 				errx(EXIT_FAILURE,
117 				    "%s: Invalid number", optarg);
118 			}
119 			rflag = 1;
120 			tval = (time_t)val;
121 			break;
122 		case 'u':		/* do everything in UTC */
123 			(void)setenv("TZ", "UTC0", 1);
124 			break;
125 		default:
126 			usage();
127 		}
128 	}
129 	argc -= optind;
130 	argv += optind;
131 
132 	if (!rflag && time(&tval) == -1)
133 		err(EXIT_FAILURE, "time");
134 
135 
136 	/* allow the operands in any order */
137 	if (*argv && **argv == '+') {
138 		format = *argv;
139 		++argv;
140 	} else
141 		format = "+%a %b %e %H:%M:%S %Z %Y";
142 
143 	if (*argv) {
144 		setthetime(*argv);
145 		++argv;
146 	}
147 
148 	if (*argv && **argv == '+')
149 		format = *argv;
150 
151 	if ((buf = malloc(bufsiz = 1024)) == NULL)
152 		goto bad;
153 
154 	if ((tm = localtime(&tval)) == NULL)
155 		err(EXIT_FAILURE, "%lld: localtime", (long long)tval);
156 
157 	while (strftime(buf, bufsiz, format, tm) == 0)
158 		if ((buf = realloc(buf, bufsiz <<= 1)) == NULL)
159 			goto bad;
160 
161 	(void)printf("%s\n", buf + 1);
162 	free(buf);
163 	return 0;
164 bad:
165 	err(EXIT_FAILURE, "Cannot allocate format buffer");
166 }
167 
168 static void
169 badcanotime(const char *msg, const char *val, size_t where)
170 {
171 	warnx("%s in canonical time", msg);
172 	warnx("%s", val);
173 	warnx("%*s", (int)where + 1, "^");
174 	usage();
175 }
176 
177 #define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
178 
179 static void
180 setthetime(const char *p)
181 {
182 	struct timeval tv;
183 	time_t new_time;
184 	struct tm *lt;
185 	const char *dot, *t, *op;
186 	size_t len;
187 	int yearset;
188 
189 	for (t = p, dot = NULL; *t; ++t) {
190 		if (*t == '.') {
191 			if (dot == NULL) {
192 				dot = t;
193 			} else {
194 				badcanotime("Unexpected dot", p, t - p);
195 			}
196 		} else if (!isdigit((unsigned char)*t)) {
197 			badcanotime("Expected digit", p, t - p);
198 		}
199 	}
200 
201 	if ((lt = localtime(&tval)) == NULL)
202 		err(EXIT_FAILURE, "%lld: localtime", (long long)tval);
203 
204 	lt->tm_isdst = -1;			/* Divine correct DST */
205 
206 	if (dot != NULL) {			/* .ss */
207 		len = strlen(dot);
208 		if (len > 3) {
209 			badcanotime("Unexpected digit after seconds field",
210 				    p, strlen(p) - 1);
211 		} else if (len < 3) {
212 			badcanotime("Expected digit in seconds field",
213 				    p, strlen(p));
214 		}
215 		++dot;
216 		lt->tm_sec = ATOI2(dot);
217 		if (lt->tm_sec > 61)
218 			badcanotime("Seconds out of range", p, strlen(p) - 1);
219 	} else {
220 		len = 0;
221 		lt->tm_sec = 0;
222 	}
223 
224 	op = p;
225 	yearset = 0;
226 	switch (strlen(p) - len) {
227 	case 12:				/* cc */
228 		lt->tm_year = ATOI2(p) * 100 - TM_YEAR_BASE;
229 		if (lt->tm_year < 0)
230 			badcanotime("Year before 1900", op, p - op + 1);
231 		yearset = 1;
232 		/* FALLTHROUGH */
233 	case 10:				/* yy */
234 		if (yearset) {
235 			lt->tm_year += ATOI2(p);
236 		} else {
237 			yearset = ATOI2(p);
238 			if (yearset < 69)
239 				lt->tm_year = yearset + 2000 - TM_YEAR_BASE;
240 			else
241 				lt->tm_year = yearset + 1900 - TM_YEAR_BASE;
242 		}
243 		/* FALLTHROUGH */
244 	case 8:					/* mm */
245 		lt->tm_mon = ATOI2(p);
246 		if (lt->tm_mon > 12 || lt->tm_mon == 0)
247 			badcanotime("Month out of range", op, p - op - 1);
248 		--lt->tm_mon;			/* time struct is 0 - 11 */
249 		/* FALLTHROUGH */
250 	case 6:					/* dd */
251 		lt->tm_mday = ATOI2(p);
252 		switch (lt->tm_mon) {
253 		case 0:
254 		case 2:
255 		case 4:
256 		case 6:
257 		case 7:
258 		case 9:
259 		case 11:
260 			if (lt->tm_mday > 31 || lt->tm_mday == 0)
261 				badcanotime("Day out of range (max 31)",
262 					    op, p - op - 1);
263 			break;
264 		case 3:
265 		case 5:
266 		case 8:
267 		case 10:
268 			if (lt->tm_mday > 30 || lt->tm_mday == 0)
269 				badcanotime("Day out of range (max 30)",
270 					    op, p - op - 1);
271 			break;
272 		case 1:
273 			if (isleap(lt->tm_year + TM_YEAR_BASE)) {
274 				if (lt->tm_mday > 29 || lt->tm_mday == 0) {
275 					badcanotime("Day out of range "
276 						    "(max 29)",
277 						    op, p - op - 1);
278 				}
279 			} else {
280 				if (lt->tm_mday > 28 || lt->tm_mday == 0) {
281 					badcanotime("Day out of range "
282 						    "(max 28)",
283 						    op, p - op - 1);
284 				}
285 			}
286 			break;
287 		default:
288 			/*
289 			 * If the month was given, it's already been
290 			 * checked.  If a bad value came back from
291 			 * localtime, something's badly broken.
292 			 * (make this an assertion?)
293 			 */
294 			errx(EXIT_FAILURE, "localtime gave invalid month %d",
295 			    lt->tm_mon);
296 		}
297 		/* FALLTHROUGH */
298 	case 4:					/* hh */
299 		lt->tm_hour = ATOI2(p);
300 		if (lt->tm_hour > 23)
301 			badcanotime("Hour out of range", op, p - op - 1);
302 		/* FALLTHROUGH */
303 	case 2:					/* mm */
304 		lt->tm_min = ATOI2(p);
305 		if (lt->tm_min > 59)
306 			badcanotime("Minute out of range", op, p - op - 1);
307 		break;
308 	case 0:					/* was just .sss */
309 		if (len != 0)
310 			break;
311 		/* FALLTHROUGH */
312 	default:
313 	    if (strlen(p) - len > 12) {
314 		    badcanotime("Too many digits", p, 12);
315 	    } else {
316 		    badcanotime("Not enough digits", p, strlen(p) - len);
317 	    }
318 	}
319 
320 	/* convert broken-down time to UTC clock time */
321 	if ((new_time = mktime(lt)) == -1) {
322 		/* Can this actually happen? */
323 		err(EXIT_FAILURE, "%s: mktime", op);
324 	}
325 
326 	/* if jflag is set, don't actually change the time, just return */
327 	if (jflag) {
328 		tval = new_time;
329 		return;
330 	}
331 
332 	/* set the time */
333 	if (nflag || netsettime(new_time)) {
334 		logwtmp("|", "date", "");
335 		if (aflag) {
336 			tv.tv_sec = new_time - tval;
337 			tv.tv_usec = 0;
338 			if (adjtime(&tv, NULL))
339 				err(EXIT_FAILURE, "adjtime");
340 		} else {
341 			tval = new_time;
342 			tv.tv_sec = tval;
343 			tv.tv_usec = 0;
344 			if (settimeofday(&tv, NULL))
345 				err(EXIT_FAILURE, "settimeofday");
346 		}
347 		logwtmp("{", "date", "");
348 	}
349 
350 	if ((p = getlogin()) == NULL)
351 		p = "???";
352 	syslog(LOG_AUTH | LOG_NOTICE, "date set by %s", p);
353 }
354 
355 static void
356 usage(void)
357 {
358 	(void)fprintf(stderr,
359 	    "Usage: %s [-ajnu] [-d date] [-r seconds] [+format]",
360 	    getprogname());
361 	(void)fprintf(stderr, " [[[[[[CC]yy]mm]dd]HH]MM[.SS]]\n");
362 	exit(EXIT_FAILURE);
363 	/* NOTREACHED */
364 }
365