1/***********************************************************************
2*                                                                      *
3*               This software is part of the ast package               *
4*          Copyright (c) 1992-2012 AT&T Intellectual Property          *
5*                      and is licensed under the                       *
6*                 Eclipse Public License, Version 1.0                  *
7*                    by AT&T Intellectual Property                     *
8*                                                                      *
9*                A copy of the License is available at                 *
10*          http://www.eclipse.org/org/documents/epl-v10.html           *
11*         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
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) 2011-01-27 $\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"		[+B?full month name]"
93"		[+c?\bctime\b(3) style date without the trailing newline]"
94"		[+C?2-digit century]"
95"		[+d?day of month number]"
96"		[+D?date as \amm/dd/yy\a]"
97"		[+e?blank padded 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, use pad _ for \aSHH:MM\a]"
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"[R:rfc-2822?List date and time in RFC 2822 format "
172    "(%a, %-e %h %Y %H:%M:%S %z).]"
173"[T:rfc-3339?List date and time in RFC 3339 format according to "
174    "\atype\a:]:[type]"
175    "{"
176        "[d:date?(%Y-%m-%d)]"
177        "[s:seconds?(%Y-%m-%d %H:%M:%S%_z)]"
178        "[n:ns|nanoseconds?(%Y-%m-%d %H:%M:%S.%N%_z)]"
179    "}"
180"[s:show?Show the date without setting the system time.]"
181"[u:utc|gmt|zulu|universal?Output dates in \acoordinated universal time\a (UTC).]"
182"[U:unelapsed?Interpret each argument as \bfmtelapsed\b(3) elapsed"
183"	time and list the \bstrelapsed\b(3) 1/\ascale\a seconds.]#[scale]"
184"[z:list-zones?List the known time zone table and exit. The table columns"
185"	are: country code, standard zone name, savings time zone name,"
186"	minutes west of \bUTC\b, and savings time minutes offset. Blank"
187"	or empty entries are listed as \b-\b.]"
188
189"\n"
190"\n[ +format | date ... | file ... ]\n"
191"\n"
192
193"[+SEE ALSO?\bcrontab\b(1), \bls\b(1), \btouch\b(1), \bfmtelapsed\b(3),"
194"	\bstrftime\b(3), \bstrptime\b(3), \btm\b(3)]"
195;
196
197#include <cmd.h>
198#include <ls.h>
199#include <proc.h>
200#include <tmx.h>
201#include <times.h>
202
203typedef struct Fmt
204{
205	struct Fmt*	next;
206	char*		format;
207} Fmt_t;
208
209#ifndef ENOSYS
210#define ENOSYS		EINVAL
211#endif
212
213/*
214 * set the system clock
215 * the standards wimped out here
216 */
217
218static int
219settime(Shbltin_t* context, const char* cmd, Time_t now, int adjust, int network)
220{
221	char*		s;
222	char**		argv;
223	char*		args[5];
224	char		buf[1024];
225
226	if (!adjust && !network)
227		return tmxsettime(now);
228	argv = args;
229	s = "/usr/bin/date";
230	if (!streq(cmd, s) && (!eaccess(s, X_OK) || !eaccess(s+=4, X_OK)))
231	{
232		*argv++ = s;
233		if (streq(astconf("UNIVERSE", NiL, NiL), "att"))
234		{
235			tmxfmt(buf, sizeof(buf), "%m%d%H" "%M%Y.%S", now);
236			if (adjust)
237				*argv++ = "-a";
238		}
239		else
240		{
241			tmxfmt(buf, sizeof(buf), "%Y%m%d%H" "%M.%S", now);
242			if (network)
243				*argv++ = "-n";
244			if (tm_info.flags & TM_UTC)
245				*argv++ = "-u";
246		}
247		*argv++ = buf;
248		*argv = 0;
249		if (!sh_run(context, argv - args, args))
250			return 0;
251	}
252	return -1;
253}
254
255/*
256 * convert s to Time_t with error checking
257 */
258
259static Time_t
260convert(register Fmt_t* f, char* s, Time_t now)
261{
262	char*	t;
263	char*	u;
264
265	do
266	{
267		now = tmxscan(s, &t, f->format, &u, now, 0);
268		if (!*t && (!f->format || !*u))
269			break;
270	} while (f = f->next);
271	if (!f || *t)
272		error(3, "%s: invalid date specification", f ? t : s);
273	return now;
274}
275
276int
277b_date(int argc, register char** argv, Shbltin_t* context)
278{
279	register int	n;
280	register char*	s;
281	register Fmt_t*	f;
282	char*		t;
283	unsigned long	u;
284	Time_t		now;
285	Time_t		ts;
286	Time_t		te;
287	Time_t		e;
288	char		buf[1024];
289	Fmt_t*		fmts;
290	Fmt_t		fmt;
291	struct stat	st;
292
293	char*		cmd = argv[0];	/* original command path	*/
294	char*		format = 0;	/* tmxfmt() format		*/
295	char*		string = 0;	/* date string			*/
296	int		elapsed = 0;	/* args are start/stop pairs	*/
297	int		filetime = 0;	/* use this st_ time field	*/
298	int		increment = 0;	/* incrementally adjust time	*/
299	int		last = 0;	/* display the last time arg	*/
300	Tm_zone_t*	listzones = 0;	/* known time zone table	*/
301	int		network = 0;	/* don't set network time	*/
302	int		show = 0;	/* show date and don't set	*/
303	int		unelapsed = 0;	/* fmtelapsed() => strelapsed	*/
304
305	cmdinit(argc, argv, context, ERROR_CATALOG, 0);
306	tm_info.flags = TM_DATESTYLE;
307	fmts = &fmt;
308	fmt.format = "";
309	fmt.next = 0;
310	for (;;)
311	{
312		switch (optget(argv, usage))
313		{
314		case 'a':
315		case 'c':
316		case 'm':
317			filetime = opt_info.option[1];
318			continue;
319		case 'd':
320			string = opt_info.arg;
321			show = 1;
322			continue;
323		case 'e':
324			format = "%s";
325			continue;
326		case 'E':
327			elapsed = 1;
328			continue;
329		case 'f':
330			format = opt_info.arg;
331			continue;
332		case 'i':
333			increment = 1;
334			continue;
335		case 'l':
336			tm_info.flags |= TM_LEAP;
337			continue;
338		case 'L':
339			last = 1;
340			continue;
341		case 'n':
342			network = 1;
343			continue;
344		case 'p':
345			if (!(f = newof(0, Fmt_t, 1, 0)))
346				error(ERROR_SYSTEM|3, "out of space [format]");
347			f->next = fmts;
348			f->format = opt_info.arg;
349			fmts = f;
350			continue;
351		case 'R':
352			format = "%a, %-e %h %Y %H:%M:%S %z";
353			continue;
354		case 's':
355			show = 1;
356			continue;
357		case 'T':
358			switch (opt_info.num)
359			{
360			case 'd':
361				format = "%Y-%m-%d";
362				continue;
363			case 'n':
364				format = "%Y-%m-%d %H:%M:%S.%N%_z";
365				continue;
366			case 's':
367				format = "%Y-%m-%d %H:%M:%S%_z";
368				continue;
369			}
370			continue;
371		case 'u':
372			tm_info.flags |= TM_UTC;
373			continue;
374		case 'U':
375			unelapsed = (int)opt_info.num;
376			continue;
377		case 'z':
378			listzones = tm_data.zone;
379			continue;
380		case '?':
381			error(ERROR_USAGE|4, "%s", opt_info.arg);
382			continue;
383		case ':':
384			error(2, "%s", opt_info.arg);
385			continue;
386		}
387		break;
388	}
389	argv += opt_info.index;
390	if (error_info.errors)
391		error(ERROR_USAGE|4, "%s", optusage(NiL));
392	now = tmxgettime();
393	if (listzones)
394	{
395		s = "-";
396		while (listzones->standard)
397		{
398			if (listzones->type)
399				s = listzones->type;
400			sfprintf(sfstdout, "%3s %4s %4s %4d %4d\n", s, *listzones->standard ? listzones->standard : "-", listzones->daylight ? listzones->daylight : "-", listzones->west, listzones->dst);
401			listzones++;
402			show = 1;
403		}
404	}
405	else if (elapsed)
406	{
407		e = 0;
408		while (s = *argv++)
409		{
410			if (!(t = *argv++))
411			{
412				argv--;
413				t = "now";
414			}
415			ts = convert(fmts, s, now);
416			te = convert(fmts, t, now);
417			if (te > ts)
418				e += te - ts;
419			else
420				e += ts - te;
421		}
422		sfputr(sfstdout, fmtelapsed((unsigned long)tmxsec(e), 1), '\n');
423		show = 1;
424	}
425	else if (unelapsed)
426	{
427		while (s = *argv++)
428		{
429			u = strelapsed(s, &t, unelapsed);
430			if (*t)
431				error(3, "%s: invalid elapsed time", s);
432			sfprintf(sfstdout, "%lu\n", u);
433		}
434		show = 1;
435	}
436	else if (filetime)
437	{
438		if (!*argv)
439			error(ERROR_USAGE|4, "%s", optusage(NiL));
440		n = argv[1] != 0;
441		while (s = *argv++)
442		{
443			if (stat(s, &st))
444				error(2, "%s: not found", s);
445			else
446			{
447				switch (filetime)
448				{
449				case 'a':
450					now = tmxgetatime(&st);
451					break;
452				case 'c':
453					now = tmxgetctime(&st);
454					break;
455				default:
456					now = tmxgetmtime(&st);
457					break;
458				}
459				tmxfmt(buf, sizeof(buf), format, now);
460				if (n)
461					sfprintf(sfstdout, "%s: %s\n", s, buf);
462				else
463					sfprintf(sfstdout, "%s\n", buf);
464				show = 1;
465			}
466		}
467	}
468	else
469	{
470		if ((s = *argv) && !format && *s == '+')
471		{
472			format = s + 1;
473			argv++;
474			s = *argv;
475		}
476		if (s || (s = string))
477		{
478			if (*argv && string)
479				error(ERROR_USAGE|4, "%s", optusage(NiL));
480			now = convert(fmts, s, now);
481			if (*argv && (s = *++argv))
482			{
483				show = 1;
484				do
485				{
486					if (!last)
487					{
488						tmxfmt(buf, sizeof(buf), format, now);
489						sfprintf(sfstdout, "%s\n", buf);
490					}
491					now = convert(fmts, s, now);
492				} while (s = *++argv);
493			}
494		}
495		else
496			show = 1;
497		if (format || show)
498		{
499			tmxfmt(buf, sizeof(buf), format, now);
500			sfprintf(sfstdout, "%s\n", buf);
501		}
502		else if (settime(context, cmd, now, increment, network))
503			error(ERROR_SYSTEM|3, "cannot set system time");
504	}
505	while (fmts != &fmt)
506	{
507		f = fmts;
508		fmts = fmts->next;
509		free(f);
510	}
511	tm_info.flags = 0;
512	if (show && sfsync(sfstdout))
513		error(ERROR_system(0), "write error");
514	return error_info.errors != 0;
515}
516