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