1 /* $NetBSD: strftime.c,v 1.56 2024/06/07 13:53:23 riastradh 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.56 2024/06/07 13:53:23 riastradh 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[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 /*LINTED possibly unreached*/ 400 uintmax_t n = mkt; 401 (void)snprintf(buf, sizeof(buf), 402 "%"PRIuMAX, n); 403 } 404 pt = _add(buf, pt, ptlim); 405 } 406 continue; 407 case 'T': 408 pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp, 409 loc); 410 continue; 411 case 't': 412 pt = _add("\t", pt, ptlim); 413 continue; 414 case 'U': 415 pt = _conv((t->tm_yday + DAYSPERWEEK - 416 t->tm_wday) / DAYSPERWEEK, 417 fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex], 418 pt, ptlim, loc); 419 continue; 420 case 'u': 421 /* 422 ** From Arnold Robbins' strftime version 3.0: 423 ** "ISO 8601: Weekday as a decimal number 424 ** [1 (Monday) - 7]" 425 ** (ado, 1993-05-24) 426 */ 427 pt = _conv((t->tm_wday == 0) ? 428 DAYSPERWEEK : t->tm_wday, 429 "%d", pt, ptlim, loc); 430 continue; 431 case 'V': /* ISO 8601 week number */ 432 case 'G': /* ISO 8601 year (four digits) */ 433 case 'g': /* ISO 8601 year (two digits) */ 434 /* 435 ** From Arnold Robbins' strftime version 3.0: "the week number of the 436 ** year (the first Monday as the first day of week 1) as a decimal number 437 ** (01-53)." 438 ** (ado, 1993-05-24) 439 ** 440 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn: 441 ** "Week 01 of a year is per definition the first week which has the 442 ** Thursday in this year, which is equivalent to the week which contains 443 ** the fourth day of January. In other words, the first week of a new year 444 ** is the week which has the majority of its days in the new year. Week 01 445 ** might also contain days from the previous year and the week before week 446 ** 01 of a year is the last week (52 or 53) of the previous year even if 447 ** it contains days from the new year. A week starts with Monday (day 1) 448 ** and ends with Sunday (day 7). For example, the first week of the year 449 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 450 ** (ado, 1996-01-02) 451 */ 452 { 453 int year; 454 int base; 455 int yday; 456 int wday; 457 int w; 458 459 year = t->tm_year; 460 base = TM_YEAR_BASE; 461 yday = t->tm_yday; 462 wday = t->tm_wday; 463 for ( ; ; ) { 464 int len; 465 int bot; 466 int top; 467 468 len = isleap_sum(year, base) ? 469 DAYSPERLYEAR : 470 DAYSPERNYEAR; 471 /* 472 ** What yday (-3 ... 3) does 473 ** the ISO year begin on? 474 */ 475 bot = ((yday + 11 - wday) % 476 DAYSPERWEEK) - 3; 477 /* 478 ** What yday does the NEXT 479 ** ISO year begin on? 480 */ 481 top = bot - 482 (len % DAYSPERWEEK); 483 if (top < -3) 484 top += DAYSPERWEEK; 485 top += len; 486 if (yday >= top) { 487 ++base; 488 w = 1; 489 break; 490 } 491 if (yday >= bot) { 492 w = 1 + ((yday - bot) / 493 DAYSPERWEEK); 494 break; 495 } 496 --base; 497 yday += isleap_sum(year, base) ? 498 DAYSPERLYEAR : 499 DAYSPERNYEAR; 500 } 501 #ifdef XPG4_1994_04_09 502 if ((w == 52 && 503 t->tm_mon == TM_JANUARY) || 504 (w == 1 && 505 t->tm_mon == TM_DECEMBER)) 506 w = 53; 507 #endif /* defined XPG4_1994_04_09 */ 508 if (*format == 'V') 509 pt = _conv(w, 510 fmt_padding[ 511 PAD_FMT_WEEKOFYEAR][ 512 PadIndex], pt, ptlim, loc); 513 else if (*format == 'g') { 514 *warnp = IN_ALL; 515 pt = _yconv(year, base, 516 false, true, 517 pt, ptlim, loc); 518 } else pt = _yconv(year, base, 519 true, true, 520 pt, ptlim, loc); 521 } 522 continue; 523 case 'v': 524 /* 525 ** From Arnold Robbins' strftime version 3.0: 526 ** "date as dd-bbb-YYYY" 527 ** (ado, 1993-05-24) 528 */ 529 pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp, 530 loc); 531 continue; 532 case 'W': 533 pt = _conv((t->tm_yday + DAYSPERWEEK - 534 (t->tm_wday ? 535 (t->tm_wday - 1) : 536 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 537 fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex], 538 pt, ptlim, loc); 539 continue; 540 case 'w': 541 pt = _conv(t->tm_wday, "%d", pt, ptlim, loc); 542 continue; 543 case 'X': 544 pt = _fmt(sp, tptr->t_fmt, t, pt, 545 ptlim, warnp, loc); 546 continue; 547 case 'x': 548 { 549 enum warn warn2 = IN_SOME; 550 551 pt = _fmt(sp, tptr->d_fmt, t, pt, 552 ptlim, &warn2, loc); 553 if (warn2 == IN_ALL) 554 warn2 = IN_THIS; 555 if (warn2 > *warnp) 556 *warnp = warn2; 557 } 558 continue; 559 case 'y': 560 *warnp = IN_ALL; 561 pt = _yconv(t->tm_year, TM_YEAR_BASE, 562 false, true, 563 pt, ptlim, loc); 564 continue; 565 case 'Y': 566 pt = _yconv(t->tm_year, TM_YEAR_BASE, 567 true, true, 568 pt, ptlim, loc); 569 continue; 570 case 'Z': 571 #ifdef TM_ZONE 572 pt = _add(t->TM_ZONE, pt, ptlim); 573 #elif HAVE_TZNAME 574 if (t->tm_isdst >= 0) { 575 int oerrno = errno, dst = t->tm_isdst; 576 const char *z = 577 tzgetname(sp, dst); 578 if (z == NULL) 579 z = tzgetname(sp, !dst); 580 if (z != NULL) 581 pt = _add(z, pt, ptlim); 582 errno = oerrno; 583 } 584 #endif 585 /* 586 ** C99 and later say that %Z must be 587 ** replaced by the empty string if the 588 ** time zone abbreviation is not 589 ** determinable. 590 */ 591 continue; 592 case 'z': 593 #if defined TM_GMTOFF || USG_COMPAT || ALTZONE 594 { 595 long diff; 596 char const * sign; 597 bool negative; 598 599 if (t->tm_isdst < 0) 600 continue; 601 # ifdef TM_GMTOFF 602 diff = (int)t->TM_GMTOFF; 603 # else 604 /* 605 ** C99 and later say that the UT offset must 606 ** be computed by looking only at 607 ** tm_isdst. This requirement is 608 ** incorrect, since it means the code 609 ** must rely on magic (in this case 610 ** altzone and timezone), and the 611 ** magic might not have the correct 612 ** offset. Doing things correctly is 613 ** tricky and requires disobeying the standard; 614 ** see GNU C strftime for details. 615 ** For now, punt and conform to the 616 ** standard, even though it's incorrect. 617 ** 618 ** C99 and later say that %z must be replaced by 619 ** the empty string if the time zone is not 620 ** determinable, so output nothing if the 621 ** appropriate variables are not available. 622 */ 623 # ifndef STD_INSPIRED 624 if (t->tm_isdst == 0) 625 # if USG_COMPAT 626 diff = -timezone; 627 # else 628 continue; 629 # endif 630 else 631 # if ALTZONE 632 diff = -altzone; 633 # else 634 continue; 635 # endif 636 # else 637 { 638 struct tm tmp; 639 time_t lct, gct; 640 641 /* 642 ** Get calendar time from t 643 ** being treated as local. 644 */ 645 tmp = *t; /* mktime discards const */ 646 lct = mktime_z(sp, &tmp); 647 648 if (lct == (time_t)-1) 649 continue; 650 651 /* 652 ** Get calendar time from t 653 ** being treated as GMT. 654 **/ 655 tmp = *t; /* mktime discards const */ 656 gct = timegm(&tmp); 657 658 if (gct == (time_t)-1) 659 continue; 660 661 /* LINTED difference will fit int */ 662 diff = (intmax_t)gct - (intmax_t)lct; 663 } 664 # endif 665 # endif 666 negative = diff < 0; 667 if (diff == 0) { 668 # ifdef TM_ZONE 669 negative = t->TM_ZONE[0] == '-'; 670 # else 671 negative = t->tm_isdst < 0; 672 # if HAVE_TZNAME 673 if (tzname[t->tm_isdst != 0][0] == '-') 674 negative = true; 675 # endif 676 # endif 677 } 678 if (negative) { 679 sign = "-"; 680 diff = -diff; 681 } else sign = "+"; 682 pt = _add(sign, pt, ptlim); 683 diff /= SECSPERMIN; 684 diff = (diff / MINSPERHOUR) * 100 + 685 (diff % MINSPERHOUR); 686 _DIAGASSERT(__type_fit(int, diff)); 687 pt = _conv((int)diff, 688 fmt_padding[PAD_FMT_YEAR][PadIndex], 689 pt, ptlim, loc); 690 } 691 #endif 692 continue; 693 case '+': 694 #ifdef notyet 695 /* XXX: no date_fmt in _TimeLocale */ 696 pt = _fmt(sp, tptr->date_fmt, t, 697 pt, ptlim, warnp, loc); 698 #else 699 pt = _fmt(sp, "%a %b %e %H:%M:%S %Z %Y", t, 700 pt, ptlim, warnp, loc); 701 #endif 702 continue; 703 case '-': 704 if (PadIndex != PAD_DEFAULT) 705 break; 706 PadIndex = PAD_LESS; 707 goto label; 708 case '_': 709 if (PadIndex != PAD_DEFAULT) 710 break; 711 PadIndex = PAD_SPACE; 712 goto label; 713 case '0': 714 if (PadIndex != PAD_DEFAULT) 715 break; 716 PadIndex = PAD_ZERO; 717 goto label; 718 case '%': 719 /* 720 ** X311J/88-090 (4.12.3.5): if conversion char is 721 ** undefined, behavior is undefined. Print out the 722 ** character itself as printf(3) also does. 723 */ 724 default: 725 break; 726 } 727 } 728 if (pt == ptlim) 729 break; 730 *pt++ = *format; 731 } 732 return pt; 733 } 734 735 size_t 736 strftime(char *restrict s, size_t maxsize, char const *restrict format, 737 struct tm const *restrict t) 738 { 739 size_t r; 740 741 rwlock_wrlock(&__lcl_lock); 742 tzset_unlocked(); 743 r = strftime_z(__lclptr, s, maxsize, format, t); 744 rwlock_unlock(&__lcl_lock); 745 746 return r; 747 } 748 749 size_t 750 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format, 751 const struct tm * __restrict t, locale_t loc) 752 { 753 size_t r; 754 755 rwlock_wrlock(&__lcl_lock); 756 tzset_unlocked(); 757 r = strftime_lz(__lclptr, s, maxsize, format, t, loc); 758 rwlock_unlock(&__lcl_lock); 759 760 return r; 761 } 762 763 static char * 764 _conv(int n, const char *format, char *pt, const char *ptlim, locale_t loc) 765 { 766 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 767 768 (void) snprintf_l(buf, sizeof(buf), loc, format, n); 769 return _add(buf, pt, ptlim); 770 } 771 772 static char * 773 _add(const char *str, char *pt, const char *ptlim) 774 { 775 while (pt < ptlim && (*pt = *str++) != '\0') 776 ++pt; 777 return pt; 778 } 779 780 /* 781 ** POSIX and the C Standard are unclear or inconsistent about 782 ** what %C and %y do if the year is negative or exceeds 9999. 783 ** Use the convention that %C concatenated with %y yields the 784 ** same output as %Y, and that %Y contains at least 4 bytes, 785 ** with more only if necessary. 786 */ 787 788 static char * 789 _yconv(int a, int b, bool convert_top, bool convert_yy, 790 char *pt, const char * ptlim, locale_t loc) 791 { 792 int lead; 793 int trail; 794 795 int DIVISOR = 100; 796 trail = a % DIVISOR + b % DIVISOR; 797 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; 798 trail %= DIVISOR; 799 if (trail < 0 && lead > 0) { 800 trail += DIVISOR; 801 --lead; 802 } else if (lead < 0 && trail > 0) { 803 trail -= DIVISOR; 804 ++lead; 805 } 806 if (convert_top) { 807 if (lead == 0 && trail < 0) 808 pt = _add("-0", pt, ptlim); 809 else pt = _conv(lead, "%02d", pt, ptlim, loc); 810 } 811 if (convert_yy) 812 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim, 813 loc); 814 return pt; 815 } 816