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
29 static const char usage[] =
30 "[-?\n@(#)$Id: date (AT&T Research) 2011-01-27 $\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 " [+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
203 typedef 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
218 static int
settime(Shbltin_t * context,const char * cmd,Time_t now,int adjust,int network)219 settime(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
259 static Time_t
convert(register Fmt_t * f,char * s,Time_t now)260 convert(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
276 int
b_date(int argc,register char ** argv,Shbltin_t * context)277 b_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