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