1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1985, 1987, 1988, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/param.h> 33 #include <sys/time.h> 34 #include <sys/stat.h> 35 36 #include <ctype.h> 37 #include <err.h> 38 #include <errno.h> 39 #include <locale.h> 40 #include <stdbool.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <syslog.h> 45 #include <unistd.h> 46 #include <utmpx.h> 47 48 #include "vary.h" 49 50 #ifndef TM_YEAR_BASE 51 #define TM_YEAR_BASE 1900 52 #endif 53 54 static void badformat(void); 55 static void iso8601_usage(const char *) __dead2; 56 static void multipleformats(void); 57 static void printdate(const char *); 58 static void printisodate(struct tm *, long); 59 static void setthetime(const char *, const char *, int, struct timespec *); 60 static size_t strftime_ns(char * __restrict, size_t, const char * __restrict, 61 const struct tm * __restrict, long); 62 static void usage(void) __dead2; 63 64 static const struct iso8601_fmt { 65 const char *refname; 66 const char *format_string; 67 } iso8601_fmts[] = { 68 { "date", "%Y-%m-%d" }, 69 { "hours", "T%H" }, 70 { "minutes", ":%M" }, 71 { "seconds", ":%S" }, 72 { "ns", ",%N" }, 73 }; 74 static const struct iso8601_fmt *iso8601_selected; 75 76 static const char *rfc2822_format = "%a, %d %b %Y %T %z"; 77 78 int 79 main(int argc, char *argv[]) 80 { 81 struct timespec ts; 82 int ch, rflag; 83 bool Iflag, jflag, Rflag; 84 const char *format; 85 char buf[1024]; 86 char *fmt, *outzone = NULL; 87 char *tmp; 88 struct vary *v; 89 const struct vary *badv; 90 struct tm *lt; 91 struct stat sb; 92 size_t i; 93 94 v = NULL; 95 fmt = NULL; 96 (void) setlocale(LC_TIME, ""); 97 rflag = 0; 98 Iflag = jflag = Rflag = 0; 99 ts.tv_sec = 0; 100 ts.tv_nsec = 0; 101 while ((ch = getopt(argc, argv, "f:I::jnRr:uv:z:")) != -1) 102 switch((char)ch) { 103 case 'f': 104 fmt = optarg; 105 break; 106 case 'I': 107 if (Rflag) 108 multipleformats(); 109 Iflag = 1; 110 if (optarg == NULL) { 111 iso8601_selected = iso8601_fmts; 112 break; 113 } 114 for (i = 0; i < nitems(iso8601_fmts); i++) 115 if (strcmp(optarg, iso8601_fmts[i].refname) == 0) 116 break; 117 if (i == nitems(iso8601_fmts)) 118 iso8601_usage(optarg); 119 120 iso8601_selected = &iso8601_fmts[i]; 121 break; 122 case 'j': 123 jflag = 1; /* don't set time */ 124 break; 125 case 'n': 126 break; 127 case 'R': /* RFC 2822 datetime format */ 128 if (Iflag) 129 multipleformats(); 130 Rflag = 1; 131 break; 132 case 'r': /* user specified seconds */ 133 rflag = 1; 134 ts.tv_sec = strtoq(optarg, &tmp, 0); 135 if (*tmp != 0) { 136 if (stat(optarg, &sb) == 0) { 137 ts.tv_sec = sb.st_mtim.tv_sec; 138 ts.tv_nsec = sb.st_mtim.tv_nsec; 139 } else 140 usage(); 141 } 142 break; 143 case 'u': /* do everything in UTC */ 144 (void)setenv("TZ", "UTC0", 1); 145 break; 146 case 'z': 147 outzone = optarg; 148 break; 149 case 'v': 150 v = vary_append(v, optarg); 151 break; 152 default: 153 usage(); 154 } 155 argc -= optind; 156 argv += optind; 157 158 if (!rflag && clock_gettime(CLOCK_REALTIME, &ts) == -1) 159 err(1, "clock_gettime"); 160 161 format = "%+"; 162 163 if (Rflag) 164 format = rfc2822_format; 165 166 /* allow the operands in any order */ 167 if (*argv && **argv == '+') { 168 if (Iflag) 169 multipleformats(); 170 format = *argv + 1; 171 ++argv; 172 } 173 174 if (*argv) { 175 setthetime(fmt, *argv, jflag, &ts); 176 ++argv; 177 } else if (fmt != NULL) 178 usage(); 179 180 if (*argv && **argv == '+') { 181 if (Iflag) 182 multipleformats(); 183 format = *argv + 1; 184 } 185 186 if (outzone != NULL && setenv("TZ", outzone, 1) != 0) 187 err(1, "setenv(TZ)"); 188 lt = localtime(&ts.tv_sec); 189 if (lt == NULL) 190 errx(1, "invalid time"); 191 badv = vary_apply(v, lt); 192 if (badv) { 193 fprintf(stderr, "%s: Cannot apply date adjustment\n", 194 badv->arg); 195 vary_destroy(v); 196 usage(); 197 } 198 vary_destroy(v); 199 200 if (Iflag) 201 printisodate(lt, ts.tv_nsec); 202 203 if (format == rfc2822_format) 204 /* 205 * When using RFC 2822 datetime format, don't honor the 206 * locale. 207 */ 208 setlocale(LC_TIME, "C"); 209 210 211 (void)strftime_ns(buf, sizeof(buf), format, lt, ts.tv_nsec); 212 printdate(buf); 213 } 214 215 static void 216 printdate(const char *buf) 217 { 218 (void)printf("%s\n", buf); 219 if (fflush(stdout)) 220 err(1, "stdout"); 221 exit(EXIT_SUCCESS); 222 } 223 224 static void 225 printisodate(struct tm *lt, long nsec) 226 { 227 const struct iso8601_fmt *it; 228 char fmtbuf[64], buf[64], tzbuf[8]; 229 230 fmtbuf[0] = 0; 231 for (it = iso8601_fmts; it <= iso8601_selected; it++) 232 strlcat(fmtbuf, it->format_string, sizeof(fmtbuf)); 233 234 (void)strftime_ns(buf, sizeof(buf), fmtbuf, lt, nsec); 235 236 if (iso8601_selected > iso8601_fmts) { 237 (void)strftime_ns(tzbuf, sizeof(tzbuf), "%z", lt, nsec); 238 memmove(&tzbuf[4], &tzbuf[3], 3); 239 tzbuf[3] = ':'; 240 strlcat(buf, tzbuf, sizeof(buf)); 241 } 242 243 printdate(buf); 244 } 245 246 #define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) 247 248 static void 249 setthetime(const char *fmt, const char *p, int jflag, struct timespec *ts) 250 { 251 struct utmpx utx; 252 struct tm *lt; 253 const char *dot, *t; 254 int century; 255 256 lt = localtime(&ts->tv_sec); 257 if (lt == NULL) 258 errx(1, "invalid time"); 259 lt->tm_isdst = -1; /* divine correct DST */ 260 261 if (fmt != NULL) { 262 t = strptime(p, fmt, lt); 263 if (t == NULL) { 264 fprintf(stderr, "Failed conversion of ``%s''" 265 " using format ``%s''\n", p, fmt); 266 badformat(); 267 } else if (*t != '\0') 268 fprintf(stderr, "Warning: Ignoring %ld extraneous" 269 " characters in date string (%s)\n", 270 (long) strlen(t), t); 271 } else { 272 for (t = p, dot = NULL; *t; ++t) { 273 if (isdigit(*t)) 274 continue; 275 if (*t == '.' && dot == NULL) { 276 dot = t; 277 continue; 278 } 279 badformat(); 280 } 281 282 if (dot != NULL) { /* .ss */ 283 dot++; /* *dot++ = '\0'; */ 284 if (strlen(dot) != 2) 285 badformat(); 286 lt->tm_sec = ATOI2(dot); 287 if (lt->tm_sec > 61) 288 badformat(); 289 } else 290 lt->tm_sec = 0; 291 292 century = 0; 293 /* if p has a ".ss" field then let's pretend it's not there */ 294 switch (strlen(p) - ((dot != NULL) ? 3 : 0)) { 295 case 12: /* cc */ 296 lt->tm_year = ATOI2(p) * 100 - TM_YEAR_BASE; 297 century = 1; 298 /* FALLTHROUGH */ 299 case 10: /* yy */ 300 if (century) 301 lt->tm_year += ATOI2(p); 302 else { 303 lt->tm_year = ATOI2(p); 304 if (lt->tm_year < 69) /* hack for 2000 ;-} */ 305 lt->tm_year += 2000 - TM_YEAR_BASE; 306 else 307 lt->tm_year += 1900 - TM_YEAR_BASE; 308 } 309 /* FALLTHROUGH */ 310 case 8: /* mm */ 311 lt->tm_mon = ATOI2(p); 312 if (lt->tm_mon > 12) 313 badformat(); 314 --lt->tm_mon; /* time struct is 0 - 11 */ 315 /* FALLTHROUGH */ 316 case 6: /* dd */ 317 lt->tm_mday = ATOI2(p); 318 if (lt->tm_mday > 31) 319 badformat(); 320 /* FALLTHROUGH */ 321 case 4: /* HH */ 322 lt->tm_hour = ATOI2(p); 323 if (lt->tm_hour > 23) 324 badformat(); 325 /* FALLTHROUGH */ 326 case 2: /* MM */ 327 lt->tm_min = ATOI2(p); 328 if (lt->tm_min > 59) 329 badformat(); 330 break; 331 default: 332 badformat(); 333 } 334 } 335 336 /* convert broken-down time to GMT clock time */ 337 lt->tm_yday = -1; 338 ts->tv_sec = mktime(lt); 339 if (lt->tm_yday == -1) 340 errx(1, "nonexistent time"); 341 ts->tv_nsec = 0; 342 343 if (!jflag) { 344 utx.ut_type = OLD_TIME; 345 memset(utx.ut_id, 0, sizeof(utx.ut_id)); 346 (void)gettimeofday(&utx.ut_tv, NULL); 347 pututxline(&utx); 348 if (clock_settime(CLOCK_REALTIME, ts) != 0) 349 err(1, "clock_settime"); 350 utx.ut_type = NEW_TIME; 351 (void)gettimeofday(&utx.ut_tv, NULL); 352 pututxline(&utx); 353 354 if ((p = getlogin()) == NULL) 355 p = "???"; 356 syslog(LOG_AUTH | LOG_NOTICE, "date set by %s", p); 357 } 358 } 359 360 /* 361 * The strftime_ns function is a wrapper around strftime(3), which adds support 362 * for features absent from strftime(3). Currently, the only extra feature is 363 * support for %N, the nanosecond conversion specification. 364 * 365 * The functions scans the format string for the non-standard conversion 366 * specifications and replaces them with the date and time values before 367 * passing the format string to strftime(3). The handling of the non-standard 368 * conversion specifications happens before the call to strftime(3) to handle 369 * cases like "%%N" correctly ("%%N" should yield "%N" instead of nanoseconds). 370 */ 371 static size_t 372 strftime_ns(char * __restrict s, size_t maxsize, const char * __restrict format, 373 const struct tm * __restrict t, long nsec) 374 { 375 size_t prefixlen; 376 size_t ret; 377 char *newformat; 378 char *oldformat; 379 const char *prefix; 380 const char *suffix; 381 const char *tok; 382 bool seen_percent; 383 384 seen_percent = false; 385 if ((newformat = strdup(format)) == NULL) 386 err(1, "strdup"); 387 tok = newformat; 388 for (tok = newformat; *tok != '\0'; tok++) { 389 switch (*tok) { 390 case '%': 391 /* 392 * If the previous token was a percent sign, 393 * then there are two percent tokens in a row. 394 */ 395 if (seen_percent) 396 seen_percent = false; 397 else 398 seen_percent = true; 399 break; 400 case 'N': 401 if (seen_percent) { 402 oldformat = newformat; 403 prefix = oldformat; 404 prefixlen = tok - oldformat - 1; 405 suffix = tok + 1; 406 /* 407 * Construct a new format string from the 408 * prefix (i.e., the part of the old format 409 * from its beginning to the currently handled 410 * "%N" conversion specification), the 411 * nanoseconds, and the suffix (i.e., the part 412 * of the old format from the next token to the 413 * end). 414 */ 415 if (asprintf(&newformat, "%.*s%.9ld%s", 416 (int)prefixlen, prefix, nsec, 417 suffix) < 0) { 418 err(1, "asprintf"); 419 } 420 free(oldformat); 421 tok = newformat + prefixlen + 9; 422 } 423 seen_percent = false; 424 break; 425 default: 426 seen_percent = false; 427 break; 428 } 429 } 430 431 ret = strftime(s, maxsize, newformat, t); 432 free(newformat); 433 return (ret); 434 } 435 436 static void 437 badformat(void) 438 { 439 warnx("illegal time format"); 440 usage(); 441 } 442 443 static void 444 iso8601_usage(const char *badarg) 445 { 446 errx(1, "invalid argument '%s' for -I", badarg); 447 } 448 449 static void 450 multipleformats(void) 451 { 452 errx(1, "multiple output formats specified"); 453 } 454 455 static void 456 usage(void) 457 { 458 (void)fprintf(stderr, "%s\n%s\n%s\n", 459 "usage: date [-jnRu] [-I[date|hours|minutes|seconds|ns]] [-f input_fmt]", 460 " " 461 "[ -z output_zone ] [-r filename|seconds] [-v[+|-]val[y|m|w|d|H|M|S]]", 462 " " 463 "[[[[[[cc]yy]mm]dd]HH]MM[.SS] | new_date] [+output_fmt]" 464 ); 465 exit(1); 466 } 467