1 #if defined(LIBC_SCCS) && !defined(lint) && !defined(NOID) 2 static char elsieid[] = "@(#)strftime.c 7.57"; 3 static char *rcsid = "$OpenBSD: strftime.c,v 1.5 1998/08/14 21:39:44 deraadt Exp $"; 4 #endif /* LIBC_SCCS and not lint */ 5 6 #include "private.h" 7 8 /* 9 ** Based on the UCB version with the ID appearing below. 10 ** This is ANSIish only when "multibyte character == plain character". 11 ** 12 ** Copyright (c) 1989 The Regents of the University of California. 13 ** All rights reserved. 14 ** 15 ** Redistribution and use in source and binary forms are permitted 16 ** provided that the above copyright notice and this paragraph are 17 ** duplicated in all such forms and that any documentation, 18 ** advertising materials, and other materials related to such 19 ** distribution and use acknowledge that the software was developed 20 ** by the University of California, Berkeley. The name of the 21 ** University may not be used to endorse or promote products derived 22 ** from this software without specific prior written permission. 23 ** THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 24 ** IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 25 ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 26 */ 27 28 #if 0 29 #ifndef LIBC_SCCS 30 #ifndef lint 31 static const char sccsid[] = "@(#)strftime.c 5.4 (Berkeley) 3/14/89"; 32 #endif /* !defined lint */ 33 #endif /* !defined LIBC_SCCS */ 34 #endif 35 36 #include "tzfile.h" 37 #include "fcntl.h" 38 #include "locale.h" 39 40 struct lc_time_T { 41 const char * mon[MONSPERYEAR]; 42 const char * month[MONSPERYEAR]; 43 const char * wday[DAYSPERWEEK]; 44 const char * weekday[DAYSPERWEEK]; 45 const char * X_fmt; 46 const char * x_fmt; 47 const char * c_fmt; 48 const char * am; 49 const char * pm; 50 const char * date_fmt; 51 }; 52 53 #ifdef LOCALE_HOME 54 #include "sys/stat.h" 55 static struct lc_time_T localebuf; 56 static struct lc_time_T * _loc P((void)); 57 #define Locale _loc() 58 #endif /* defined LOCALE_HOME */ 59 #ifndef LOCALE_HOME 60 #define Locale (&C_time_locale) 61 #endif /* !defined LOCALE_HOME */ 62 63 static const struct lc_time_T C_time_locale = { 64 { 65 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 66 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 67 }, { 68 "January", "February", "March", "April", "May", "June", 69 "July", "August", "September", "October", "November", "December" 70 }, { 71 "Sun", "Mon", "Tue", "Wed", 72 "Thu", "Fri", "Sat" 73 }, { 74 "Sunday", "Monday", "Tuesday", "Wednesday", 75 "Thursday", "Friday", "Saturday" 76 }, 77 78 /* X_fmt */ 79 "%H:%M:%S", 80 81 /* 82 ** x_fmt 83 ** Since the C language standard calls for 84 ** "date, using locale's date format," anything goes. 85 ** Using just numbers (as here) makes Quakers happier; 86 ** it's also compatible with SVR4. 87 ** 88 ** XXX--might it be better to use the year-2000 friendly 89 ** %Y-%m-%d 90 ** here? 91 */ 92 "%m/%d/%y", 93 94 /* 95 ** c_fmt 96 ** XXX--Changed by millert from "%D %X" 97 ** to the more common "%a %b %d %H:%M:%S %Y" 98 ** used by everyone else. 99 */ 100 "%a %b %d %H:%M:%S %Y", 101 102 /* am */ 103 "AM", 104 105 /* pm */ 106 "PM", 107 108 /* date_fmt */ 109 "%a %b %e %H:%M:%S %Z %Y" 110 }; 111 112 static char * _add P((const char *, char *, const char *)); 113 static char * _conv P((int, const char *, char *, const char *)); 114 static char * _fmt P((const char *, const struct tm *, char *, const char *, int *)); 115 116 size_t strftime P((char *, size_t, const char *, const struct tm *)); 117 118 extern char * tzname[]; 119 120 #ifndef YEAR_2000_NAME 121 #define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" 122 #endif /* !defined YEAR_2000_NAME */ 123 124 125 #define IN_NONE 0 126 #define IN_SOME 1 127 #define IN_THIS 2 128 #define IN_ALL 3 129 130 size_t 131 strftime(s, maxsize, format, t) 132 char * const s; 133 const size_t maxsize; 134 const char * const format; 135 const struct tm * const t; 136 { 137 char * p; 138 int warn; 139 140 tzset(); 141 #ifdef LOCALE_HOME 142 localebuf.mon[0] = 0; 143 #endif /* defined LOCALE_HOME */ 144 warn = IN_NONE; 145 p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn); 146 #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU 147 if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) { 148 (void) fprintf(stderr, "\n"); 149 if (format == NULL) 150 (void) fprintf(stderr, "NULL strftime format "); 151 else (void) fprintf(stderr, "strftime format \"%s\" ", 152 format); 153 (void) fprintf(stderr, "yields only two digits of years in "); 154 if (warn == IN_SOME) 155 (void) fprintf(stderr, "some locales"); 156 else if (warn == IN_THIS) 157 (void) fprintf(stderr, "the current locale"); 158 else (void) fprintf(stderr, "all locales"); 159 (void) fprintf(stderr, "\n"); 160 } 161 #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */ 162 if (p == s + maxsize) { 163 if (maxsize > 0) 164 s[maxsize - 1] = '\0'; 165 return 0; 166 } 167 *p = '\0'; 168 return p - s; 169 } 170 171 static char * 172 _fmt(format, t, pt, ptlim, warnp) 173 const char * format; 174 const struct tm * const t; 175 char * pt; 176 const char * const ptlim; 177 int * warnp; 178 { 179 for ( ; *format; ++format) { 180 if (*format == '%') { 181 label: 182 switch (*++format) { 183 case '\0': 184 --format; 185 break; 186 case 'A': 187 pt = _add((t->tm_wday < 0 || 188 t->tm_wday >= DAYSPERWEEK) ? 189 "?" : Locale->weekday[t->tm_wday], 190 pt, ptlim); 191 continue; 192 case 'a': 193 pt = _add((t->tm_wday < 0 || 194 t->tm_wday >= DAYSPERWEEK) ? 195 "?" : Locale->wday[t->tm_wday], 196 pt, ptlim); 197 continue; 198 case 'B': 199 pt = _add((t->tm_mon < 0 || 200 t->tm_mon >= MONSPERYEAR) ? 201 "?" : Locale->month[t->tm_mon], 202 pt, ptlim); 203 continue; 204 case 'b': 205 case 'h': 206 pt = _add((t->tm_mon < 0 || 207 t->tm_mon >= MONSPERYEAR) ? 208 "?" : Locale->mon[t->tm_mon], 209 pt, ptlim); 210 continue; 211 case 'C': 212 /* 213 ** %C used to do a... 214 ** _fmt("%a %b %e %X %Y", t); 215 ** ...whereas now POSIX 1003.2 calls for 216 ** something completely different. 217 ** (ado, 1993-05-24) 218 */ 219 pt = _conv((t->tm_year + TM_YEAR_BASE) / 100, 220 "%02d", pt, ptlim); 221 continue; 222 case 'c': 223 { 224 int warn2 = IN_SOME; 225 226 pt = _fmt(Locale->c_fmt, t, pt, ptlim, warnp); 227 if (warn2 == IN_ALL) 228 warn2 = IN_THIS; 229 if (warn2 > *warnp) 230 *warnp = warn2; 231 } 232 continue; 233 case 'D': 234 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp); 235 continue; 236 case 'd': 237 pt = _conv(t->tm_mday, "%02d", pt, ptlim); 238 continue; 239 case 'E': 240 case 'O': 241 /* 242 ** POSIX locale extensions, a la 243 ** Arnold Robbins' strftime version 3.0. 244 ** The sequences 245 ** %Ec %EC %Ex %Ey %EY 246 ** %Od %oe %OH %OI %Om %OM 247 ** %OS %Ou %OU %OV %Ow %OW %Oy 248 ** are supposed to provide alternate 249 ** representations. 250 ** (ado, 1993-05-24) 251 */ 252 goto label; 253 case 'e': 254 pt = _conv(t->tm_mday, "%2d", pt, ptlim); 255 continue; 256 case 'H': 257 pt = _conv(t->tm_hour, "%02d", pt, ptlim); 258 continue; 259 case 'I': 260 pt = _conv((t->tm_hour % 12) ? 261 (t->tm_hour % 12) : 12, 262 "%02d", pt, ptlim); 263 continue; 264 case 'j': 265 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); 266 continue; 267 case 'k': 268 /* 269 ** This used to be... 270 ** _conv(t->tm_hour % 12 ? 271 ** t->tm_hour % 12 : 12, 2, ' '); 272 ** ...and has been changed to the below to 273 ** match SunOS 4.1.1 and Arnold Robbins' 274 ** strftime version 3.0. That is, "%k" and 275 ** "%l" have been swapped. 276 ** (ado, 1993-05-24) 277 */ 278 pt = _conv(t->tm_hour, "%2d", pt, ptlim); 279 continue; 280 #ifdef KITCHEN_SINK 281 case 'K': 282 /* 283 ** After all this time, still unclaimed! 284 */ 285 pt = _add("kitchen sink", pt, ptlim); 286 continue; 287 #endif /* defined KITCHEN_SINK */ 288 case 'l': 289 /* 290 ** This used to be... 291 ** _conv(t->tm_hour, 2, ' '); 292 ** ...and has been changed to the below to 293 ** match SunOS 4.1.1 and Arnold Robbin's 294 ** strftime version 3.0. That is, "%k" and 295 ** "%l" have been swapped. 296 ** (ado, 1993-05-24) 297 */ 298 pt = _conv((t->tm_hour % 12) ? 299 (t->tm_hour % 12) : 12, 300 "%2d", pt, ptlim); 301 continue; 302 case 'M': 303 pt = _conv(t->tm_min, "%02d", pt, ptlim); 304 continue; 305 case 'm': 306 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); 307 continue; 308 case 'n': 309 pt = _add("\n", pt, ptlim); 310 continue; 311 case 'p': 312 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? 313 Locale->pm : 314 Locale->am, 315 pt, ptlim); 316 continue; 317 case 'R': 318 pt = _fmt("%H:%M", t, pt, ptlim, warnp); 319 continue; 320 case 'r': 321 pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp); 322 continue; 323 case 'S': 324 pt = _conv(t->tm_sec, "%02d", pt, ptlim); 325 continue; 326 case 's': 327 { 328 struct tm tm; 329 char buf[INT_STRLEN_MAXIMUM( 330 time_t) + 1]; 331 time_t mkt; 332 333 tm = *t; 334 mkt = mktime(&tm); 335 if (TYPE_SIGNED(time_t)) 336 (void) sprintf(buf, "%ld", 337 (long) mkt); 338 else (void) sprintf(buf, "%lu", 339 (unsigned long) mkt); 340 pt = _add(buf, pt, ptlim); 341 } 342 continue; 343 case 'T': 344 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp); 345 continue; 346 case 't': 347 pt = _add("\t", pt, ptlim); 348 continue; 349 case 'U': 350 pt = _conv((t->tm_yday + DAYSPERWEEK - 351 t->tm_wday) / DAYSPERWEEK, 352 "%02d", pt, ptlim); 353 continue; 354 case 'u': 355 /* 356 ** From Arnold Robbins' strftime version 3.0: 357 ** "ISO 8601: Weekday as a decimal number 358 ** [1 (Monday) - 7]" 359 ** (ado, 1993-05-24) 360 */ 361 pt = _conv((t->tm_wday == 0) ? 362 DAYSPERWEEK : t->tm_wday, 363 "%d", pt, ptlim); 364 continue; 365 case 'V': /* ISO 8601 week number */ 366 case 'G': /* ISO 8601 year (four digits) */ 367 case 'g': /* ISO 8601 year (two digits) */ 368 /* 369 ** From Arnold Robbins' strftime version 3.0: "the week number of the 370 ** year (the first Monday as the first day of week 1) as a decimal number 371 ** (01-53)." 372 ** (ado, 1993-05-24) 373 ** 374 ** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn: 375 ** "Week 01 of a year is per definition the first week which has the 376 ** Thursday in this year, which is equivalent to the week which contains 377 ** the fourth day of January. In other words, the first week of a new year 378 ** is the week which has the majority of its days in the new year. Week 01 379 ** might also contain days from the previous year and the week before week 380 ** 01 of a year is the last week (52 or 53) of the previous year even if 381 ** it contains days from the new year. A week starts with Monday (day 1) 382 ** and ends with Sunday (day 7). For example, the first week of the year 383 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 384 ** (ado, 1996-01-02) 385 */ 386 { 387 int year; 388 int yday; 389 int wday; 390 int w; 391 392 year = t->tm_year + TM_YEAR_BASE; 393 yday = t->tm_yday; 394 wday = t->tm_wday; 395 for ( ; ; ) { 396 int len; 397 int bot; 398 int top; 399 400 len = isleap(year) ? 401 DAYSPERLYEAR : 402 DAYSPERNYEAR; 403 /* 404 ** What yday (-3 ... 3) does 405 ** the ISO year begin on? 406 */ 407 bot = ((yday + 11 - wday) % 408 DAYSPERWEEK) - 3; 409 /* 410 ** What yday does the NEXT 411 ** ISO year begin on? 412 */ 413 top = bot - 414 (len % DAYSPERWEEK); 415 if (top < -3) 416 top += DAYSPERWEEK; 417 top += len; 418 if (yday >= top) { 419 ++year; 420 w = 1; 421 break; 422 } 423 if (yday >= bot) { 424 w = 1 + ((yday - bot) / 425 DAYSPERWEEK); 426 break; 427 } 428 --year; 429 yday += isleap(year) ? 430 DAYSPERLYEAR : 431 DAYSPERNYEAR; 432 } 433 #ifdef XPG4_1994_04_09 434 if ((w == 52 435 && t->tm_mon == TM_JANUARY) 436 || (w == 1 437 && t->tm_mon == TM_DECEMBER)) 438 w = 53; 439 #endif /* defined XPG4_1994_04_09 */ 440 if (*format == 'V') 441 pt = _conv(w, "%02d", 442 pt, ptlim); 443 else if (*format == 'g') { 444 *warnp = IN_ALL; 445 pt = _conv(year % 100, "%02d", 446 pt, ptlim); 447 } else pt = _conv(year, "%04d", 448 pt, ptlim); 449 } 450 continue; 451 case 'v': 452 /* 453 ** From Arnold Robbins' strftime version 3.0: 454 ** "date as dd-bbb-YYYY" 455 ** (ado, 1993-05-24) 456 */ 457 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp); 458 continue; 459 case 'W': 460 pt = _conv((t->tm_yday + DAYSPERWEEK - 461 (t->tm_wday ? 462 (t->tm_wday - 1) : 463 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 464 "%02d", pt, ptlim); 465 continue; 466 case 'w': 467 pt = _conv(t->tm_wday, "%d", pt, ptlim); 468 continue; 469 case 'X': 470 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); 471 continue; 472 case 'x': 473 { 474 int warn2 = IN_SOME; 475 476 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); 477 if (warn2 == IN_ALL) 478 warn2 = IN_THIS; 479 if (warn2 > *warnp) 480 *warnp = warn2; 481 } 482 continue; 483 case 'y': 484 *warnp = IN_ALL; 485 pt = _conv((t->tm_year + TM_YEAR_BASE) % 100, 486 "%02d", pt, ptlim); 487 continue; 488 case 'Y': 489 pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d", 490 pt, ptlim); 491 continue; 492 case 'Z': 493 #ifdef TM_ZONE 494 if (t->TM_ZONE != NULL) 495 pt = _add(t->TM_ZONE, pt, ptlim); 496 else 497 #endif /* defined TM_ZONE */ 498 if (t->tm_isdst == 0 || t->tm_isdst == 1) { 499 pt = _add(tzname[t->tm_isdst], 500 pt, ptlim); 501 } else pt = _add("?", pt, ptlim); 502 continue; 503 case '+': 504 pt = _fmt(Locale->date_fmt, t, pt, ptlim, 505 warnp); 506 continue; 507 case '%': 508 /* 509 * X311J/88-090 (4.12.3.5): if conversion char is 510 * undefined, behavior is undefined. Print out the 511 * character itself as printf(3) also does. 512 */ 513 default: 514 break; 515 } 516 } 517 if (pt == ptlim) 518 break; 519 *pt++ = *format; 520 } 521 return pt; 522 } 523 524 static char * 525 _conv(n, format, pt, ptlim) 526 const int n; 527 const char * const format; 528 char * const pt; 529 const char * const ptlim; 530 { 531 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 532 533 (void) sprintf(buf, format, n); 534 return _add(buf, pt, ptlim); 535 } 536 537 static char * 538 _add(str, pt, ptlim) 539 const char * str; 540 char * pt; 541 const char * const ptlim; 542 { 543 while (pt < ptlim && (*pt = *str++) != '\0') 544 ++pt; 545 return pt; 546 } 547 548 #ifdef LOCALE_HOME 549 static struct lc_time_T * 550 _loc P((void)) 551 { 552 static const char locale_home[] = LOCALE_HOME; 553 static const char lc_time[] = "LC_TIME"; 554 static char * locale_buf; 555 static char locale_buf_C[] = "C"; 556 557 int fd; 558 int oldsun; /* "...ain't got nothin' to do..." */ 559 char * lbuf; 560 char * nlbuf; 561 char * name; 562 char * p; 563 const char ** ap; 564 const char * plim; 565 char filename[FILENAME_MAX]; 566 struct stat st; 567 size_t namesize; 568 size_t bufsize; 569 570 /* 571 ** Use localebuf.mon[0] to signal whether locale is already set up. 572 */ 573 if (localebuf.mon[0]) 574 return &localebuf; 575 name = setlocale(LC_TIME, (char *) NULL); 576 if (name == NULL || *name == '\0') 577 goto no_locale; 578 /* 579 ** If the locale name is the same as our cache, use the cache. 580 */ 581 lbuf = locale_buf; 582 if (lbuf != NULL && strcmp(name, lbuf) == 0) { 583 p = lbuf; 584 for (ap = (const char **) &localebuf; 585 ap < (const char **) (&localebuf + 1); 586 ++ap) 587 *ap = p += strlen(p) + 1; 588 return &localebuf; 589 } 590 /* 591 ** Slurp the locale file into the cache. 592 */ 593 namesize = strlen(name) + 1; 594 if (sizeof(filename) < 595 sizeof(locale_home) + namesize + sizeof(lc_time)) 596 goto no_locale; 597 oldsun = 0; 598 (void) sprintf(filename, "%s/%s/%s", locale_home, name, lc_time); 599 fd = open(filename, O_RDONLY); 600 if (fd < 0) { 601 /* 602 ** Old Sun systems have a different naming and data convention. 603 */ 604 oldsun = 1; 605 (void) sprintf(filename, "%s/%s/%s", locale_home, 606 lc_time, name); 607 fd = open(filename, O_RDONLY); 608 if (fd < 0) 609 goto no_locale; 610 } 611 if (fstat(fd, &st) != 0) 612 goto bad_locale; 613 if (st.st_size <= 0) 614 goto bad_locale; 615 bufsize = namesize + st.st_size; 616 locale_buf = NULL; 617 nlbuf = (lbuf == NULL || lbuf == locale_buf_C) ? 618 malloc(bufsize) : realloc(lbuf, bufsize); 619 if (nlbuf == NULL) { 620 if (lbuf) 621 free(lbuf); 622 lbuf = NULL; 623 goto bad_locale; 624 } 625 lbuf = nlbuf; 626 (void) strcpy(lbuf, name); 627 p = lbuf + namesize; 628 plim = p + st.st_size; 629 if (read(fd, p, (size_t) st.st_size) != st.st_size) 630 goto bad_lbuf; 631 if (close(fd) != 0) 632 goto bad_lbuf; 633 /* 634 ** Parse the locale file into localebuf. 635 */ 636 if (plim[-1] != '\n') 637 goto bad_lbuf; 638 for (ap = (const char **) &localebuf; 639 ap < (const char **) (&localebuf + 1); 640 ++ap) { 641 if (p == plim) 642 goto bad_lbuf; 643 *ap = p; 644 while (*p != '\n') 645 ++p; 646 *p++ = '\0'; 647 } 648 if (oldsun) { 649 /* 650 ** SunOS 4 used an obsolescent format; see localdtconv(3). 651 ** c_fmt had the ``short format for dates and times together'' 652 ** (SunOS 4 date, "%a %b %e %T %Z %Y" in the C locale); 653 ** date_fmt had the ``long format for dates'' 654 ** (SunOS 4 strftime %C, "%A, %B %e, %Y" in the C locale). 655 ** Discard the latter in favor of the former. 656 */ 657 localebuf.date_fmt = localebuf.c_fmt; 658 } 659 /* 660 ** Record the successful parse in the cache. 661 */ 662 locale_buf = lbuf; 663 664 return &localebuf; 665 666 bad_lbuf: 667 free(lbuf); 668 bad_locale: 669 (void) close(fd); 670 no_locale: 671 localebuf = C_time_locale; 672 locale_buf = locale_buf_C; 673 return &localebuf; 674 } 675 #endif /* defined LOCALE_HOME */ 676