1 /* $NetBSD: strftime.c,v 1.43 2019/04/04 19:27:28 christos Exp $ */ 2 3 /* Convert a broken-down timestamp to a string. */ 4 5 /* Copyright 1989 The Regents of the University of California. 6 All rights reserved. 7 8 Redistribution and use in source and binary forms, with or without 9 modification, are permitted provided that the following conditions 10 are met: 11 1. Redistributions of source code must retain the above copyright 12 notice, this list of conditions and the following disclaimer. 13 2. Redistributions in binary form must reproduce the above copyright 14 notice, this list of conditions and the following disclaimer in the 15 documentation and/or other materials provided with the distribution. 16 3. Neither the name of the University nor the names of its contributors 17 may be used to endorse or promote products derived from this software 18 without specific prior written permission. 19 20 THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND 21 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 SUCH DAMAGE. */ 31 32 #include <sys/cdefs.h> 33 #if defined(LIBC_SCCS) && !defined(lint) 34 #if 0 35 static char elsieid[] = "@(#)strftime.c 7.64"; 36 static char elsieid[] = "@(#)strftime.c 8.3"; 37 #else 38 __RCSID("$NetBSD: strftime.c,v 1.43 2019/04/04 19:27:28 christos Exp $"); 39 #endif 40 #endif /* LIBC_SCCS and not lint */ 41 42 #include "namespace.h" 43 44 #include <stddef.h> 45 #include <assert.h> 46 #include <locale.h> 47 #include "setlocale_local.h" 48 49 /* 50 ** Based on the UCB version with the copyright notice appearing above. 51 ** 52 ** This is ANSIish only when "multibyte character == plain character". 53 */ 54 55 #include "private.h" 56 57 /* 58 ** We don't use these extensions in strftime operation even when 59 ** supported by the local tzcode configuration. A strictly 60 ** conforming C application may leave them in undefined state. 61 */ 62 63 #ifdef _LIBC 64 #undef TM_ZONE 65 #undef TM_GMTOFF 66 #endif 67 68 #include <fcntl.h> 69 #include <locale.h> 70 #include <stdio.h> 71 72 #ifndef DEPRECATE_TWO_DIGIT_YEARS 73 # define DEPRECATE_TWO_DIGIT_YEARS false 74 #endif 75 76 #ifdef __weak_alias 77 __weak_alias(strftime_l, _strftime_l) 78 __weak_alias(strftime_lz, _strftime_lz) 79 __weak_alias(strftime_z, _strftime_z) 80 #endif 81 82 #include "sys/localedef.h" 83 #define _TIME_LOCALE(loc) \ 84 ((_TimeLocale *)((loc)->part_impl[(size_t)LC_TIME])) 85 #define c_fmt d_t_fmt 86 87 enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL }; 88 89 static char * _add(const char *, char *, const char *); 90 static char * _conv(int, const char *, char *, const char *); 91 static char * _fmt(const timezone_t, const char *, const struct tm *, char *, 92 const char *, enum warn *, locale_t); 93 static char * _yconv(int, int, bool, bool, char *, const char *); 94 95 #ifndef YEAR_2000_NAME 96 #define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" 97 #endif /* !defined YEAR_2000_NAME */ 98 99 size_t 100 strftime_z(const timezone_t sp, char * __restrict s, size_t maxsize, 101 const char * __restrict format, const struct tm * __restrict t) 102 { 103 return strftime_lz(sp, s, maxsize, format, t, _current_locale()); 104 } 105 106 #if HAVE_STRFTIME_L 107 size_t 108 strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t, 109 locale_t locale) 110 { 111 /* Just call strftime, as only the C locale is supported. */ 112 return strftime(s, maxsize, format, t); 113 } 114 #endif 115 116 size_t 117 strftime_lz(const timezone_t sp, char *const s, const size_t maxsize, 118 const char *const format, const struct tm *const t, locale_t loc) 119 { 120 char * p; 121 enum warn warn = IN_NONE; 122 123 p = _fmt(sp, format, t, s, s + maxsize, &warn, loc); 124 if (/*CONSTCOND*/DEPRECATE_TWO_DIGIT_YEARS 125 && warn != IN_NONE && getenv(YEAR_2000_NAME)) { 126 (void) fprintf(stderr, "\n"); 127 (void) fprintf(stderr, "strftime format \"%s\" ", format); 128 (void) fprintf(stderr, "yields only two digits of years in "); 129 if (warn == IN_SOME) 130 (void) fprintf(stderr, "some locales"); 131 else if (warn == IN_THIS) 132 (void) fprintf(stderr, "the current locale"); 133 else (void) fprintf(stderr, "all locales"); 134 (void) fprintf(stderr, "\n"); 135 } 136 if (p == s + maxsize) 137 return 0; 138 *p = '\0'; 139 return p - s; 140 } 141 142 static char * 143 _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt, 144 const char *ptlim, enum warn *warnp, locale_t loc) 145 { 146 for ( ; *format; ++format) { 147 if (*format == '%') { 148 label: 149 switch (*++format) { 150 case '\0': 151 --format; 152 break; 153 case 'A': 154 pt = _add((t->tm_wday < 0 || 155 t->tm_wday >= DAYSPERWEEK) ? 156 "?" : _TIME_LOCALE(loc)->day[t->tm_wday], 157 pt, ptlim); 158 continue; 159 case 'a': 160 pt = _add((t->tm_wday < 0 || 161 t->tm_wday >= DAYSPERWEEK) ? 162 "?" : _TIME_LOCALE(loc)->abday[t->tm_wday], 163 pt, ptlim); 164 continue; 165 case 'B': 166 pt = _add((t->tm_mon < 0 || 167 t->tm_mon >= MONSPERYEAR) ? 168 "?" : _TIME_LOCALE(loc)->mon[t->tm_mon], 169 pt, ptlim); 170 continue; 171 case 'b': 172 case 'h': 173 pt = _add((t->tm_mon < 0 || 174 t->tm_mon >= MONSPERYEAR) ? 175 "?" : _TIME_LOCALE(loc)->abmon[t->tm_mon], 176 pt, ptlim); 177 continue; 178 case 'C': 179 /* 180 ** %C used to do a... 181 ** _fmt("%a %b %e %X %Y", t); 182 ** ...whereas now POSIX 1003.2 calls for 183 ** something completely different. 184 ** (ado, 1993-05-24) 185 */ 186 pt = _yconv(t->tm_year, TM_YEAR_BASE, 187 true, false, pt, ptlim); 188 continue; 189 case 'c': 190 { 191 enum warn warn2 = IN_SOME; 192 193 pt = _fmt(sp, _TIME_LOCALE(loc)->c_fmt, t, pt, 194 ptlim, &warn2, loc); 195 if (warn2 == IN_ALL) 196 warn2 = IN_THIS; 197 if (warn2 > *warnp) 198 *warnp = warn2; 199 } 200 continue; 201 case 'D': 202 pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp, 203 loc); 204 continue; 205 case 'd': 206 pt = _conv(t->tm_mday, "%02d", pt, ptlim); 207 continue; 208 case 'E': 209 case 'O': 210 /* 211 ** Locale modifiers of C99 and later. 212 ** The sequences 213 ** %Ec %EC %Ex %EX %Ey %EY 214 ** %Od %oe %OH %OI %Om %OM 215 ** %OS %Ou %OU %OV %Ow %OW %Oy 216 ** are supposed to provide alternative 217 ** representations. 218 */ 219 goto label; 220 case 'e': 221 pt = _conv(t->tm_mday, "%2d", pt, ptlim); 222 continue; 223 case 'F': 224 pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp, 225 loc); 226 continue; 227 case 'H': 228 pt = _conv(t->tm_hour, "%02d", pt, ptlim); 229 continue; 230 case 'I': 231 pt = _conv((t->tm_hour % 12) ? 232 (t->tm_hour % 12) : 12, 233 "%02d", pt, ptlim); 234 continue; 235 case 'j': 236 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); 237 continue; 238 case 'k': 239 /* 240 ** This used to be... 241 ** _conv(t->tm_hour % 12 ? 242 ** t->tm_hour % 12 : 12, 2, ' '); 243 ** ...and has been changed to the below to 244 ** match SunOS 4.1.1 and Arnold Robbins' 245 ** strftime version 3.0. That is, "%k" and 246 ** "%l" have been swapped. 247 ** (ado, 1993-05-24) 248 */ 249 pt = _conv(t->tm_hour, "%2d", pt, ptlim); 250 continue; 251 #ifdef KITCHEN_SINK 252 case 'K': 253 /* 254 ** After all this time, still unclaimed! 255 */ 256 pt = _add("kitchen sink", pt, ptlim); 257 continue; 258 #endif /* defined KITCHEN_SINK */ 259 case 'l': 260 /* 261 ** This used to be... 262 ** _conv(t->tm_hour, 2, ' '); 263 ** ...and has been changed to the below to 264 ** match SunOS 4.1.1 and Arnold Robbin's 265 ** strftime version 3.0. That is, "%k" and 266 ** "%l" have been swapped. 267 ** (ado, 1993-05-24) 268 */ 269 pt = _conv((t->tm_hour % 12) ? 270 (t->tm_hour % 12) : 12, 271 "%2d", pt, ptlim); 272 continue; 273 case 'M': 274 pt = _conv(t->tm_min, "%02d", pt, ptlim); 275 continue; 276 case 'm': 277 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); 278 continue; 279 case 'n': 280 pt = _add("\n", pt, ptlim); 281 continue; 282 case 'p': 283 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? 284 _TIME_LOCALE(loc)->am_pm[1] : 285 _TIME_LOCALE(loc)->am_pm[0], 286 pt, ptlim); 287 continue; 288 case 'R': 289 pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp, 290 loc); 291 continue; 292 case 'r': 293 pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt_ampm, t, 294 pt, ptlim, warnp, loc); 295 continue; 296 case 'S': 297 pt = _conv(t->tm_sec, "%02d", pt, ptlim); 298 continue; 299 case 's': 300 { 301 struct tm tm; 302 char buf[INT_STRLEN_MAXIMUM( 303 time_t) + 1]; 304 time_t mkt; 305 306 tm = *t; 307 mkt = mktime_z(sp, &tm); 308 /* CONSTCOND */ 309 if (TYPE_SIGNED(time_t)) 310 (void)snprintf(buf, sizeof(buf), 311 "%jd", (intmax_t) mkt); 312 else (void)snprintf(buf, sizeof(buf), 313 "%ju", (uintmax_t) mkt); 314 pt = _add(buf, pt, ptlim); 315 } 316 continue; 317 case 'T': 318 pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp, 319 loc); 320 continue; 321 case 't': 322 pt = _add("\t", pt, ptlim); 323 continue; 324 case 'U': 325 pt = _conv((t->tm_yday + DAYSPERWEEK - 326 t->tm_wday) / DAYSPERWEEK, 327 "%02d", pt, ptlim); 328 continue; 329 case 'u': 330 /* 331 ** From Arnold Robbins' strftime version 3.0: 332 ** "ISO 8601: Weekday as a decimal number 333 ** [1 (Monday) - 7]" 334 ** (ado, 1993-05-24) 335 */ 336 pt = _conv((t->tm_wday == 0) ? 337 DAYSPERWEEK : t->tm_wday, 338 "%d", pt, ptlim); 339 continue; 340 case 'V': /* ISO 8601 week number */ 341 case 'G': /* ISO 8601 year (four digits) */ 342 case 'g': /* ISO 8601 year (two digits) */ 343 /* 344 ** From Arnold Robbins' strftime version 3.0: "the week number of the 345 ** year (the first Monday as the first day of week 1) as a decimal number 346 ** (01-53)." 347 ** (ado, 1993-05-24) 348 ** 349 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn: 350 ** "Week 01 of a year is per definition the first week which has the 351 ** Thursday in this year, which is equivalent to the week which contains 352 ** the fourth day of January. In other words, the first week of a new year 353 ** is the week which has the majority of its days in the new year. Week 01 354 ** might also contain days from the previous year and the week before week 355 ** 01 of a year is the last week (52 or 53) of the previous year even if 356 ** it contains days from the new year. A week starts with Monday (day 1) 357 ** and ends with Sunday (day 7). For example, the first week of the year 358 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 359 ** (ado, 1996-01-02) 360 */ 361 { 362 int year; 363 int base; 364 int yday; 365 int wday; 366 int w; 367 368 year = t->tm_year; 369 base = TM_YEAR_BASE; 370 yday = t->tm_yday; 371 wday = t->tm_wday; 372 for ( ; ; ) { 373 int len; 374 int bot; 375 int top; 376 377 len = isleap_sum(year, base) ? 378 DAYSPERLYEAR : 379 DAYSPERNYEAR; 380 /* 381 ** What yday (-3 ... 3) does 382 ** the ISO year begin on? 383 */ 384 bot = ((yday + 11 - wday) % 385 DAYSPERWEEK) - 3; 386 /* 387 ** What yday does the NEXT 388 ** ISO year begin on? 389 */ 390 top = bot - 391 (len % DAYSPERWEEK); 392 if (top < -3) 393 top += DAYSPERWEEK; 394 top += len; 395 if (yday >= top) { 396 ++base; 397 w = 1; 398 break; 399 } 400 if (yday >= bot) { 401 w = 1 + ((yday - bot) / 402 DAYSPERWEEK); 403 break; 404 } 405 --base; 406 yday += isleap_sum(year, base) ? 407 DAYSPERLYEAR : 408 DAYSPERNYEAR; 409 } 410 #ifdef XPG4_1994_04_09 411 if ((w == 52 && 412 t->tm_mon == TM_JANUARY) || 413 (w == 1 && 414 t->tm_mon == TM_DECEMBER)) 415 w = 53; 416 #endif /* defined XPG4_1994_04_09 */ 417 if (*format == 'V') 418 pt = _conv(w, "%02d", 419 pt, ptlim); 420 else if (*format == 'g') { 421 *warnp = IN_ALL; 422 pt = _yconv(year, base, 423 false, true, 424 pt, ptlim); 425 } else pt = _yconv(year, base, 426 true, true, 427 pt, ptlim); 428 } 429 continue; 430 case 'v': 431 /* 432 ** From Arnold Robbins' strftime version 3.0: 433 ** "date as dd-bbb-YYYY" 434 ** (ado, 1993-05-24) 435 */ 436 pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp, 437 loc); 438 continue; 439 case 'W': 440 pt = _conv((t->tm_yday + DAYSPERWEEK - 441 (t->tm_wday ? 442 (t->tm_wday - 1) : 443 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 444 "%02d", pt, ptlim); 445 continue; 446 case 'w': 447 pt = _conv(t->tm_wday, "%d", pt, ptlim); 448 continue; 449 case 'X': 450 pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt, t, pt, 451 ptlim, warnp, loc); 452 continue; 453 case 'x': 454 { 455 enum warn warn2 = IN_SOME; 456 457 pt = _fmt(sp, _TIME_LOCALE(loc)->d_fmt, t, pt, 458 ptlim, &warn2, loc); 459 if (warn2 == IN_ALL) 460 warn2 = IN_THIS; 461 if (warn2 > *warnp) 462 *warnp = warn2; 463 } 464 continue; 465 case 'y': 466 *warnp = IN_ALL; 467 pt = _yconv(t->tm_year, TM_YEAR_BASE, 468 false, true, 469 pt, ptlim); 470 continue; 471 case 'Y': 472 pt = _yconv(t->tm_year, TM_YEAR_BASE, 473 true, true, 474 pt, ptlim); 475 continue; 476 case 'Z': 477 #ifdef TM_ZONE 478 pt = _add(t->TM_ZONE, pt, ptlim); 479 #elif HAVE_TZNAME 480 if (t->tm_isdst >= 0) 481 pt = _add(tzgetname(sp, t->tm_isdst), 482 pt, ptlim); 483 #endif 484 /* 485 ** C99 and later say that %Z must be 486 ** replaced by the empty string if the 487 ** time zone abbreviation is not 488 ** determinable. 489 */ 490 continue; 491 case 'z': 492 #if defined TM_GMTOFF || USG_COMPAT || defined ALTZONE 493 { 494 long diff; 495 char const * sign; 496 bool negative; 497 498 if (t->tm_isdst < 0) 499 continue; 500 # ifdef TM_GMTOFF 501 diff = (int)t->TM_GMTOFF; 502 # else 503 /* 504 ** C99 and later say that the UT offset must 505 ** be computed by looking only at 506 ** tm_isdst. This requirement is 507 ** incorrect, since it means the code 508 ** must rely on magic (in this case 509 ** altzone and timezone), and the 510 ** magic might not have the correct 511 ** offset. Doing things correctly is 512 ** tricky and requires disobeying the standard; 513 ** see GNU C strftime for details. 514 ** For now, punt and conform to the 515 ** standard, even though it's incorrect. 516 ** 517 ** C99 and later say that %z must be replaced by 518 ** the empty string if the time zone is not 519 ** determinable, so output nothing if the 520 ** appropriate variables are not available. 521 */ 522 # ifndef STD_INSPIRED 523 if (t->tm_isdst == 0) 524 # if USG_COMPAT 525 diff = -timezone; 526 # else 527 continue; 528 # endif 529 else 530 # ifdef ALTZONE 531 diff = -altzone; 532 # else 533 continue; 534 # endif 535 # else 536 { 537 struct tm tmp; 538 time_t lct, gct; 539 540 /* 541 ** Get calendar time from t 542 ** being treated as local. 543 */ 544 tmp = *t; /* mktime discards const */ 545 lct = mktime_z(sp, &tmp); 546 547 if (lct == (time_t)-1) 548 continue; 549 550 /* 551 ** Get calendar time from t 552 ** being treated as GMT. 553 **/ 554 tmp = *t; /* mktime discards const */ 555 gct = timegm(&tmp); 556 557 if (gct == (time_t)-1) 558 continue; 559 560 /* LINTED difference will fit int */ 561 diff = (intmax_t)gct - (intmax_t)lct; 562 } 563 # endif 564 # endif 565 negative = diff < 0; 566 if (diff == 0) { 567 #ifdef TM_ZONE 568 negative = t->TM_ZONE[0] == '-'; 569 #else 570 negative = t->tm_isdst < 0; 571 # if HAVE_TZNAME 572 if (tzname[t->tm_isdst != 0][0] == '-') 573 negative = true; 574 # endif 575 #endif 576 } 577 if (negative) { 578 sign = "-"; 579 diff = -diff; 580 } else sign = "+"; 581 pt = _add(sign, pt, ptlim); 582 diff /= SECSPERMIN; 583 diff = (diff / MINSPERHOUR) * 100 + 584 (diff % MINSPERHOUR); 585 _DIAGASSERT(__type_fit(int, diff)); 586 pt = _conv((int)diff, "%04d", pt, ptlim); 587 } 588 #endif 589 continue; 590 #if 0 591 case '+': 592 pt = _fmt(sp, _TIME_LOCALE(loc)->date_fmt, t, 593 pt, ptlim, warnp, loc); 594 continue; 595 #endif 596 case '%': 597 /* 598 ** X311J/88-090 (4.12.3.5): if conversion char is 599 ** undefined, behavior is undefined. Print out the 600 ** character itself as printf(3) also does. 601 */ 602 default: 603 break; 604 } 605 } 606 if (pt == ptlim) 607 break; 608 *pt++ = *format; 609 } 610 return pt; 611 } 612 613 size_t 614 strftime(char *s, size_t maxsize, const char *format, const struct tm *t) 615 { 616 size_t r; 617 618 rwlock_wrlock(&__lcl_lock); 619 tzset_unlocked(); 620 r = strftime_z(__lclptr, s, maxsize, format, t); 621 rwlock_unlock(&__lcl_lock); 622 623 return r; 624 } 625 626 size_t 627 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format, 628 const struct tm * __restrict t, locale_t loc) 629 { 630 size_t r; 631 632 rwlock_wrlock(&__lcl_lock); 633 tzset_unlocked(); 634 r = strftime_lz(__lclptr, s, maxsize, format, t, loc); 635 rwlock_unlock(&__lcl_lock); 636 637 return r; 638 } 639 640 static char * 641 _conv(int n, const char *format, char *pt, const char *ptlim) 642 { 643 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 644 645 (void) snprintf(buf, sizeof(buf), format, n); 646 return _add(buf, pt, ptlim); 647 } 648 649 static char * 650 _add(const char *str, char *pt, const char *ptlim) 651 { 652 while (pt < ptlim && (*pt = *str++) != '\0') 653 ++pt; 654 return pt; 655 } 656 657 /* 658 ** POSIX and the C Standard are unclear or inconsistent about 659 ** what %C and %y do if the year is negative or exceeds 9999. 660 ** Use the convention that %C concatenated with %y yields the 661 ** same output as %Y, and that %Y contains at least 4 bytes, 662 ** with more only if necessary. 663 */ 664 665 static char * 666 _yconv(int a, int b, bool convert_top, bool convert_yy, 667 char *pt, const char * ptlim) 668 { 669 int lead; 670 int trail; 671 672 #define DIVISOR 100 673 trail = a % DIVISOR + b % DIVISOR; 674 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; 675 trail %= DIVISOR; 676 if (trail < 0 && lead > 0) { 677 trail += DIVISOR; 678 --lead; 679 } else if (lead < 0 && trail > 0) { 680 trail -= DIVISOR; 681 ++lead; 682 } 683 if (convert_top) { 684 if (lead == 0 && trail < 0) 685 pt = _add("-0", pt, ptlim); 686 else pt = _conv(lead, "%02d", pt, ptlim); 687 } 688 if (convert_yy) 689 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); 690 return pt; 691 } 692