xref: /titanic_44/usr/src/lib/libcmd/common/date.c (revision 468c413a79615e77179e8d98f22a7e513a8135bd)
1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1992-2009 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                  Common Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *            http://www.opensource.org/licenses/cpl1.0.txt             *
11 *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *                 Glenn Fowler <gsf@research.att.com>                  *
18 *                  David Korn <dgk@research.att.com>                   *
19 *                                                                      *
20 ***********************************************************************/
21 #pragma prototyped
22 /*
23  * Glenn Fowler
24  * AT&T Research
25  *
26  * date -- set/display date
27  */
28 
29 static const char usage[] =
30 "[-?\n@(#)$Id: date (AT&T Research) 2009-03-03 $\n]"
31 USAGE_LICENSE
32 "[+NAME?date - set/list/convert dates]"
33 "[+DESCRIPTION?\bdate\b sets the current date and time (with appropriate"
34 "	privilege), lists the current date or file dates, or converts"
35 "	dates.]"
36 "[+?Most common \adate\a forms are recognized, including those for"
37 "	\bcrontab\b(1), \bls\b(1), \btouch\b(1), and the default"
38 "	output from \bdate\b itself.]"
39 "[+?If the \adate\a operand consists of 4, 6, 8, 10 or 12 digits followed"
40 "	by an optional \b.\b and two digits then it is interpreted as:"
41 "	\aHHMM.SS\a, \addHHMM.SS\a, \ammddHHMM.SS\a, \ammddHHMMyy.SS\a or"
42 "	\ayymmddHHMM.SS\a, or \ammddHHMMccyy.SS\a or \accyymmddHHMM.SS\a."
43 "	Conflicting standards and practice allow a leading or trailing"
44 "	2 or 4 digit year for the 10 and 12 digit forms; the X/Open trailing"
45 "	form is used to disambiguate (\btouch\b(1) uses the leading form.)"
46 "	Avoid the 10 digit form to avoid confusion. The digit fields are:]{"
47 "		[+cc?Century - 1, 19-20.]"
48 "		[+yy?Year in century, 00-99.]"
49 "		[+mm?Month, 01-12.]"
50 "		[+dd?Day of month, 01-31.]"
51 "		[+HH?Hour, 00-23.]"
52 "		[+MM?Minute, 00-59.]"
53 "		[+SS?Seconds, 00-60.]"
54 "}"
55 "[+?If more than one \adate\a operand is specified then:]{"
56 "		[+1.?Each operand sets the reference date for the next"
57 "			operand.]"
58 "		[+2.?The date is listed for each operand.]"
59 "		[+3.?The system date is not set.]"
60 "}"
61 
62 "[a:access-time|atime?List file argument access times.]"
63 "[c:change-time|ctime?List file argument change times.]"
64 "[d:date?Use \adate\a as the current date and do not set the system"
65 "	clock.]:[date]"
66 "[e:epoch?Output the date in seconds since the epoch."
67 "	Equivalent to \b--format=%s\b.]"
68 "[E:elapsed?Interpret pairs of arguments as start and stop dates, sum the"
69 "	differences between all pairs, and list the result as a"
70 "	\bfmtelapsed\b(3) elapsed time on the standard output. If there are"
71 "	an odd number of arguments then the last time argument is differenced"
72 "	with the current time.]"
73 "[f:format?Output the date according to the \bstrftime\b(3) \aformat\a."
74 "	For backwards compatibility, a first argument of the form"
75 "	\b+\b\aformat\a is equivalent to \b-f\b format."
76 "	\aformat\a is in \bprintf\b(3) style, where %\afield\a names"
77 "	a fixed size field, zero padded if necessary,"
78 "	and \\\ac\a and \\\annn\a sequences are as in C. Invalid"
79 "	%\afield\a specifications and all other characters are copied"
80 "	without change. \afield\a may be preceded by \b%-\b to turn off"
81 "	padding or \b%_\b to pad with space, otherwise numeric fields"
82 "	are padded with \b0\b and string fields are padded with space."
83 "	\afield\a may also be preceded by \bE\b for alternate era"
84 "	representation or \bO\b for alternate digit representation (if"
85 "	supported by the current locale.) Finally, an integral \awidth\a"
86 "	preceding \afield\a truncates the field to \awidth\a characters."
87 "	The fields are:]:[format]{"
88 "		[+%?% character]"
89 "		[+a?abbreviated weekday name]"
90 "		[+A?full weekday name]"
91 "		[+b?abbreviated month name]"
92 "		[+c?\bctime\b(3) style date without the trailing newline]"
93 "		[+C?2-digit century]"
94 "		[+d?day of month number]"
95 "		[+D?date as \amm/dd/yy\a]"
96 "		[+e?blank padded day of month number]"
97 "		[+E?unpadded day of month number]"
98 "		[+f?locale default override date format]"
99 "		[+F?%ISO 8601:2000 standard date format; equivalent to Y-%m-%d]"
100 "		[+g?\bls\b(1) \b-l\b recent date with \ahh:mm\a]"
101 "		[+G?\bls\b(1) \b-l\b distant date with \ayyyy\a]"
102 "		[+h?abbreviated month name]"
103 "		[+H?24-hour clock hour]"
104 "		[+i?international \bdate\b(1) date with time zone type name]"
105 "		[+I?12-hour clock hour]"
106 "		[+j?1-offset Julian date]"
107 "		[+J?0-offset Julian date]"
108 "		[+k?\bdate\b(1) style date]"
109 "		[+K?all numeric date; equivalent to \b%Y-%m-%d+%H:%M:%S\b; \b%_[EO]]K\b for space separator, %OK adds \b.%N\b, \b%EK\b adds \b%.N%z\b, \b%_EK\b adds \b.%N %z\b]"
110 "		[+l?\bls\b(1) \b-l\b date; equivalent to \b%Q/%g/%G/\b]"
111 "		[+L?locale default date format]"
112 "		[+m?month number]"
113 "		[+M?minutes]"
114 "		[+n?newline character]"
115 "		[+N?nanoseconds 000000000-999999999]"
116 "		[+p?meridian (e.g., \bAM\b or \bPM\b)]"
117 "		[+q?time zone type name (nation code)]"
118 "		[+Q?\a<del>recent<del>distant<del>\a: \a<del>\a is a unique"
119 "			delimter character; \arecent\a format for recent"
120 "			dates, \adistant\a format otherwise]"
121 "		[+r?12-hour time as \ahh:mm:ss meridian\a]"
122 "		[+R?24-hour time as \ahh:mm\a]"
123 "		[+s?number of seconds since the epoch; \a.prec\a preceding"
124 "			\bs\b appends \aprec\a nanosecond digits, \b9\b if"
125 "			\aprec\a is omitted]"
126 "		[+S?seconds 00-60]"
127 "		[+t?tab character]"
128 "		[+T?24-hour time as \ahh:mm:ss\a]"
129 "		[+u?weekday number 1(Monday)-7]"
130 "		[+U?week number with Sunday as the first day]"
131 "		[+V?ISO week number (i18n is \afun\a)]"
132 "		[+w?weekday number 0(Sunday)-6]"
133 "		[+W?week number with Monday as the first day]"
134 "		[+x?locale date style that includes month, day and year]"
135 "		[+X?locale time style that includes hours and minutes]"
136 "		[+y?2-digit year (you'll be sorry)]"
137 "		[+Y?4-digit year]"
138 "		[+z?time zone \aSHHMM\a west of GMT offset where S is"
139 "			\b+\b or \b-\b]"
140 "		[+Z?time zone name]"
141 "		[+=[=]][-+]]flag?set (default or +) or clear (-) \aflag\a"
142 "			for the remainder of \aformat\a, or for the remainder"
143 "			of the process if \b==\b is specified. \aflag\a may be:]{"
144 "			[+l?enable leap second adjustments]"
145 "			[+n?convert \b%S\b as \b%S.%N\b]"
146 "			[+u?UTC time zone]"
147 "		}"
148 "		[+#?equivalent to %s]"
149 "		[+??alternate?use \aalternate\a format if a default format"
150 "			override has not been specified, e.g., \bls\b(1) uses"
151 "			\"%?%l\"; export TM_OPTIONS=\"format='\aoverride\a'\""
152 "			to override the default]"
153 "}"
154 "[i:incremental|adjust?Set the system time in incrementatl adjustments to"
155 "	avoid complete time shift shock. Negative adjustments still maintain"
156 "	monotonic increasing time. Not available on all systems.]"
157 "[L:last?List only the last time for multiple \adate\a operands.]"
158 "[l:leap-seconds?Include leap seconds in time calculations. Leap seconds"
159 "	after the ast library release date are not accounted for.]"
160 "[m:modify-time|mtime?List file argument modify times.]"
161 "[n!:network?Set network time.]"
162 "[p:parse?Add \aformat\a to the list of \bstrptime\b(3) parse conversion"
163 "	formats. \aformat\a follows the same conventions as the"
164 "	\b--format\b option, with the addition of these format"
165 "	fields:]:[format]{"
166 "		[+|?If the format failed before this point then restart"
167 "			the parse with the remaining format.]"
168 "		[+&?Call the \btmdate\b(3) heuristic parser. This is"
169 "			is the default when \b--parse\b is omitted.]"
170 "}"
171 "[s:show?Show the date without setting the system time.]"
172 "[u:utc|gmt|zulu?Output dates in \acoordinated universal time\a (UTC).]"
173 "[U:unelapsed?Interpret each argument as \bfmtelapsed\b(3) elapsed"
174 "	time and list the \bstrelapsed\b(3) 1/\ascale\a seconds.]#[scale]"
175 "[z:list-zones?List the known time zone table and exit. The table columns"
176 "	are: country code, standard zone name, savings time zone name,"
177 "	minutes west of \bUTC\b, and savings time minutes offset. Blank"
178 "	or empty entries are listed as \b-\b.]"
179 
180 "\n"
181 "\n[ +format | date ... | file ... ]\n"
182 "\n"
183 
184 "[+SEE ALSO?\bcrontab\b(1), \bls\b(1), \btouch\b(1), \bfmtelapsed\b(3),"
185 "	\bstrftime\b(3), \bstrptime\b(3), \btm\b(3)]"
186 ;
187 
188 #include <cmd.h>
189 #include <ls.h>
190 #include <proc.h>
191 #include <tmx.h>
192 #include <times.h>
193 
194 typedef struct Fmt
195 {
196 	struct Fmt*	next;
197 	char*		format;
198 } Fmt_t;
199 
200 #ifndef ENOSYS
201 #define ENOSYS		EINVAL
202 #endif
203 
204 /*
205  * set the system clock
206  * the standards wimped out here
207  */
208 
209 static int
210 settime(void* context, const char* cmd, Time_t now, int adjust, int network)
211 {
212 	char*		s;
213 	char**		argv;
214 	char*		args[5];
215 	char		buf[1024];
216 
217 	if (!adjust && !network)
218 		return tmxsettime(now);
219 	argv = args;
220 	s = "/usr/bin/date";
221 	if (!streq(cmd, s) && (!eaccess(s, X_OK) || !eaccess(s+=4, X_OK)))
222 	{
223 		*argv++ = s;
224 		if (streq(astconf("UNIVERSE", NiL, NiL), "att"))
225 		{
226 			tmxfmt(buf, sizeof(buf), "%m%d%H" "%M%Y.%S", now);
227 			if (adjust)
228 				*argv++ = "-a";
229 		}
230 		else
231 		{
232 			tmxfmt(buf, sizeof(buf), "%Y%m%d%H" "%M.%S", now);
233 			if (network)
234 				*argv++ = "-n";
235 			if (tm_info.flags & TM_UTC)
236 				*argv++ = "-u";
237 		}
238 		*argv++ = buf;
239 		*argv = 0;
240 		if (!sh_run(context, argv - args, args))
241 			return 0;
242 	}
243 	return -1;
244 }
245 
246 /*
247  * convert s to Time_t with error checking
248  */
249 
250 static Time_t
251 convert(register Fmt_t* f, char* s, Time_t now)
252 {
253 	char*	t;
254 	char*	u;
255 
256 	do
257 	{
258 		now = tmxscan(s, &t, f->format, &u, now, 0);
259 		if (!*t && (!f->format || !*u))
260 			break;
261 	} while (f = f->next);
262 	if (!f || *t)
263 		error(3, "%s: invalid date specification", f ? t : s);
264 	return now;
265 }
266 
267 int
268 b_date(int argc, register char** argv, void* context)
269 {
270 	register int	n;
271 	register char*	s;
272 	register Fmt_t*	f;
273 	char*		t;
274 	unsigned long	u;
275 	Time_t		now;
276 	Time_t		ts;
277 	Time_t		te;
278 	Time_t		e;
279 	char		buf[1024];
280 	Fmt_t*		fmts;
281 	Fmt_t		fmt;
282 	struct stat	st;
283 
284 	char*		cmd = argv[0];	/* original command path	*/
285 	char*		format = 0;	/* tmxfmt() format		*/
286 	char*		string = 0;	/* date string			*/
287 	int		elapsed = 0;	/* args are start/stop pairs	*/
288 	int		filetime = 0;	/* use this st_ time field	*/
289 	int		increment = 0;	/* incrementally adjust time	*/
290 	int		last = 0;	/* display the last time arg	*/
291 	Tm_zone_t*	listzones = 0;	/* known time zone table	*/
292 	int		network = 0;	/* don't set network time	*/
293 	int		show = 0;	/* show date and don't set	*/
294 	int		unelapsed = 0;	/* fmtelapsed() => strelapsed	*/
295 
296 	cmdinit(argc, argv, context, ERROR_CATALOG, 0);
297 	setlocale(LC_ALL, "");
298 	tm_info.flags = TM_DATESTYLE;
299 	fmts = &fmt;
300 	fmt.format = "";
301 	fmt.next = 0;
302 	for (;;)
303 	{
304 		switch (optget(argv, usage))
305 		{
306 		case 'a':
307 		case 'c':
308 		case 'm':
309 			filetime = opt_info.option[1];
310 			continue;
311 		case 'd':
312 			string = opt_info.arg;
313 			show = 1;
314 			continue;
315 		case 'e':
316 			format = "%#";
317 			continue;
318 		case 'E':
319 			elapsed = 1;
320 			continue;
321 		case 'f':
322 			format = opt_info.arg;
323 			continue;
324 		case 'i':
325 			increment = 1;
326 			continue;
327 		case 'l':
328 			tm_info.flags |= TM_LEAP;
329 			continue;
330 		case 'L':
331 			last = 1;
332 			continue;
333 		case 'n':
334 			network = 1;
335 			continue;
336 		case 'p':
337 			if (!(f = newof(0, Fmt_t, 1, 0)))
338 				error(ERROR_SYSTEM|3, "out of space [format]");
339 			f->next = fmts;
340 			f->format = opt_info.arg;
341 			fmts = f;
342 			continue;
343 		case 's':
344 			show = 1;
345 			continue;
346 		case 'u':
347 			tm_info.flags |= TM_UTC;
348 			continue;
349 		case 'U':
350 			unelapsed = (int)opt_info.num;
351 			continue;
352 		case 'z':
353 			listzones = tm_data.zone;
354 			continue;
355 		case '?':
356 			error(ERROR_USAGE|4, "%s", opt_info.arg);
357 			continue;
358 		case ':':
359 			error(2, "%s", opt_info.arg);
360 			continue;
361 		}
362 		break;
363 	}
364 	argv += opt_info.index;
365 	if (error_info.errors)
366 		error(ERROR_USAGE|4, "%s", optusage(NiL));
367 	now = tmxgettime();
368 	if (listzones)
369 	{
370 		s = "-";
371 		while (listzones->standard)
372 		{
373 			if (listzones->type)
374 				s = listzones->type;
375 			sfprintf(sfstdout, "%3s %4s %4s %4d %4d\n", s, *listzones->standard ? listzones->standard : "-", listzones->daylight ? listzones->daylight : "-", listzones->west, listzones->dst);
376 			listzones++;
377 			show = 1;
378 		}
379 	}
380 	else if (elapsed)
381 	{
382 		e = 0;
383 		while (s = *argv++)
384 		{
385 			if (!(t = *argv++))
386 			{
387 				argv--;
388 				t = "now";
389 			}
390 			ts = convert(fmts, s, now);
391 			te = convert(fmts, t, now);
392 			if (te > ts)
393 				e += te - ts;
394 			else
395 				e += ts - te;
396 		}
397 		sfputr(sfstdout, fmtelapsed((unsigned long)tmxsec(e), 1), '\n');
398 		show = 1;
399 	}
400 	else if (unelapsed)
401 	{
402 		while (s = *argv++)
403 		{
404 			u = strelapsed(s, &t, unelapsed);
405 			if (*t)
406 				error(3, "%s: invalid elapsed time", s);
407 			sfprintf(sfstdout, "%lu\n", u);
408 		}
409 		show = 1;
410 	}
411 	else if (filetime)
412 	{
413 		if (!*argv)
414 			error(ERROR_USAGE|4, "%s", optusage(NiL));
415 		n = argv[1] != 0;
416 		while (s = *argv++)
417 		{
418 			if (stat(s, &st))
419 				error(2, "%s: not found", s);
420 			else
421 			{
422 				switch (filetime)
423 				{
424 				case 'a':
425 					now = tmxgetatime(&st);
426 					break;
427 				case 'c':
428 					now = tmxgetctime(&st);
429 					break;
430 				default:
431 					now = tmxgetmtime(&st);
432 					break;
433 				}
434 				tmxfmt(buf, sizeof(buf), format, now);
435 				if (n)
436 					sfprintf(sfstdout, "%s: %s\n", s, buf);
437 				else
438 					sfprintf(sfstdout, "%s\n", buf);
439 				show = 1;
440 			}
441 		}
442 	}
443 	else
444 	{
445 		if ((s = *argv) && !format && *s == '+')
446 		{
447 			format = s + 1;
448 			argv++;
449 			s = *argv;
450 		}
451 		if (s || (s = string))
452 		{
453 			if (*argv && string)
454 				error(ERROR_USAGE|4, "%s", optusage(NiL));
455 			now = convert(fmts, s, now);
456 			if (*argv && (s = *++argv))
457 			{
458 				show = 1;
459 				do
460 				{
461 					if (!last)
462 					{
463 						tmxfmt(buf, sizeof(buf), format, now);
464 						sfprintf(sfstdout, "%s\n", buf);
465 					}
466 					now = convert(fmts, s, now);
467 				} while (s = *++argv);
468 			}
469 		}
470 		else
471 			show = 1;
472 		if (format || show)
473 		{
474 			tmxfmt(buf, sizeof(buf), format, now);
475 			sfprintf(sfstdout, "%s\n", buf);
476 		}
477 		else if (settime(context, cmd, now, increment, network))
478 			error(ERROR_SYSTEM|3, "cannot set system time");
479 	}
480 	while (fmts != &fmt)
481 	{
482 		f = fmts;
483 		fmts = fmts->next;
484 		free(f);
485 	}
486 	tm_info.flags = 0;
487 	if (show && sfsync(sfstdout))
488 		error(ERROR_system(0), "write error");
489 	return error_info.errors != 0;
490 }
491