xref: /illumos-gate/usr/src/cmd/date/date.c (revision 05d57413)
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) 2012, Joyent, Inc.  All rights reserved.
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	<ctype.h>
60 #include	<utmpx.h>
61 #include	<tzfile.h>
62 
63 #define	year_size(A)	((isleap(A)) ? 366 : 365)
64 static 	char	buf[BUFSIZ];
65 static	time_t	clock_val;
66 static  short	month_size[12] =
67 	{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
68 static  struct  utmpx wtmpx[2] = {
69 	{"", "", OTIME_MSG, 0, OLD_TIME, 0, 0, 0},
70 	{"", "", NTIME_MSG, 0, NEW_TIME, 0, 0, 0}
71 	};
72 static char *usage =
73 	"usage:\tdate [-u] mmddHHMM[[cc]yy][.SS]\n\tdate [-Ru] [+format]\n"
74 	"\tdate -a [-]sss[.fff]\n";
75 static int uflag = 0;
76 static int Rflag = 0;
77 
78 static int get_adj(char *, struct timeval *);
79 static int setdate(struct tm *, char *);
80 static void fmt_extensions(char *, size_t,
81     const char *, const struct timespec *);
82 
83 int
84 main(int argc, char **argv)
85 {
86 	struct tm *tp, tm;
87 	struct timeval tv;
88 	char *fmt;
89 	char fmtbuf[BUFSIZ];
90 	int c, aflag = 0, illflag = 0;
91 	struct timespec ts;
92 
93 	(void) setlocale(LC_ALL, "");
94 
95 #if !defined(TEXT_DOMAIN)
96 #define	TEXT_DOMAIN "SYS_TEST"
97 #endif
98 	(void) textdomain(TEXT_DOMAIN);
99 
100 	while ((c = getopt(argc, argv, "a:uR")) != EOF)
101 		switch (c) {
102 		case 'a':
103 			aflag++;
104 			if (get_adj(optarg, &tv) < 0) {
105 				(void) fprintf(stderr,
106 				    gettext("date: invalid argument -- %s\n"),
107 				    optarg);
108 				illflag++;
109 			}
110 			break;
111 		case 'u':
112 			uflag++;
113 			break;
114 		case 'R':
115 			Rflag++;
116 			break;
117 		default:
118 			illflag++;
119 		}
120 
121 	argc -= optind;
122 	argv  = &argv[optind];
123 
124 	/* -a is mutually exclusive with -u and -R */
125 	if (uflag && aflag)
126 		illflag++;
127 	if (Rflag && aflag)
128 		illflag++;
129 
130 	if (illflag) {
131 		(void) fprintf(stderr, gettext(usage));
132 		exit(1);
133 	}
134 
135 	if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
136 		perror(gettext("date: Failed to obtain system time"));
137 		exit(1);
138 	}
139 	clock_val = ts.tv_sec;
140 
141 	if (aflag) {
142 		if (adjtime(&tv, 0) < 0) {
143 			perror(gettext("date: Failed to adjust date"));
144 			exit(1);
145 		}
146 		exit(0);
147 	}
148 
149 	if (argc > 0) {
150 		if (*argv[0] == '+')
151 			fmt = &argv[0][1];
152 		else {
153 			if (setdate(localtime(&clock_val), argv[0])) {
154 				(void) fprintf(stderr, gettext(usage));
155 				exit(1);
156 			}
157 			fmt = nl_langinfo(_DATE_FMT);
158 		}
159 	} else if (Rflag) {
160 		fmt = "%a, %d %h %Y %H:%M:%S %z";
161 	} else
162 		fmt = nl_langinfo(_DATE_FMT);
163 
164 	fmt_extensions(fmtbuf, sizeof (fmtbuf), fmt, &ts);
165 
166 	if (uflag) {
167 		(void) putenv("TZ=GMT0");
168 		tzset();
169 		tp = gmtime(&clock_val);
170 	} else
171 		tp = localtime(&clock_val);
172 	(void) memcpy(&tm, tp, sizeof (struct tm));
173 	(void) strftime(buf, BUFSIZ, fmtbuf, &tm);
174 
175 	(void) puts(buf);
176 
177 	return (0);
178 }
179 
180 int
181 setdate(struct tm *current_date, char *date)
182 {
183 	int	i;
184 	int	mm;
185 	int	hh;
186 	int	min;
187 	int	sec = 0;
188 	char	*secptr;
189 	int	yy;
190 	int	dd	= 0;
191 	int	minidx	= 6;
192 	int	len;
193 	int	dd_check;
194 
195 	/*  Parse date string  */
196 	if ((secptr = strchr(date, '.')) != NULL && strlen(&secptr[1]) == 2 &&
197 	    isdigit(secptr[1]) && isdigit(secptr[2]) &&
198 	    (sec = atoi(&secptr[1])) >= 0 && sec < 60)
199 		secptr[0] = '\0';	/* eat decimal point only on success */
200 
201 	len = strlen(date);
202 
203 	for (i = 0; i < len; i++) {
204 		if (!isdigit(date[i])) {
205 			(void) fprintf(stderr,
206 			gettext("date: bad conversion\n"));
207 			exit(1);
208 		}
209 	}
210 	switch (strlen(date)) {
211 	case 12:
212 		yy = atoi(&date[8]);
213 		date[8] = '\0';
214 		break;
215 	case 10:
216 		/*
217 		 * The YY format has the following representation:
218 		 * 00-68 = 2000 thru 2068
219 		 * 69-99 = 1969 thru 1999
220 		 */
221 		if (atoi(&date[8]) <= 68) {
222 			yy = 1900 + (atoi(&date[8]) + 100);
223 		} else {
224 			yy = 1900 + atoi(&date[8]);
225 		}
226 		date[8] = '\0';
227 		break;
228 	case 8:
229 		yy = 1900 + current_date->tm_year;
230 		break;
231 	case 4:
232 		yy = 1900 + current_date->tm_year;
233 		mm = current_date->tm_mon + 1; 	/* tm_mon goes from 1 to 11 */
234 		dd = current_date->tm_mday;
235 		minidx = 2;
236 		break;
237 	default:
238 		(void) fprintf(stderr, gettext("date: bad conversion\n"));
239 		return (1);
240 	}
241 
242 	min = atoi(&date[minidx]);
243 	date[minidx] = '\0';
244 	hh = atoi(&date[minidx-2]);
245 	date[minidx-2] = '\0';
246 
247 	if (!dd) {
248 		/*
249 		 * if dd is 0 (not between 1 and 31), then
250 		 * read the value supplied by the user.
251 		 */
252 		dd = atoi(&date[2]);
253 		date[2] = '\0';
254 		mm = atoi(&date[0]);
255 	}
256 
257 	if (hh == 24)
258 		hh = 0, dd++;
259 
260 	/*  Validate date elements  */
261 	dd_check = 0;
262 	if (mm >= 1 && mm <= 12) {
263 		dd_check = month_size[mm - 1];	/* get days in this month */
264 		if (mm == 2 && isleap(yy))	/* adjust for leap year */
265 			dd_check++;
266 	}
267 	if (!((mm >= 1 && mm <= 12) && (dd >= 1 && dd <= dd_check) &&
268 	    (hh >= 0 && hh <= 23) && (min >= 0 && min <= 59))) {
269 		(void) fprintf(stderr, gettext("date: bad conversion\n"));
270 		return (1);
271 	}
272 
273 	/*  Build date and time number  */
274 	for (clock_val = 0, i = 1970; i < yy; i++)
275 		clock_val += year_size(i);
276 	/*  Adjust for leap year  */
277 	if (isleap(yy) && mm >= 3)
278 		clock_val += 1;
279 	/*  Adjust for different month lengths  */
280 	while (--mm)
281 		clock_val += (time_t)month_size[mm - 1];
282 	/*  Load up the rest  */
283 	clock_val += (time_t)(dd - 1);
284 	clock_val *= 24;
285 	clock_val += (time_t)hh;
286 	clock_val *= 60;
287 	clock_val += (time_t)min;
288 	clock_val *= 60;
289 	clock_val += sec;
290 
291 	if (!uflag) {
292 		/* convert to GMT assuming standard time */
293 		/* correction is made in localtime(3C) */
294 
295 		/*
296 		 * call localtime to set up "timezone" variable applicable
297 		 * for clock_val time, to support Olson timezones which
298 		 * can allow timezone rules to change.
299 		 */
300 		(void) localtime(&clock_val);
301 
302 		clock_val += (time_t)timezone;
303 
304 		/* correct if daylight savings time in effect */
305 
306 		if (localtime(&clock_val)->tm_isdst)
307 			clock_val = clock_val - (time_t)(timezone - altzone);
308 	}
309 
310 	(void) time(&wtmpx[0].ut_xtime);
311 	if (stime(&clock_val) < 0) {
312 		perror("date");
313 		return (1);
314 	}
315 #if defined(i386)
316 	/* correct the kernel's "gmt_lag" and the PC's RTC */
317 	(void) system("/usr/sbin/rtc -c > /dev/null 2>&1");
318 #endif
319 	(void) time(&wtmpx[1].ut_xtime);
320 	(void) pututxline(&wtmpx[0]);
321 	(void) pututxline(&wtmpx[1]);
322 	(void) updwtmpx(WTMPX_FILE, &wtmpx[0]);
323 	(void) updwtmpx(WTMPX_FILE, &wtmpx[1]);
324 	return (0);
325 }
326 
327 int
328 get_adj(char *cp, struct timeval *tp)
329 {
330 	register int mult;
331 	int sign;
332 
333 	/* arg must be [-]sss[.fff] */
334 
335 	tp->tv_sec = tp->tv_usec = 0;
336 	if (*cp == '-') {
337 		sign = -1;
338 		cp++;
339 	} else {
340 		sign = 1;
341 	}
342 
343 	while (*cp >= '0' && *cp <= '9') {
344 		tp->tv_sec *= 10;
345 		tp->tv_sec += *cp++ - '0';
346 	}
347 	if (*cp == '.') {
348 		cp++;
349 		mult = 100000;
350 		while (*cp >= '0' && *cp <= '9') {
351 			tp->tv_usec += (*cp++ - '0') * mult;
352 			mult /= 10;
353 		}
354 	}
355 	/*
356 	 * if there's anything left in the string,
357 	 * the input was invalid.
358 	 */
359 	if (*cp) {
360 		return (-1);
361 	} else {
362 		tp->tv_sec *= sign;
363 		tp->tv_usec *= sign;
364 		return (0);
365 	}
366 }
367 
368 /*
369  * Extensions that cannot be interpreted by strftime are interpreted here.
370  */
371 void
372 fmt_extensions(char *fmtbuf, size_t len,
373     const char *fmt, const struct timespec *tsp)
374 {
375 	const char *p;
376 	char *q;
377 
378 	for (p = fmt, q = fmtbuf; *p != '\0' && q < fmtbuf + len; ++p) {
379 		if (*p == '%') {
380 			switch (*(p + 1)) {
381 			case 'N':
382 				++p;
383 				q += snprintf(q, len - (q - fmtbuf),
384 				    "%09lu", tsp->tv_nsec);
385 				continue;
386 			}
387 		}
388 		*q++ = *p;
389 	}
390 
391 	if (q < fmtbuf + len)
392 		*q = '\0';
393 	else
394 		fmtbuf[len - 1] = '\0';
395 }
396