1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1992-2010 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
29static const char usage[] =
30"[-?\n@(#)$Id: date (AT&T Research) 2009-03-03 $\n]"
31USAGE_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
194typedef 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
209static int
210settime(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
250static Time_t
251convert(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
267int
268b_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	tm_info.flags = TM_DATESTYLE;
298	fmts = &fmt;
299	fmt.format = "";
300	fmt.next = 0;
301	for (;;)
302	{
303		switch (optget(argv, usage))
304		{
305		case 'a':
306		case 'c':
307		case 'm':
308			filetime = opt_info.option[1];
309			continue;
310		case 'd':
311			string = opt_info.arg;
312			show = 1;
313			continue;
314		case 'e':
315			format = "%#";
316			continue;
317		case 'E':
318			elapsed = 1;
319			continue;
320		case 'f':
321			format = opt_info.arg;
322			continue;
323		case 'i':
324			increment = 1;
325			continue;
326		case 'l':
327			tm_info.flags |= TM_LEAP;
328			continue;
329		case 'L':
330			last = 1;
331			continue;
332		case 'n':
333			network = 1;
334			continue;
335		case 'p':
336			if (!(f = newof(0, Fmt_t, 1, 0)))
337				error(ERROR_SYSTEM|3, "out of space [format]");
338			f->next = fmts;
339			f->format = opt_info.arg;
340			fmts = f;
341			continue;
342		case 's':
343			show = 1;
344			continue;
345		case 'u':
346			tm_info.flags |= TM_UTC;
347			continue;
348		case 'U':
349			unelapsed = (int)opt_info.num;
350			continue;
351		case 'z':
352			listzones = tm_data.zone;
353			continue;
354		case '?':
355			error(ERROR_USAGE|4, "%s", opt_info.arg);
356			continue;
357		case ':':
358			error(2, "%s", opt_info.arg);
359			continue;
360		}
361		break;
362	}
363	argv += opt_info.index;
364	if (error_info.errors)
365		error(ERROR_USAGE|4, "%s", optusage(NiL));
366	now = tmxgettime();
367	if (listzones)
368	{
369		s = "-";
370		while (listzones->standard)
371		{
372			if (listzones->type)
373				s = listzones->type;
374			sfprintf(sfstdout, "%3s %4s %4s %4d %4d\n", s, *listzones->standard ? listzones->standard : "-", listzones->daylight ? listzones->daylight : "-", listzones->west, listzones->dst);
375			listzones++;
376			show = 1;
377		}
378	}
379	else if (elapsed)
380	{
381		e = 0;
382		while (s = *argv++)
383		{
384			if (!(t = *argv++))
385			{
386				argv--;
387				t = "now";
388			}
389			ts = convert(fmts, s, now);
390			te = convert(fmts, t, now);
391			if (te > ts)
392				e += te - ts;
393			else
394				e += ts - te;
395		}
396		sfputr(sfstdout, fmtelapsed((unsigned long)tmxsec(e), 1), '\n');
397		show = 1;
398	}
399	else if (unelapsed)
400	{
401		while (s = *argv++)
402		{
403			u = strelapsed(s, &t, unelapsed);
404			if (*t)
405				error(3, "%s: invalid elapsed time", s);
406			sfprintf(sfstdout, "%lu\n", u);
407		}
408		show = 1;
409	}
410	else if (filetime)
411	{
412		if (!*argv)
413			error(ERROR_USAGE|4, "%s", optusage(NiL));
414		n = argv[1] != 0;
415		while (s = *argv++)
416		{
417			if (stat(s, &st))
418				error(2, "%s: not found", s);
419			else
420			{
421				switch (filetime)
422				{
423				case 'a':
424					now = tmxgetatime(&st);
425					break;
426				case 'c':
427					now = tmxgetctime(&st);
428					break;
429				default:
430					now = tmxgetmtime(&st);
431					break;
432				}
433				tmxfmt(buf, sizeof(buf), format, now);
434				if (n)
435					sfprintf(sfstdout, "%s: %s\n", s, buf);
436				else
437					sfprintf(sfstdout, "%s\n", buf);
438				show = 1;
439			}
440		}
441	}
442	else
443	{
444		if ((s = *argv) && !format && *s == '+')
445		{
446			format = s + 1;
447			argv++;
448			s = *argv;
449		}
450		if (s || (s = string))
451		{
452			if (*argv && string)
453				error(ERROR_USAGE|4, "%s", optusage(NiL));
454			now = convert(fmts, s, now);
455			if (*argv && (s = *++argv))
456			{
457				show = 1;
458				do
459				{
460					if (!last)
461					{
462						tmxfmt(buf, sizeof(buf), format, now);
463						sfprintf(sfstdout, "%s\n", buf);
464					}
465					now = convert(fmts, s, now);
466				} while (s = *++argv);
467			}
468		}
469		else
470			show = 1;
471		if (format || show)
472		{
473			tmxfmt(buf, sizeof(buf), format, now);
474			sfprintf(sfstdout, "%s\n", buf);
475		}
476		else if (settime(context, cmd, now, increment, network))
477			error(ERROR_SYSTEM|3, "cannot set system time");
478	}
479	while (fmts != &fmt)
480	{
481		f = fmts;
482		fmts = fmts->next;
483		free(f);
484	}
485	tm_info.flags = 0;
486	if (show && sfsync(sfstdout))
487		error(ERROR_system(0), "write error");
488	return error_info.errors != 0;
489}
490