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 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 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 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