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