xref: /illumos-gate/usr/src/cmd/date/date.c (revision 4c28a617e3922d92a58e813a5b955eb526b9c386)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 /*
27  * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
28  */
29 /*
30  * Copyright (c) 2017, Joyent, Inc.
31  */
32 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
33 /*	  All Rights Reserved  	*/
34 
35 /*
36  * University Copyright- Copyright (c) 1982, 1986, 1988
37  * The Regents of the University of California
38  * All Rights Reserved
39  *
40  * University Acknowledgment- Portions of this document are derived from
41  * software developed by the University of California, Berkeley, and its
42  * contributors.
43  */
44 
45 /*
46  *	date - with format capabilities and international flair
47  */
48 
49 #include	<locale.h>
50 #include	<fcntl.h>
51 #include	<langinfo.h>
52 #include	<stdio.h>
53 #include	<stdlib.h>
54 #include	<string.h>
55 #include	<time.h>
56 #include	<unistd.h>
57 #include	<sys/time.h>
58 #include	<sys/types.h>
59 #include	<sys/stat.h>
60 #include	<ctype.h>
61 #include	<errno.h>
62 #include	<utmpx.h>
63 #include	<tzfile.h>
64 
65 #define	year_size(A)	((isleap(A)) ? 366 : 365)
66 static 	char	buf[BUFSIZ];
67 static	time_t	clock_val;
68 static  short	month_size[12] =
69 	{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
70 static  struct  utmpx wtmpx[2] = {
71 	{"", "", OTIME_MSG, 0, OLD_TIME, 0, 0, 0},
72 	{"", "", NTIME_MSG, 0, NEW_TIME, 0, 0, 0}
73 	};
74 static char *usage =
75 	"usage:\tdate [-u] mmddHHMM[[cc]yy][.SS]\n"
76 	"\tdate [-Ru] [-r seconds | filename] [+format]\n"
77 	"\tdate -a [-]sss[.fff]\n";
78 static int uflag = 0;
79 static int Rflag = 0;
80 static int rflag = 0;
81 
82 static int get_adj(char *, struct timeval *);
83 static int setdate(struct tm *, char *);
84 static void fmt_extensions(char *, size_t,
85     const char *, const struct timespec *);
86 
87 int
88 main(int argc, char **argv)
89 {
90 	struct tm *tp, tm;
91 	struct timeval tv;
92 	char *fmt, *eptr;
93 	char fmtbuf[BUFSIZ];
94 	int c, aflag = 0, illflag = 0;
95 	struct timespec ts;
96 
97 	(void) setlocale(LC_ALL, "");
98 
99 #if !defined(TEXT_DOMAIN)
100 #define	TEXT_DOMAIN "SYS_TEST"
101 #endif
102 	(void) textdomain(TEXT_DOMAIN);
103 
104 	while ((c = getopt(argc, argv, "a:uRr:")) != EOF)
105 		switch (c) {
106 		case 'a':
107 			aflag++;
108 			if (get_adj(optarg, &tv) < 0) {
109 				(void) fprintf(stderr,
110 				    gettext("date: invalid argument -- %s\n"),
111 				    optarg);
112 				illflag++;
113 			}
114 			break;
115 		case 'u':
116 			uflag++;
117 			break;
118 		case 'R':
119 			Rflag++;
120 			break;
121 		case 'r':
122 
123 			/*
124 			 * BSD originally used -r to specify a unix time. GNU
125 			 * used -r to specify a reference to a file. Now, like
126 			 * some BSDs we attempt to parse the time. If we can,
127 			 * then we use that, otherwise we fall back and treat it
128 			 * like GNU.
129 			 */
130 			rflag++;
131 			errno = 0;
132 			ts.tv_sec = strtol(optarg, &eptr, 0);
133 			if (errno == EINVAL || *eptr != '\0') {
134 				struct stat st;
135 				if (stat(optarg, &st) == 0) {
136 					ts.tv_sec = st.st_mtime;
137 				} else {
138 					(void) fprintf(stderr,
139 					    gettext("date: failed to get stat "
140 					    "information about %s: %s\n"),
141 					    optarg, strerror(errno));
142 					exit(1);
143 				}
144 			} else if (errno != 0) {
145 				(void) fprintf(stderr,
146 				    gettext("date: failed to parse -r "
147 				    "argument: %s\n"), optarg);
148 				exit(1);
149 			}
150 			break;
151 		default:
152 			illflag++;
153 		}
154 
155 	argc -= optind;
156 	argv  = &argv[optind];
157 
158 	/* -a is mutually exclusive with -u, -R, and -r */
159 	if (uflag && aflag)
160 		illflag++;
161 	if (Rflag && aflag)
162 		illflag++;
163 	if (rflag && aflag)
164 		illflag++;
165 
166 	if (illflag) {
167 		(void) fprintf(stderr, gettext(usage));
168 		exit(1);
169 	}
170 
171 	if (rflag == 0) {
172 		if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
173 			perror(gettext("date: Failed to obtain system time"));
174 			exit(1);
175 		}
176 	}
177 	clock_val = ts.tv_sec;
178 
179 	if (aflag) {
180 		if (adjtime(&tv, 0) < 0) {
181 			perror(gettext("date: Failed to adjust date"));
182 			exit(1);
183 		}
184 		exit(0);
185 	}
186 
187 	if (argc > 0) {
188 		if (*argv[0] == '+')
189 			fmt = &argv[0][1];
190 		else {
191 			if (setdate(localtime(&clock_val), argv[0])) {
192 				(void) fprintf(stderr, gettext(usage));
193 				exit(1);
194 			}
195 			fmt = nl_langinfo(_DATE_FMT);
196 		}
197 	} else if (Rflag) {
198 		fmt = "%a, %d %h %Y %H:%M:%S %z";
199 	} else
200 		fmt = nl_langinfo(_DATE_FMT);
201 
202 	fmt_extensions(fmtbuf, sizeof (fmtbuf), fmt, &ts);
203 
204 	if (uflag) {
205 		(void) putenv("TZ=GMT0");
206 		tzset();
207 		tp = gmtime(&clock_val);
208 	} else
209 		tp = localtime(&clock_val);
210 	(void) memcpy(&tm, tp, sizeof (struct tm));
211 	(void) strftime(buf, BUFSIZ, fmtbuf, &tm);
212 
213 	(void) puts(buf);
214 
215 	return (0);
216 }
217 
218 int
219 setdate(struct tm *current_date, char *date)
220 {
221 	int	i;
222 	int	mm;
223 	int	hh;
224 	int	min;
225 	int	sec = 0;
226 	char	*secptr;
227 	int	yy;
228 	int	dd	= 0;
229 	int	minidx	= 6;
230 	int	len;
231 	int	dd_check;
232 
233 	/*  Parse date string  */
234 	if ((secptr = strchr(date, '.')) != NULL && strlen(&secptr[1]) == 2 &&
235 	    isdigit(secptr[1]) && isdigit(secptr[2]) &&
236 	    (sec = atoi(&secptr[1])) >= 0 && sec < 60)
237 		secptr[0] = '\0';	/* eat decimal point only on success */
238 
239 	len = strlen(date);
240 
241 	for (i = 0; i < len; i++) {
242 		if (!isdigit(date[i])) {
243 			(void) fprintf(stderr,
244 			gettext("date: bad conversion\n"));
245 			exit(1);
246 		}
247 	}
248 	switch (strlen(date)) {
249 	case 12:
250 		yy = atoi(&date[8]);
251 		date[8] = '\0';
252 		break;
253 	case 10:
254 		/*
255 		 * The YY format has the following representation:
256 		 * 00-68 = 2000 thru 2068
257 		 * 69-99 = 1969 thru 1999
258 		 */
259 		if (atoi(&date[8]) <= 68) {
260 			yy = 1900 + (atoi(&date[8]) + 100);
261 		} else {
262 			yy = 1900 + atoi(&date[8]);
263 		}
264 		date[8] = '\0';
265 		break;
266 	case 8:
267 		yy = 1900 + current_date->tm_year;
268 		break;
269 	case 4:
270 		yy = 1900 + current_date->tm_year;
271 		mm = current_date->tm_mon + 1; 	/* tm_mon goes from 1 to 11 */
272 		dd = current_date->tm_mday;
273 		minidx = 2;
274 		break;
275 	default:
276 		(void) fprintf(stderr, gettext("date: bad conversion\n"));
277 		return (1);
278 	}
279 
280 	min = atoi(&date[minidx]);
281 	date[minidx] = '\0';
282 	hh = atoi(&date[minidx-2]);
283 	date[minidx-2] = '\0';
284 
285 	if (!dd) {
286 		/*
287 		 * if dd is 0 (not between 1 and 31), then
288 		 * read the value supplied by the user.
289 		 */
290 		dd = atoi(&date[2]);
291 		date[2] = '\0';
292 		mm = atoi(&date[0]);
293 	}
294 
295 	if (hh == 24)
296 		hh = 0, dd++;
297 
298 	/*  Validate date elements  */
299 	dd_check = 0;
300 	if (mm >= 1 && mm <= 12) {
301 		dd_check = month_size[mm - 1];	/* get days in this month */
302 		if (mm == 2 && isleap(yy))	/* adjust for leap year */
303 			dd_check++;
304 	}
305 	if (!((mm >= 1 && mm <= 12) && (dd >= 1 && dd <= dd_check) &&
306 	    (hh >= 0 && hh <= 23) && (min >= 0 && min <= 59))) {
307 		(void) fprintf(stderr, gettext("date: bad conversion\n"));
308 		return (1);
309 	}
310 
311 	/*  Build date and time number  */
312 	for (clock_val = 0, i = 1970; i < yy; i++)
313 		clock_val += year_size(i);
314 	/*  Adjust for leap year  */
315 	if (isleap(yy) && mm >= 3)
316 		clock_val += 1;
317 	/*  Adjust for different month lengths  */
318 	while (--mm)
319 		clock_val += (time_t)month_size[mm - 1];
320 	/*  Load up the rest  */
321 	clock_val += (time_t)(dd - 1);
322 	clock_val *= 24;
323 	clock_val += (time_t)hh;
324 	clock_val *= 60;
325 	clock_val += (time_t)min;
326 	clock_val *= 60;
327 	clock_val += sec;
328 
329 	if (!uflag) {
330 		/* convert to GMT assuming standard time */
331 		/* correction is made in localtime(3C) */
332 
333 		/*
334 		 * call localtime to set up "timezone" variable applicable
335 		 * for clock_val time, to support Olson timezones which
336 		 * can allow timezone rules to change.
337 		 */
338 		(void) localtime(&clock_val);
339 
340 		clock_val += (time_t)timezone;
341 
342 		/* correct if daylight savings time in effect */
343 
344 		if (localtime(&clock_val)->tm_isdst)
345 			clock_val = clock_val - (time_t)(timezone - altzone);
346 	}
347 
348 	(void) time(&wtmpx[0].ut_xtime);
349 	if (stime(&clock_val) < 0) {
350 		perror("date");
351 		return (1);
352 	}
353 #if defined(i386)
354 	/* correct the kernel's "gmt_lag" and the PC's RTC */
355 	(void) system("/usr/sbin/rtc -c > /dev/null 2>&1");
356 #endif
357 	(void) time(&wtmpx[1].ut_xtime);
358 	(void) pututxline(&wtmpx[0]);
359 	(void) pututxline(&wtmpx[1]);
360 	(void) updwtmpx(WTMPX_FILE, &wtmpx[0]);
361 	(void) updwtmpx(WTMPX_FILE, &wtmpx[1]);
362 	return (0);
363 }
364 
365 int
366 get_adj(char *cp, struct timeval *tp)
367 {
368 	register int mult;
369 	int sign;
370 
371 	/* arg must be [-]sss[.fff] */
372 
373 	tp->tv_sec = tp->tv_usec = 0;
374 	if (*cp == '-') {
375 		sign = -1;
376 		cp++;
377 	} else {
378 		sign = 1;
379 	}
380 
381 	while (*cp >= '0' && *cp <= '9') {
382 		tp->tv_sec *= 10;
383 		tp->tv_sec += *cp++ - '0';
384 	}
385 	if (*cp == '.') {
386 		cp++;
387 		mult = 100000;
388 		while (*cp >= '0' && *cp <= '9') {
389 			tp->tv_usec += (*cp++ - '0') * mult;
390 			mult /= 10;
391 		}
392 	}
393 	/*
394 	 * if there's anything left in the string,
395 	 * the input was invalid.
396 	 */
397 	if (*cp) {
398 		return (-1);
399 	} else {
400 		tp->tv_sec *= sign;
401 		tp->tv_usec *= sign;
402 		return (0);
403 	}
404 }
405 
406 /*
407  * Extensions that cannot be interpreted by strftime are interpreted here.
408  */
409 void
410 fmt_extensions(char *fmtbuf, size_t len,
411     const char *fmt, const struct timespec *tsp)
412 {
413 	const char *p;
414 	char *q;
415 
416 	for (p = fmt, q = fmtbuf; *p != '\0' && q < fmtbuf + len; ++p) {
417 		if (*p == '%') {
418 			switch (*(p + 1)) {
419 			case 'N':
420 				++p;
421 				q += snprintf(q, len - (q - fmtbuf),
422 				    "%09lu", tsp->tv_nsec);
423 				continue;
424 			}
425 		}
426 		*q++ = *p;
427 	}
428 
429 	if (q < fmtbuf + len)
430 		*q = '\0';
431 	else
432 		fmtbuf[len - 1] = '\0';
433 }
434