1 /* $NetBSD: strftime.c,v 1.50 2022/08/16 10:56:21 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.50 2022/08/16 10:56:21 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 *, locale_t); 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 *, locale_t); 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 #define IN_NONE 0 100 #define IN_SOME 1 101 #define IN_THIS 2 102 #define IN_ALL 3 103 104 #define PAD_DEFAULT 0 105 #define PAD_LESS 1 106 #define PAD_SPACE 2 107 #define PAD_ZERO 3 108 109 static const char fmt_padding[][4][5] = { 110 /* DEFAULT, LESS, SPACE, ZERO */ 111 #define PAD_FMT_MONTHDAY 0 112 #define PAD_FMT_HMS 0 113 #define PAD_FMT_CENTURY 0 114 #define PAD_FMT_SHORTYEAR 0 115 #define PAD_FMT_MONTH 0 116 #define PAD_FMT_WEEKOFYEAR 0 117 #define PAD_FMT_DAYOFMONTH 0 118 { "%02d", "%d", "%2d", "%02d" }, 119 #define PAD_FMT_SDAYOFMONTH 1 120 #define PAD_FMT_SHMS 1 121 { "%2d", "%d", "%2d", "%02d" }, 122 #define PAD_FMT_DAYOFYEAR 2 123 { "%03d", "%d", "%3d", "%03d" }, 124 #define PAD_FMT_YEAR 3 125 { "%04d", "%d", "%4d", "%04d" } 126 }; 127 128 size_t 129 strftime_z(const timezone_t sp, char * __restrict s, size_t maxsize, 130 const char * __restrict format, const struct tm * __restrict t) 131 { 132 return strftime_lz(sp, s, maxsize, format, t, _current_locale()); 133 } 134 135 #if HAVE_STRFTIME_L 136 size_t 137 strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t, 138 locale_t locale) 139 { 140 /* Just call strftime, as only the C locale is supported. */ 141 return strftime(s, maxsize, format, t); 142 } 143 #endif 144 145 size_t 146 strftime_lz(const timezone_t sp, char *const s, const size_t maxsize, 147 const char *const format, const struct tm *const t, locale_t loc) 148 { 149 char * p; 150 int saved_errno = errno; 151 enum warn warn = IN_NONE; 152 153 p = _fmt(sp, format, t, s, s + maxsize, &warn, loc); 154 if (!p) { 155 errno = EOVERFLOW; 156 return 0; 157 } 158 if (/*CONSTCOND*/DEPRECATE_TWO_DIGIT_YEARS 159 && warn != IN_NONE && getenv(YEAR_2000_NAME)) { 160 (void) fprintf(stderr, "\n"); 161 (void) fprintf(stderr, "strftime format \"%s\" ", format); 162 (void) fprintf(stderr, "yields only two digits of years in "); 163 if (warn == IN_SOME) 164 (void) fprintf(stderr, "some locales"); 165 else if (warn == IN_THIS) 166 (void) fprintf(stderr, "the current locale"); 167 else (void) fprintf(stderr, "all locales"); 168 (void) fprintf(stderr, "\n"); 169 } 170 if (p == s + maxsize) { 171 errno = ERANGE; 172 return 0; 173 } 174 *p = '\0'; 175 errno = saved_errno; 176 return p - s; 177 } 178 179 static char * 180 _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt, 181 const char *ptlim, enum warn *warnp, locale_t loc) 182 { 183 int Ealternative, Oalternative, PadIndex; 184 _TimeLocale *tptr = _TIME_LOCALE(loc); 185 186 for ( ; *format; ++format) { 187 if (*format == '%') { 188 Ealternative = 0; 189 Oalternative = 0; 190 PadIndex = PAD_DEFAULT; 191 label: 192 switch (*++format) { 193 case '\0': 194 --format; 195 break; 196 case 'A': 197 pt = _add((t->tm_wday < 0 || 198 t->tm_wday >= DAYSPERWEEK) ? 199 "?" : tptr->day[t->tm_wday], 200 pt, ptlim); 201 continue; 202 case 'a': 203 pt = _add((t->tm_wday < 0 || 204 t->tm_wday >= DAYSPERWEEK) ? 205 "?" : tptr->abday[t->tm_wday], 206 pt, ptlim); 207 continue; 208 case 'B': 209 pt = _add((t->tm_mon < 0 || 210 t->tm_mon >= MONSPERYEAR) ? 211 "?" : 212 /* no alt_month in _TimeLocale */ 213 (Oalternative ? tptr->mon/*alt_month*/: 214 tptr->mon)[t->tm_mon], 215 pt, ptlim); 216 continue; 217 case 'b': 218 case 'h': 219 pt = _add((t->tm_mon < 0 || 220 t->tm_mon >= MONSPERYEAR) ? 221 "?" : tptr->abmon[t->tm_mon], 222 pt, ptlim); 223 continue; 224 case 'C': 225 /* 226 ** %C used to do a... 227 ** _fmt("%a %b %e %X %Y", t); 228 ** ...whereas now POSIX 1003.2 calls for 229 ** something completely different. 230 ** (ado, 1993-05-24) 231 */ 232 pt = _yconv(t->tm_year, TM_YEAR_BASE, 233 true, false, pt, ptlim, loc); 234 continue; 235 case 'c': 236 { 237 enum warn warn2 = IN_SOME; 238 239 pt = _fmt(sp, tptr->c_fmt, t, pt, 240 ptlim, &warn2, loc); 241 if (warn2 == IN_ALL) 242 warn2 = IN_THIS; 243 if (warn2 > *warnp) 244 *warnp = warn2; 245 } 246 continue; 247 case 'D': 248 pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp, 249 loc); 250 continue; 251 case 'd': 252 pt = _conv(t->tm_mday, 253 fmt_padding[PAD_FMT_DAYOFMONTH][PadIndex], 254 pt, ptlim, loc); 255 continue; 256 case 'E': 257 if (Ealternative || Oalternative) 258 break; 259 Ealternative++; 260 goto label; 261 case 'O': 262 /* 263 ** Locale modifiers of C99 and later. 264 ** The sequences 265 ** %Ec %EC %Ex %EX %Ey %EY 266 ** %Od %oe %OH %OI %Om %OM 267 ** %OS %Ou %OU %OV %Ow %OW %Oy 268 ** are supposed to provide alternative 269 ** representations. 270 */ 271 if (Ealternative || Oalternative) 272 break; 273 Oalternative++; 274 goto label; 275 case 'e': 276 pt = _conv(t->tm_mday, 277 fmt_padding[PAD_FMT_SDAYOFMONTH][PadIndex], 278 pt, ptlim, loc); 279 continue; 280 case 'F': 281 pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp, 282 loc); 283 continue; 284 case 'H': 285 pt = _conv(t->tm_hour, 286 fmt_padding[PAD_FMT_HMS][PadIndex], 287 pt, ptlim, loc); 288 continue; 289 case 'I': 290 pt = _conv((t->tm_hour % 12) ? 291 (t->tm_hour % 12) : 12, 292 fmt_padding[PAD_FMT_HMS][PadIndex], 293 pt, ptlim, loc); 294 continue; 295 case 'j': 296 pt = _conv(t->tm_yday + 1, 297 fmt_padding[PAD_FMT_DAYOFYEAR][PadIndex], 298 pt, ptlim, loc); 299 continue; 300 case 'k': 301 /* 302 ** This used to be... 303 ** _conv(t->tm_hour % 12 ? 304 ** t->tm_hour % 12 : 12, 2, ' '); 305 ** ...and has been changed to the below to 306 ** match SunOS 4.1.1 and Arnold Robbins' 307 ** strftime version 3.0. That is, "%k" and 308 ** "%l" have been swapped. 309 ** (ado, 1993-05-24) 310 */ 311 pt = _conv(t->tm_hour, 312 fmt_padding[PAD_FMT_SHMS][PadIndex], 313 pt, ptlim, loc); 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 fmt_padding[PAD_FMT_SHMS][PadIndex], 336 pt, ptlim, loc); 337 continue; 338 case 'M': 339 pt = _conv(t->tm_min, 340 fmt_padding[PAD_FMT_HMS][PadIndex], 341 pt, ptlim, loc); 342 continue; 343 case 'm': 344 pt = _conv(t->tm_mon + 1, 345 fmt_padding[PAD_FMT_MONTH][PadIndex], 346 pt, ptlim, loc); 347 continue; 348 case 'n': 349 pt = _add("\n", pt, ptlim); 350 continue; 351 case 'p': 352 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? 353 tptr->am_pm[1] : 354 tptr->am_pm[0], 355 pt, ptlim); 356 continue; 357 case 'R': 358 pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp, 359 loc); 360 continue; 361 case 'r': 362 pt = _fmt(sp, tptr->t_fmt_ampm, t, 363 pt, ptlim, warnp, loc); 364 continue; 365 case 'S': 366 pt = _conv(t->tm_sec, 367 fmt_padding[PAD_FMT_HMS][PadIndex], 368 pt, ptlim, loc); 369 continue; 370 case 's': 371 { 372 struct tm tm; 373 char buf[INT_STRLEN_MAXIMUM( 374 time_t) + 1]; 375 time_t mkt; 376 377 tm = *t; 378 mkt = mktime_z(sp, &tm); 379 /* There is no portable, definitive 380 test for whether whether mktime 381 succeeded, so treat (time_t) -1 as 382 the success that it might be. */ 383 /* CONSTCOND */ 384 if (TYPE_SIGNED(time_t)) { 385 intmax_t n = mkt; 386 (void)snprintf(buf, sizeof(buf), 387 "%"PRIdMAX, n); 388 } else { 389 uintmax_t n = mkt; 390 (void)snprintf(buf, sizeof(buf), 391 "%"PRIuMAX, n); 392 } 393 pt = _add(buf, pt, ptlim); 394 } 395 continue; 396 case 'T': 397 pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp, 398 loc); 399 continue; 400 case 't': 401 pt = _add("\t", pt, ptlim); 402 continue; 403 case 'U': 404 pt = _conv((t->tm_yday + DAYSPERWEEK - 405 t->tm_wday) / DAYSPERWEEK, 406 fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex], 407 pt, ptlim, loc); 408 continue; 409 case 'u': 410 /* 411 ** From Arnold Robbins' strftime version 3.0: 412 ** "ISO 8601: Weekday as a decimal number 413 ** [1 (Monday) - 7]" 414 ** (ado, 1993-05-24) 415 */ 416 pt = _conv((t->tm_wday == 0) ? 417 DAYSPERWEEK : t->tm_wday, 418 "%d", pt, ptlim, loc); 419 continue; 420 case 'V': /* ISO 8601 week number */ 421 case 'G': /* ISO 8601 year (four digits) */ 422 case 'g': /* ISO 8601 year (two digits) */ 423 /* 424 ** From Arnold Robbins' strftime version 3.0: "the week number of the 425 ** year (the first Monday as the first day of week 1) as a decimal number 426 ** (01-53)." 427 ** (ado, 1993-05-24) 428 ** 429 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn: 430 ** "Week 01 of a year is per definition the first week which has the 431 ** Thursday in this year, which is equivalent to the week which contains 432 ** the fourth day of January. In other words, the first week of a new year 433 ** is the week which has the majority of its days in the new year. Week 01 434 ** might also contain days from the previous year and the week before week 435 ** 01 of a year is the last week (52 or 53) of the previous year even if 436 ** it contains days from the new year. A week starts with Monday (day 1) 437 ** and ends with Sunday (day 7). For example, the first week of the year 438 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 439 ** (ado, 1996-01-02) 440 */ 441 { 442 int year; 443 int base; 444 int yday; 445 int wday; 446 int w; 447 448 year = t->tm_year; 449 base = TM_YEAR_BASE; 450 yday = t->tm_yday; 451 wday = t->tm_wday; 452 for ( ; ; ) { 453 int len; 454 int bot; 455 int top; 456 457 len = isleap_sum(year, base) ? 458 DAYSPERLYEAR : 459 DAYSPERNYEAR; 460 /* 461 ** What yday (-3 ... 3) does 462 ** the ISO year begin on? 463 */ 464 bot = ((yday + 11 - wday) % 465 DAYSPERWEEK) - 3; 466 /* 467 ** What yday does the NEXT 468 ** ISO year begin on? 469 */ 470 top = bot - 471 (len % DAYSPERWEEK); 472 if (top < -3) 473 top += DAYSPERWEEK; 474 top += len; 475 if (yday >= top) { 476 ++base; 477 w = 1; 478 break; 479 } 480 if (yday >= bot) { 481 w = 1 + ((yday - bot) / 482 DAYSPERWEEK); 483 break; 484 } 485 --base; 486 yday += isleap_sum(year, base) ? 487 DAYSPERLYEAR : 488 DAYSPERNYEAR; 489 } 490 #ifdef XPG4_1994_04_09 491 if ((w == 52 && 492 t->tm_mon == TM_JANUARY) || 493 (w == 1 && 494 t->tm_mon == TM_DECEMBER)) 495 w = 53; 496 #endif /* defined XPG4_1994_04_09 */ 497 if (*format == 'V') 498 pt = _conv(w, 499 fmt_padding[ 500 PAD_FMT_WEEKOFYEAR][ 501 PadIndex], pt, ptlim, loc); 502 else if (*format == 'g') { 503 *warnp = IN_ALL; 504 pt = _yconv(year, base, 505 false, true, 506 pt, ptlim, loc); 507 } else pt = _yconv(year, base, 508 true, true, 509 pt, ptlim, loc); 510 } 511 continue; 512 case 'v': 513 /* 514 ** From Arnold Robbins' strftime version 3.0: 515 ** "date as dd-bbb-YYYY" 516 ** (ado, 1993-05-24) 517 */ 518 pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp, 519 loc); 520 continue; 521 case 'W': 522 pt = _conv((t->tm_yday + DAYSPERWEEK - 523 (t->tm_wday ? 524 (t->tm_wday - 1) : 525 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 526 fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex], 527 pt, ptlim, loc); 528 continue; 529 case 'w': 530 pt = _conv(t->tm_wday, "%d", pt, ptlim, loc); 531 continue; 532 case 'X': 533 pt = _fmt(sp, tptr->t_fmt, t, pt, 534 ptlim, warnp, loc); 535 continue; 536 case 'x': 537 { 538 enum warn warn2 = IN_SOME; 539 540 pt = _fmt(sp, tptr->d_fmt, t, pt, 541 ptlim, &warn2, loc); 542 if (warn2 == IN_ALL) 543 warn2 = IN_THIS; 544 if (warn2 > *warnp) 545 *warnp = warn2; 546 } 547 continue; 548 case 'y': 549 *warnp = IN_ALL; 550 pt = _yconv(t->tm_year, TM_YEAR_BASE, 551 false, true, 552 pt, ptlim, loc); 553 continue; 554 case 'Y': 555 pt = _yconv(t->tm_year, TM_YEAR_BASE, 556 true, true, 557 pt, ptlim, loc); 558 continue; 559 case 'Z': 560 #ifdef TM_ZONE 561 pt = _add(t->TM_ZONE, pt, ptlim); 562 #elif HAVE_TZNAME 563 if (t->tm_isdst >= 0) { 564 int oerrno = errno, dst = t->tm_isdst; 565 const char *z = 566 tzgetname(sp, dst); 567 if (z == NULL) 568 z = tzgetname(sp, !dst); 569 if (z != NULL) 570 pt = _add(z, pt, ptlim); 571 errno = oerrno; 572 } 573 #endif 574 /* 575 ** C99 and later say that %Z must be 576 ** replaced by the empty string if the 577 ** time zone abbreviation is not 578 ** determinable. 579 */ 580 continue; 581 case 'z': 582 #if defined TM_GMTOFF || USG_COMPAT || ALTZONE 583 { 584 long diff; 585 char const * sign; 586 bool negative; 587 588 if (t->tm_isdst < 0) 589 continue; 590 # ifdef TM_GMTOFF 591 diff = (int)t->TM_GMTOFF; 592 # else 593 /* 594 ** C99 and later say that the UT offset must 595 ** be computed by looking only at 596 ** tm_isdst. This requirement is 597 ** incorrect, since it means the code 598 ** must rely on magic (in this case 599 ** altzone and timezone), and the 600 ** magic might not have the correct 601 ** offset. Doing things correctly is 602 ** tricky and requires disobeying the standard; 603 ** see GNU C strftime for details. 604 ** For now, punt and conform to the 605 ** standard, even though it's incorrect. 606 ** 607 ** C99 and later say that %z must be replaced by 608 ** the empty string if the time zone is not 609 ** determinable, so output nothing if the 610 ** appropriate variables are not available. 611 */ 612 # ifndef STD_INSPIRED 613 if (t->tm_isdst == 0) 614 # if USG_COMPAT 615 diff = -timezone; 616 # else 617 continue; 618 # endif 619 else 620 # if ALTZONE 621 diff = -altzone; 622 # else 623 continue; 624 # endif 625 # else 626 { 627 struct tm tmp; 628 time_t lct, gct; 629 630 /* 631 ** Get calendar time from t 632 ** being treated as local. 633 */ 634 tmp = *t; /* mktime discards const */ 635 lct = mktime_z(sp, &tmp); 636 637 if (lct == (time_t)-1) 638 continue; 639 640 /* 641 ** Get calendar time from t 642 ** being treated as GMT. 643 **/ 644 tmp = *t; /* mktime discards const */ 645 gct = timegm(&tmp); 646 647 if (gct == (time_t)-1) 648 continue; 649 650 /* LINTED difference will fit int */ 651 diff = (intmax_t)gct - (intmax_t)lct; 652 } 653 # endif 654 # endif 655 negative = diff < 0; 656 if (diff == 0) { 657 # ifdef TM_ZONE 658 negative = t->TM_ZONE[0] == '-'; 659 # else 660 negative = t->tm_isdst < 0; 661 # if HAVE_TZNAME 662 if (tzname[t->tm_isdst != 0][0] == '-') 663 negative = true; 664 # endif 665 # endif 666 } 667 if (negative) { 668 sign = "-"; 669 diff = -diff; 670 } else sign = "+"; 671 pt = _add(sign, pt, ptlim); 672 diff /= SECSPERMIN; 673 diff = (diff / MINSPERHOUR) * 100 + 674 (diff % MINSPERHOUR); 675 _DIAGASSERT(__type_fit(int, diff)); 676 pt = _conv((int)diff, 677 fmt_padding[PAD_FMT_YEAR][PadIndex], 678 pt, ptlim, loc); 679 } 680 #endif 681 continue; 682 case '+': 683 #ifdef notyet 684 /* XXX: no date_fmt in _TimeLocale */ 685 pt = _fmt(sp, tptr->date_fmt, t, 686 pt, ptlim, warnp, loc); 687 #else 688 pt = _fmt(sp, "%a %b %e %H:%M:%S %Z %Y", t, 689 pt, ptlim, warnp, loc); 690 #endif 691 continue; 692 case '-': 693 if (PadIndex != PAD_DEFAULT) 694 break; 695 PadIndex = PAD_LESS; 696 goto label; 697 case '_': 698 if (PadIndex != PAD_DEFAULT) 699 break; 700 PadIndex = PAD_SPACE; 701 goto label; 702 case '0': 703 if (PadIndex != PAD_DEFAULT) 704 break; 705 PadIndex = PAD_ZERO; 706 goto label; 707 case '%': 708 /* 709 ** X311J/88-090 (4.12.3.5): if conversion char is 710 ** undefined, behavior is undefined. Print out the 711 ** character itself as printf(3) also does. 712 */ 713 default: 714 break; 715 } 716 } 717 if (pt == ptlim) 718 break; 719 *pt++ = *format; 720 } 721 return pt; 722 } 723 724 size_t 725 strftime(char *s, size_t maxsize, const char *format, const struct tm *t) 726 { 727 size_t r; 728 729 rwlock_wrlock(&__lcl_lock); 730 tzset_unlocked(); 731 r = strftime_z(__lclptr, s, maxsize, format, t); 732 rwlock_unlock(&__lcl_lock); 733 734 return r; 735 } 736 737 size_t 738 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format, 739 const struct tm * __restrict t, locale_t loc) 740 { 741 size_t r; 742 743 rwlock_wrlock(&__lcl_lock); 744 tzset_unlocked(); 745 r = strftime_lz(__lclptr, s, maxsize, format, t, loc); 746 rwlock_unlock(&__lcl_lock); 747 748 return r; 749 } 750 751 static char * 752 _conv(int n, const char *format, char *pt, const char *ptlim, locale_t loc) 753 { 754 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 755 756 (void) snprintf_l(buf, sizeof(buf), loc, format, n); 757 return _add(buf, pt, ptlim); 758 } 759 760 static char * 761 _add(const char *str, char *pt, const char *ptlim) 762 { 763 while (pt < ptlim && (*pt = *str++) != '\0') 764 ++pt; 765 return pt; 766 } 767 768 /* 769 ** POSIX and the C Standard are unclear or inconsistent about 770 ** what %C and %y do if the year is negative or exceeds 9999. 771 ** Use the convention that %C concatenated with %y yields the 772 ** same output as %Y, and that %Y contains at least 4 bytes, 773 ** with more only if necessary. 774 */ 775 776 static char * 777 _yconv(int a, int b, bool convert_top, bool convert_yy, 778 char *pt, const char * ptlim, locale_t loc) 779 { 780 int lead; 781 int trail; 782 783 int DIVISOR = 100; 784 trail = a % DIVISOR + b % DIVISOR; 785 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; 786 trail %= DIVISOR; 787 if (trail < 0 && lead > 0) { 788 trail += DIVISOR; 789 --lead; 790 } else if (lead < 0 && trail > 0) { 791 trail -= DIVISOR; 792 ++lead; 793 } 794 if (convert_top) { 795 if (lead == 0 && trail < 0) 796 pt = _add("-0", pt, ptlim); 797 else pt = _conv(lead, "%02d", pt, ptlim, loc); 798 } 799 if (convert_yy) 800 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim, 801 loc); 802 return pt; 803 } 804