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