xref: /netbsd-src/bin/date/date.c (revision f8cf1a9151c7af1cb0bd8b09c13c66bca599c027)
1 /* $NetBSD: date.c,v 1.70 2024/09/17 15:25:39 kre 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 #if HAVE_NBTOOL_CONFIG_H
33 #include "nbtool_config.h"
34 #endif
35 
36 #include <sys/cdefs.h>
37 #ifndef lint
38 __COPYRIGHT(
39 "@(#) Copyright (c) 1985, 1987, 1988, 1993\
40  The Regents of the University of California.  All rights reserved.");
41 #endif /* not lint */
42 
43 #ifndef lint
44 #if 0
45 static char sccsid[] = "@(#)date.c	8.2 (Berkeley) 4/28/95";
46 #else
47 __RCSID("$NetBSD: date.c,v 1.70 2024/09/17 15:25:39 kre Exp $");
48 #endif
49 #endif /* not lint */
50 
51 #include <sys/param.h>
52 #include <sys/time.h>
53 
54 #include <ctype.h>
55 #include <err.h>
56 #include <fcntl.h>
57 #include <errno.h>
58 #include <locale.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <syslog.h>
63 #include <time.h>
64 #include <tzfile.h>
65 #include <unistd.h>
66 #include <util.h>
67 #if !HAVE_NBTOOL_CONFIG_H
68 #include <utmpx.h>
69 #endif
70 
71 #include "extern.h"
72 
73 static time_t tval;
74 static int Rflag, aflag, jflag, rflag, nflag;
75 
76 __dead static void badcanotime(const char *, const char *, size_t);
77 static void setthetime(const char *);
78 __dead static void usage(void);
79 
80 #if HAVE_NBTOOL_CONFIG_H
81 static int parse_iso_datetime(time_t *, const char *);
82 #else
83 static char *fmt;
84 #endif
85 
86 #if !defined(isleap)
87 # define isleap(y)   (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
88 #endif
89 
90 int
91 main(int argc, char *argv[])
92 {
93 	char *buf;
94 	size_t bufsiz;
95 	const char *format;
96 	int ch;
97 	long long val;
98 	struct tm *tm;
99 	char *default_tz;
100 
101 	setprogname(argv[0]);
102 	(void)setlocale(LC_ALL, "");
103 
104 	default_tz = getenv("TZ");
105 
106 	while ((ch = getopt(argc, argv, "ad:f:jnRr:Uuz:")) != -1) {
107 		switch (ch) {
108 		case 'a':		/* adjust time slowly */
109 			aflag = 1;
110 			nflag = 1;
111 			break;
112 		case 'd':
113 			rflag = 1;
114 #ifdef HAVE_NBTOOL_CONFIG_H
115 			if (parse_iso_datetime(&tval, optarg))
116 				break;
117 			errx(EXIT_FAILURE,
118 			    "-d only supports ISO format in the tool version");
119 			break;
120 #else
121 			errno = 0;
122 			tval = parsedate(optarg, NULL, NULL);
123 			if (tval == -1 && errno != 0) {
124 				errx(EXIT_FAILURE,
125 				    "%s: Unrecognized date format", optarg);
126 			}
127 			break;
128 		case 'f':
129 			fmt = optarg;
130 			break;
131 #endif
132 		case 'j':		/* don't set time */
133 			jflag = 1;
134 			break;
135 		case 'n':		/* don't set network */
136 			nflag = 1;
137 			break;
138 		case 'R':		/* RFC-5322 email format */
139 			Rflag = 1;
140 			break;
141 		case 'r':		/* user specified seconds */
142 			if (optarg[0] == '\0') {
143 				errx(EXIT_FAILURE, "<empty>: Invalid number");
144 			}
145 			errno = 0;
146 			val = strtoll(optarg, &buf, 0);
147 			if (errno) {
148 				err(EXIT_FAILURE, "%s", optarg);
149 			}
150 			if (optarg[0] == '\0' || *buf != '\0') {
151 				errx(EXIT_FAILURE,
152 				    "%s: Invalid number", optarg);
153 			}
154 			rflag = 1;
155 			tval = (time_t)val;
156 			break;
157 		case 'U':		/* reset to default timezone */
158 			if (default_tz)
159 				(void)setenv("TZ", default_tz, 1);
160 			else
161 				(void)unsetenv("TZ");
162 			break;
163 		case 'u':		/* do everything in UTC */
164 			(void)setenv("TZ", "UTC0", 1);
165 			break;
166 		case 'z':
167 			if (optarg[0] == '\0')
168 				(void)unsetenv("TZ");
169 			else
170 				(void)setenv("TZ", optarg, 1);
171 			break;
172 		default:
173 			usage();
174 		}
175 	}
176 	argc -= optind;
177 	argv += optind;
178 
179 	if (!rflag && time(&tval) == -1)
180 		err(EXIT_FAILURE, "time");
181 
182 
183 	/* allow the operands in any order */
184 	if (*argv && **argv == '+') {
185 		format = *argv;
186 		++argv;
187 	} else if (Rflag) {
188 		(void)setlocale(LC_TIME, "C");
189 		format = "+%a, %-e %b %Y %H:%M:%S %z";
190 	} else
191 		format = "+%a %b %e %H:%M:%S %Z %Y";
192 
193 	if (*argv) {
194 		setthetime(*argv);
195 		++argv;
196 #ifndef HAVE_NBTOOL_CONFIG_H
197 	} else if (fmt) {
198 		usage();
199 #endif
200 	}
201 
202 	if (*argv && **argv == '+')
203 		format = *argv;
204 
205 	if ((buf = malloc(bufsiz = 1024)) == NULL)
206 		goto bad;
207 
208 	if ((tm = localtime(&tval)) == NULL)
209 		err(EXIT_FAILURE, "%lld: localtime", (long long)tval);
210 
211 	while (strftime(buf, bufsiz, format, tm) == 0)
212 		if ((buf = realloc(buf, bufsiz <<= 1)) == NULL)
213 			goto bad;
214 
215 	(void)printf("%s\n", buf + 1);
216 	free(buf);
217 	return 0;
218 bad:
219 	err(EXIT_FAILURE, "Cannot allocate format buffer");
220 }
221 
222 static void
223 badcanotime(const char *msg, const char *val, size_t where)
224 {
225 	warnx("%s in canonical time", msg);
226 	warnx("%s", val);
227 	warnx("%*s", (int)where + 1, "^");
228 	usage();
229 }
230 
231 #define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
232 
233 #if HAVE_NBTOOL_CONFIG_H
234 
235 inline static int
236 digitstring(const char *s, int len)
237 {
238 	while (--len > 0) {
239 		if (!isdigit(*(unsigned char *)s))
240 			return 0;
241 		s++;
242 	}
243 	return 1;
244 }
245 
246 static int
247 parse_iso_datetime(time_t * res, const char * string)
248 {
249 	struct tm tm;
250 	time_t t;
251 
252 	memset(&tm, 0, sizeof tm);
253 
254 	if (!digitstring(string, 4))
255 		return 0;
256 	tm.tm_year = ATOI2(string) * 100;
257 	tm.tm_year += ATOI2(string);
258 	tm.tm_year -= 1900;
259 
260 	if (*string == '-')
261 		string++;
262 
263 	if (!digitstring(string, 2))
264 		return 0;
265 
266 	tm.tm_mon = ATOI2(string);
267 	if (tm.tm_mon < 1 || tm.tm_mon > 12)
268 		return 0;
269 	tm.tm_mon--;
270 
271 	if (*string == '-')
272 		string++;
273 
274 	if (!digitstring(string, 2))
275 		return 0;
276 
277 	tm.tm_mday = ATOI2(string);
278 	if (tm.tm_mday < 1)
279 		return 0;
280 	switch (tm.tm_mon) {
281 	case 0: case 2: case 4: case 6: case 7: case 9: case 11:
282 		if (tm.tm_mday > 31)
283 			return 0;
284 		break;
285 	case 3: case 5: case 8: case 10:
286 		if (tm.tm_mday > 30)
287 			return 0;
288 		break;
289 	case 1:
290 		if (tm.tm_mday > 28 + isleap(tm.tm_year + 1900))
291 			return 0;
292 		break;
293 	default:
294 		abort();
295 	}
296 
297 	do {
298 		if (*string == '\0')
299 			break;
300 		if (*string == 'T' || *string == 't' || *string == ' ' ||
301 		    *string == '-')
302 			string++;
303 
304 		if (!digitstring(string, 2))
305 			return 0;
306 		tm.tm_hour = ATOI2(string);
307 		if (tm.tm_hour > 23)
308 			return 0;
309 
310 		if (*string == '\0')
311 			break;
312 		if (*string == ':')
313 			string++;
314 
315 		if (!digitstring(string, 2))
316 			return 0;
317 		tm.tm_min = ATOI2(string);
318 		if (tm.tm_min >= 60)
319 			return 0;
320 
321 		if (*string == '\0')
322 			break;
323 		if (*string == ':')
324 			string++;
325 
326 		if (!digitstring(string, 2))
327 			return 0;
328 		tm.tm_sec = ATOI2(string);
329 		if (tm.tm_sec >= 60)
330 			return 0;
331 	} while (0);
332 
333 	if (*string != '\0')
334 		return 0;
335 
336 	tm.tm_isdst = -1;
337 	tm.tm_wday = -1;
338 
339 	t = mktime(&tm);
340 	if (tm.tm_wday == -1)
341 		return 0;
342 
343 	*res = t;
344 	return 1;
345 }
346 
347 #endif	/*NBTOOL*/
348 
349 static void
350 setthetime(const char *p)
351 {
352 	struct timeval tv;
353 	time_t new_time;
354 	struct tm *lt;
355 	const char *dot, *t, *op;
356 	size_t len;
357 	int yearset;
358 
359 	if ((lt = localtime(&tval)) == NULL)
360 		err(EXIT_FAILURE, "%lld: localtime", (long long)tval);
361 
362 	lt->tm_isdst = -1;			/* Divine correct DST */
363 
364 #ifndef HAVE_NBTOOL_CONFIG_H
365 	if (fmt) {
366 		t = strptime(p, fmt, lt);
367 		if (t == NULL) {
368 			warnx("Failed conversion of ``%s''"
369 			    " using format ``%s''\n", p, fmt);
370 		} else if (*t != '\0')
371 			warnx("Ignoring %zu extraneous"
372 				" characters in date string (%s)",
373 				strlen(t), t);
374 		goto setit;
375 	}
376 	if (getenv("POSIXLY_CORRECT") != NULL) {
377 		int yrdigs;
378 		const char * const e = "Bad POSIX format date ``%s''";
379 
380 		t = strptime(p, "%m%d%H%M", lt);
381 		if (t == NULL)
382 			errx(EXIT_FAILURE, e, p);
383 		if (*t != '\0') {
384 			yrdigs = strspn(t, "0123456789");
385 			if (yrdigs != 2 && yrdigs != 4)
386 				errx(EXIT_FAILURE, e, p);
387 			t = strptime(t, yrdigs == 2 ? "%y" : "%Y", lt);
388 			if (t == NULL || *t != '\0')
389 				errx(EXIT_FAILURE, e, p);
390 		}
391 		goto setit;
392 	}
393 #endif
394 	for (t = p, dot = NULL; *t; ++t) {
395 		if (*t == '.') {
396 			if (dot == NULL) {
397 				dot = t;
398 			} else {
399 				badcanotime("Unexpected dot", p, t - p);
400 			}
401 		} else if (!isdigit((unsigned char)*t)) {
402 			badcanotime("Expected digit", p, t - p);
403 		}
404 	}
405 
406 
407 	if (dot != NULL) {			/* .ss */
408 		len = strlen(dot);
409 		if (len > 3) {
410 			badcanotime("Unexpected digit after seconds field",
411 				    p, strlen(p) - 1);
412 		} else if (len < 3) {
413 			badcanotime("Expected digit in seconds field",
414 				    p, strlen(p));
415 		}
416 		++dot;
417 		lt->tm_sec = ATOI2(dot);
418 		if (lt->tm_sec > 61)
419 			badcanotime("Seconds out of range", p, strlen(p) - 1);
420 	} else {
421 		len = 0;
422 		lt->tm_sec = 0;
423 	}
424 
425 	op = p;
426 	yearset = 0;
427 	switch (strlen(p) - len) {
428 	case 12:				/* cc */
429 		lt->tm_year = ATOI2(p) * 100 - TM_YEAR_BASE;
430 		if (lt->tm_year < 0)
431 			badcanotime("Year before 1900", op, p - op + 1);
432 		yearset = 1;
433 		/* FALLTHROUGH */
434 	case 10:				/* yy */
435 		if (yearset) {
436 			lt->tm_year += ATOI2(p);
437 		} else {
438 			yearset = ATOI2(p);
439 			if (yearset < 69)
440 				lt->tm_year = yearset + 2000 - TM_YEAR_BASE;
441 			else
442 				lt->tm_year = yearset + 1900 - TM_YEAR_BASE;
443 		}
444 		/* FALLTHROUGH */
445 	case 8:					/* mm */
446 		lt->tm_mon = ATOI2(p);
447 		if (lt->tm_mon > 12 || lt->tm_mon == 0)
448 			badcanotime("Month out of range", op, p - op - 1);
449 		--lt->tm_mon;			/* time struct is 0 - 11 */
450 		/* FALLTHROUGH */
451 	case 6:					/* dd */
452 		lt->tm_mday = ATOI2(p);
453 		switch (lt->tm_mon) {
454 		case 0:
455 		case 2:
456 		case 4:
457 		case 6:
458 		case 7:
459 		case 9:
460 		case 11:
461 			if (lt->tm_mday > 31 || lt->tm_mday == 0)
462 				badcanotime("Day out of range (max 31)",
463 					    op, p - op - 1);
464 			break;
465 		case 3:
466 		case 5:
467 		case 8:
468 		case 10:
469 			if (lt->tm_mday > 30 || lt->tm_mday == 0)
470 				badcanotime("Day out of range (max 30)",
471 					    op, p - op - 1);
472 			break;
473 		case 1:
474 			if (isleap(lt->tm_year + TM_YEAR_BASE)) {
475 				if (lt->tm_mday > 29 || lt->tm_mday == 0) {
476 					badcanotime("Day out of range "
477 						    "(max 29)",
478 						    op, p - op - 1);
479 				}
480 			} else {
481 				if (lt->tm_mday > 28 || lt->tm_mday == 0) {
482 					badcanotime("Day out of range "
483 						    "(max 28)",
484 						    op, p - op - 1);
485 				}
486 			}
487 			break;
488 		default:
489 			/*
490 			 * If the month was given, it's already been
491 			 * checked.  If a bad value came back from
492 			 * localtime, something's badly broken.
493 			 * (make this an assertion?)
494 			 */
495 			errx(EXIT_FAILURE, "localtime gave invalid month %d",
496 			    lt->tm_mon);
497 		}
498 		/* FALLTHROUGH */
499 	case 4:					/* hh */
500 		lt->tm_hour = ATOI2(p);
501 		if (lt->tm_hour > 23)
502 			badcanotime("Hour out of range", op, p - op - 1);
503 		/* FALLTHROUGH */
504 	case 2:					/* mm */
505 		lt->tm_min = ATOI2(p);
506 		if (lt->tm_min > 59)
507 			badcanotime("Minute out of range", op, p - op - 1);
508 		break;
509 	case 0:					/* was just .sss */
510 		if (len != 0)
511 			break;
512 		/* FALLTHROUGH */
513 	default:
514 	    if (strlen(p) - len > 12) {
515 		    badcanotime("Too many digits", p, 12);
516 	    } else {
517 		    badcanotime("Not enough digits", p, strlen(p) - len);
518 	    }
519 	}
520 setit:
521 	/* convert broken-down time to UTC clock time */
522 	if ((new_time = mktime(lt)) == -1) {
523 		/* Can this actually happen? */
524 		err(EXIT_FAILURE, "mktime");
525 	}
526 
527 	/* if jflag is set, don't actually change the time, just return */
528 	if (jflag) {
529 		tval = new_time;
530 		return;
531 	}
532 
533 	/* set the time */
534 #ifndef HAVE_NBTOOL_CONFIG_H
535 	struct utmpx utx;
536 	memset(&utx, 0, sizeof(utx));
537 	utx.ut_type = OLD_TIME;
538 	(void)gettimeofday(&utx.ut_tv, NULL);
539 	pututxline(&utx);
540 
541 	if (nflag || netsettime(new_time)) {
542 		logwtmp("|", "date", "");
543 		if (aflag) {
544 			tv.tv_sec = new_time - tval;
545 			tv.tv_usec = 0;
546 			if (adjtime(&tv, NULL))
547 				err(EXIT_FAILURE, "adjtime");
548 		} else {
549 			tval = new_time;
550 			tv.tv_sec = tval;
551 			tv.tv_usec = 0;
552 			if (settimeofday(&tv, NULL))
553 				err(EXIT_FAILURE, "settimeofday");
554 		}
555 		logwtmp("{", "date", "");
556 	}
557 	utx.ut_type = NEW_TIME;
558 	(void)gettimeofday(&utx.ut_tv, NULL);
559 	pututxline(&utx);
560 
561 	if ((p = getlogin()) == NULL)
562 		p = "???";
563 	syslog(LOG_AUTH | LOG_NOTICE, "date set by %s", p);
564 #else
565 	errx(EXIT_FAILURE, "Can't set the time in the tools version");
566 #endif
567 }
568 
569 static void
570 usage(void)
571 {
572 	(void)fprintf(stderr,
573 	    "Usage: %s [-ajnRUu] [-d date] [-r seconds] [-z zone] [+format]",
574 	    getprogname());
575 	(void)fprintf(stderr, "\n\t%*s[[[[[[CC]yy]mm]dd]HH]MM[.SS]]\n",
576 	    (int)strlen(getprogname()), "");
577 	(void)fprintf(stderr,
578 	    "       %s [-ajnRu] -f input_format new_date [+format]\n",
579 	    getprogname());
580 	exit(EXIT_FAILURE);
581 	/* NOTREACHED */
582 }
583