1 /* $NetBSD: strftime.c,v 1.61 2026/09/12 19:43:35 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.61 2026/09/12 19:43:35 christos Exp $"); 39 #endif 40 #endif /* LIBC_SCCS and not lint */ 41 42 #include "namespace.h" 43 44 #include <stddef.h> 45 #include <locale.h> 46 #include "setlocale_local.h" 47 48 /* 49 ** Based on the UCB version with the copyright notice appearing above. 50 ** 51 ** This is ANSIish only when "multibyte character == plain character". 52 */ 53 54 #include "private.h" 55 56 /* 57 ** We don't use these extensions in strftime operation even when 58 ** supported by the local tzcode configuration. A strictly 59 ** conforming C application may leave them in undefined state. 60 */ 61 62 #ifdef _LIBC 63 #undef TM_ZONE 64 #undef TM_GMTOFF 65 #endif 66 67 #include <fcntl.h> 68 #include <locale.h> 69 #include <stdio.h> 70 71 /* If true, the value returned by an idealized unlimited-range mktime 72 always fits into an integer type with bounds MIN and MAX. 73 If false, the value might not fit. 74 This macro is usable in #if if its arguments are. 75 Add or subtract 2**31 - 1 for the maximum UT offset allowed in a TZif file, 76 divide by the maximum number of non-leap seconds in a year, 77 divide again by two just to be safe, 78 and account for the tm_year origin (1900) and time_t origin (1970). */ 79 #define MKTIME_FITS_IN(min, max) \ 80 ((min) < 0 \ 81 && (((min) + TWO_31_MINUS_1) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900 \ 82 < INT_MIN) \ 83 && INT_MAX < ((max) - TWO_31_MINUS_1) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900) 84 85 /* MKTIME_MIGHT_OVERFLOW is true if mktime can fail due to time_t overflow 86 or if it is not known whether mktime can fail, 87 and is false if mktime definitely cannot fail. 88 This macro is usable in #if, and so does not use TIME_T_MAX or sizeof. 89 If the builder has not configured this macro, guess based on what 90 known platforms do. When in doubt, guess true. */ 91 #ifndef MKTIME_MIGHT_OVERFLOW 92 # if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ 93 # include <sys/param.h> 94 # endif 95 # if ((/* The following heuristics assume native time_t. */ \ 96 defined_time_tz) \ 97 || ((/* Traditional time_t is 'long', so if 'long' is not wide enough \ 98 assume overflow unless we're on a known-safe host. */ \ 99 !MKTIME_FITS_IN(LONG_MIN, LONG_MAX)) \ 100 && (/* GNU C Library 2.29 (2019-02-01) and later has 64-bit time_t \ 101 if __TIMESIZE is 64. */ \ 102 !defined __TIMESIZE || __TIMESIZE < 64) \ 103 && (/* FreeBSD 12 r320347 (__FreeBSD_version 1200036; 2017-06-26), \ 104 and later has 64-bit time_t on all platforms but i386 which \ 105 is currently scheduled for end-of-life on 2028-11-30. */ \ 106 !defined __FreeBSD_version || __FreeBSD_version < 1200036 \ 107 || defined __i386) \ 108 && (/* NetBSD 6.0 (2012-10-17) and later has 64-bit time_t. */ \ 109 !defined __NetBSD_Version__ || __NetBSD_Version__ < 600000000) \ 110 && (/* OpenBSD 5.5 (2014-05-01) and later has 64-bit time_t. */ \ 111 !defined OpenBSD || OpenBSD < 201405))) 112 # define MKTIME_MIGHT_OVERFLOW 1 113 # else 114 # define MKTIME_MIGHT_OVERFLOW 0 115 # endif 116 #endif 117 /* Check that MKTIME_MIGHT_OVERFLOW is consistent with time_t's range. */ 118 static_assert(MKTIME_MIGHT_OVERFLOW 119 || MKTIME_FITS_IN(TIME_T_MIN, TIME_T_MAX)); 120 121 #if MKTIME_MIGHT_OVERFLOW 122 /* Do this after system includes as it redefines time_t, mktime, timeoff. */ 123 # define USE_TIMEX_T true 124 # include "localtime.c" 125 #endif 126 127 #ifndef DEPRECATE_TWO_DIGIT_YEARS 128 # define DEPRECATE_TWO_DIGIT_YEARS 0 129 #endif 130 131 #ifdef __weak_alias 132 __weak_alias(strftime_l, _strftime_l) 133 __weak_alias(strftime_lz, _strftime_lz) 134 __weak_alias(strftime_z, _strftime_z) 135 #endif 136 137 #include "sys/localedef.h" 138 #define _TIME_LOCALE(loc) \ 139 ((_TimeLocale *)((loc)->part_impl[LC_TIME])) 140 #define c_fmt d_t_fmt 141 142 enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL }; 143 144 static char * _add(const char *, char *, const char *); 145 static char * _conv(int, const char *, char *, const char *, locale_t); 146 static char * _fmt(const timezone_t, const char *, const struct tm *, char *, 147 const char *, enum warn *, locale_t); 148 static char * _yconv(int, int, bool, bool, char *, const char *, locale_t); 149 150 #ifndef YEAR_2000_NAME 151 # define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" 152 #endif /* !defined YEAR_2000_NAME */ 153 154 #define IN_NONE 0 155 #define IN_SOME 1 156 #define IN_THIS 2 157 #define IN_ALL 3 158 159 #define PAD_DEFAULT 0 160 #define PAD_LESS 1 161 #define PAD_SPACE 2 162 #define PAD_ZERO 3 163 164 static const char fmt_padding[][4][5] = { 165 /* DEFAULT, LESS, SPACE, ZERO */ 166 #define PAD_FMT_MONTHDAY 0 167 #define PAD_FMT_HMS 0 168 #define PAD_FMT_CENTURY 0 169 #define PAD_FMT_SHORTYEAR 0 170 #define PAD_FMT_MONTH 0 171 #define PAD_FMT_WEEKOFYEAR 0 172 #define PAD_FMT_DAYOFMONTH 0 173 { "%02d", "%d", "%2d", "%02d" }, 174 #define PAD_FMT_SDAYOFMONTH 1 175 #define PAD_FMT_SHMS 1 176 { "%2d", "%d", "%2d", "%02d" }, 177 #define PAD_FMT_DAYOFYEAR 2 178 { "%03d", "%d", "%3d", "%03d" }, 179 #define PAD_FMT_YEAR 3 180 { "%04d", "%d", "%4d", "%04d" } 181 }; 182 183 size_t 184 strftime_z(const timezone_t sp, char * __restrict s, size_t maxsize, 185 const char * __restrict format, const struct tm * __restrict t) 186 { 187 return strftime_lz(sp, s, maxsize, format, t, _current_locale()); 188 } 189 190 #if HAVE_STRFTIME_L 191 size_t 192 strftime_l(char *restrict s, size_t maxsize, char const *restrict format, 193 struct tm const *restrict t, 194 ATTRIBUTE_MAYBE_UNUSED locale_t locale) 195 { 196 /* Just call strftime, as only the C locale is supported. */ 197 return strftime(s, maxsize, format, t); 198 } 199 #endif 200 201 size_t 202 strftime_lz(const timezone_t sp, char *const s, const size_t maxsize, 203 const char *const format, const struct tm *const t, locale_t loc) 204 { 205 char * p; 206 int saved_errno = errno; 207 enum warn warn = IN_NONE; 208 209 p = _fmt(sp, format, t, s, s + maxsize, &warn, loc); 210 if (/*CONSTCOND*/DEPRECATE_TWO_DIGIT_YEARS 211 && warn != IN_NONE && getenv(YEAR_2000_NAME)) { 212 (void) fprintf(stderr, "\n"); 213 (void) fprintf(stderr, "strftime format \"%s\" ", format); 214 (void) fprintf(stderr, "yields only two digits of years in "); 215 if (warn == IN_SOME) 216 (void) fprintf(stderr, "some locales"); 217 else if (warn == IN_THIS) 218 (void) fprintf(stderr, "the current locale"); 219 else (void) fprintf(stderr, "all locales"); 220 (void) fprintf(stderr, "\n"); 221 } 222 if (p == s + maxsize) { 223 errno = ERANGE; 224 return 0; 225 } 226 *p = '\0'; 227 errno = saved_errno; 228 return p - s; 229 } 230 231 static char * 232 _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt, 233 const char *ptlim, enum warn *warnp, locale_t loc) 234 { 235 int Ealternative, Oalternative, PadIndex; 236 _TimeLocale *tptr = _TIME_LOCALE(loc); 237 238 for ( ; *format; ++format) { 239 if (*format == '%') { 240 Ealternative = 0; 241 Oalternative = 0; 242 PadIndex = PAD_DEFAULT; 243 label: 244 switch (*++format) { 245 case '\0': 246 /* Output unknown conversion specifiers as-is, 247 to aid debugging. This includes '%' at 248 format end. This conforms to C23 section 249 7.29.3.5 paragraph 6, which says behavior 250 is undefined here. */ 251 default: 252 --format; 253 break; 254 case 'A': 255 pt = _add((t->tm_wday < 0 || 256 t->tm_wday >= DAYSPERWEEK) ? 257 "?" : tptr->day[t->tm_wday], 258 pt, ptlim); 259 continue; 260 case 'a': 261 pt = _add((t->tm_wday < 0 || 262 t->tm_wday >= DAYSPERWEEK) ? 263 "?" : tptr->abday[t->tm_wday], 264 pt, ptlim); 265 continue; 266 case 'B': 267 pt = _add((t->tm_mon < 0 || 268 t->tm_mon >= MONSPERYEAR) ? 269 "?" : 270 /* no alt_month in _TimeLocale */ 271 (Oalternative ? tptr->mon/*alt_month*/: 272 tptr->mon)[t->tm_mon], 273 pt, ptlim); 274 continue; 275 case 'b': 276 case 'h': 277 pt = _add((t->tm_mon < 0 || 278 t->tm_mon >= MONSPERYEAR) ? 279 "?" : tptr->abmon[t->tm_mon], 280 pt, ptlim); 281 continue; 282 case 'C': 283 /* 284 ** %C used to do a... 285 ** _fmt("%a %b %e %X %Y", t); 286 ** ...whereas now POSIX 1003.2 calls for 287 ** something completely different. 288 ** (ado, 1993-05-24) 289 */ 290 pt = _yconv(t->tm_year, TM_YEAR_BASE, 291 true, false, pt, ptlim, loc); 292 continue; 293 case 'c': 294 { 295 enum warn warn2 = IN_SOME; 296 297 pt = _fmt(sp, tptr->c_fmt, t, pt, 298 ptlim, &warn2, loc); 299 if (warn2 == IN_ALL) 300 warn2 = IN_THIS; 301 if (warn2 > *warnp) 302 *warnp = warn2; 303 } 304 continue; 305 case 'D': 306 pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp, 307 loc); 308 continue; 309 case 'd': 310 pt = _conv(t->tm_mday, 311 fmt_padding[PAD_FMT_DAYOFMONTH][PadIndex], 312 pt, ptlim, loc); 313 continue; 314 case 'E': 315 if (Ealternative || Oalternative) 316 break; 317 Ealternative++; 318 goto label; 319 case 'O': 320 /* 321 ** Locale modifiers of C99 and later. 322 ** The sequences 323 ** %Ec %EC %Ex %EX %Ey %EY 324 ** %Od %oe %OH %OI %Om %OM 325 ** %OS %Ou %OU %OV %Ow %OW %Oy 326 ** are supposed to provide alternative 327 ** representations. 328 */ 329 if (Ealternative || Oalternative) 330 break; 331 Oalternative++; 332 goto label; 333 case 'e': 334 pt = _conv(t->tm_mday, 335 fmt_padding[PAD_FMT_SDAYOFMONTH][PadIndex], 336 pt, ptlim, loc); 337 continue; 338 case 'F': 339 pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp, 340 loc); 341 continue; 342 case 'H': 343 pt = _conv(t->tm_hour, 344 fmt_padding[PAD_FMT_HMS][PadIndex], 345 pt, ptlim, loc); 346 continue; 347 case 'I': 348 pt = _conv((t->tm_hour % 12) ? 349 (t->tm_hour % 12) : 12, 350 fmt_padding[PAD_FMT_HMS][PadIndex], 351 pt, ptlim, loc); 352 continue; 353 case 'j': 354 pt = _conv(t->tm_yday + 1, 355 fmt_padding[PAD_FMT_DAYOFYEAR][PadIndex], 356 pt, ptlim, loc); 357 continue; 358 case 'k': 359 /* 360 ** This used to be... 361 ** _conv(t->tm_hour % 12 ? 362 ** t->tm_hour % 12 : 12, 2, ' '); 363 ** ...and has been changed to the below to 364 ** match SunOS 4.1.1 and Arnold Robbins' 365 ** strftime version 3.0. That is, "%k" and 366 ** "%l" have been swapped. 367 ** (ado, 1993-05-24) 368 */ 369 pt = _conv(t->tm_hour, 370 fmt_padding[PAD_FMT_SHMS][PadIndex], 371 pt, ptlim, loc); 372 continue; 373 #ifdef KITCHEN_SINK 374 case 'K': 375 /* 376 ** After all this time, still unclaimed! 377 */ 378 pt = _add("kitchen sink", pt, ptlim); 379 continue; 380 #endif /* defined KITCHEN_SINK */ 381 case 'l': 382 /* 383 ** This used to be... 384 ** _conv(t->tm_hour, 2, ' '); 385 ** ...and has been changed to the below to 386 ** match SunOS 4.1.1 and Arnold Robbin's 387 ** strftime version 3.0. That is, "%k" and 388 ** "%l" have been swapped. 389 ** (ado, 1993-05-24) 390 */ 391 pt = _conv((t->tm_hour % 12) ? 392 (t->tm_hour % 12) : 12, 393 fmt_padding[PAD_FMT_SHMS][PadIndex], 394 pt, ptlim, loc); 395 continue; 396 case 'M': 397 pt = _conv(t->tm_min, 398 fmt_padding[PAD_FMT_HMS][PadIndex], 399 pt, ptlim, loc); 400 continue; 401 case 'm': 402 pt = _conv(t->tm_mon + 1, 403 fmt_padding[PAD_FMT_MONTH][PadIndex], 404 pt, ptlim, loc); 405 continue; 406 case 'n': 407 pt = _add("\n", pt, ptlim); 408 continue; 409 case 'p': 410 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? 411 tptr->am_pm[1] : 412 tptr->am_pm[0], 413 pt, ptlim); 414 continue; 415 case 'R': 416 pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp, 417 loc); 418 continue; 419 case 'r': 420 pt = _fmt(sp, tptr->t_fmt_ampm, t, 421 pt, ptlim, warnp, loc); 422 continue; 423 case 'S': 424 pt = _conv(t->tm_sec, 425 fmt_padding[PAD_FMT_HMS][PadIndex], 426 pt, ptlim, loc); 427 continue; 428 case 's': 429 { 430 struct tm tm; 431 char buf[INT_STRLEN_MAXIMUM( 432 time_t) + 1]; 433 time_t mkt; 434 435 tm.tm_sec = t->tm_sec; 436 tm.tm_min = t->tm_min; 437 tm.tm_hour = t->tm_hour; 438 tm.tm_mday = t->tm_mday; 439 tm.tm_mon = t->tm_mon; 440 tm.tm_year = t->tm_year; 441 442 /* Get the time_t value for TM. 443 Native time_t, or its redefinition 444 by localtime.c above, is wide enough 445 so that this cannot overflow. */ 446 #if defined TM_GMTOFF 447 mkt = timeoff(&tm, t->TM_GMTOFF); 448 #else 449 tm.tm_isdst = t->tm_isdst; 450 mkt = mktime_z(sp, &tm); 451 #endif 452 /* CONSTCOND */ 453 if (TYPE_SIGNED(time_t)) { 454 intmax_t n = mkt; 455 (void)snprintf(buf, sizeof(buf), 456 "%"PRIdMAX, n); 457 } else { 458 /*LINTED possibly unreached*/ 459 uintmax_t n = mkt; 460 (void)snprintf(buf, sizeof(buf), 461 "%"PRIuMAX, n); 462 } 463 pt = _add(buf, pt, ptlim); 464 } 465 continue; 466 case 'T': 467 pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp, 468 loc); 469 continue; 470 case 't': 471 pt = _add("\t", pt, ptlim); 472 continue; 473 case 'U': 474 pt = _conv((t->tm_yday + DAYSPERWEEK - 475 t->tm_wday) / DAYSPERWEEK, 476 fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex], 477 pt, ptlim, loc); 478 continue; 479 case 'u': 480 /* 481 ** From Arnold Robbins' strftime version 3.0: 482 ** "ISO 8601: Weekday as a decimal number 483 ** [1 (Monday) - 7]" 484 ** (ado, 1993-05-24) 485 */ 486 pt = _conv((t->tm_wday == 0) ? 487 DAYSPERWEEK : t->tm_wday, 488 "%d", pt, ptlim, loc); 489 continue; 490 case 'V': /* ISO 8601 week number */ 491 case 'G': /* ISO 8601 year (four digits) */ 492 case 'g': /* ISO 8601 year (two digits) */ 493 /* 494 ** From Arnold Robbins' strftime version 3.0: "the week number of the 495 ** year (the first Monday as the first day of week 1) as a decimal number 496 ** (01-53)." 497 ** (ado, 1993-05-24) 498 ** 499 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn: 500 ** "Week 01 of a year is per definition the first week which has the 501 ** Thursday in this year, which is equivalent to the week which contains 502 ** the fourth day of January. In other words, the first week of a new year 503 ** is the week which has the majority of its days in the new year. Week 01 504 ** might also contain days from the previous year and the week before week 505 ** 01 of a year is the last week (52 or 53) of the previous year even if 506 ** it contains days from the new year. A week starts with Monday (day 1) 507 ** and ends with Sunday (day 7). For example, the first week of the year 508 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 509 ** (ado, 1996-01-02) 510 */ 511 { 512 int year; 513 int base; 514 int yday; 515 int wday; 516 int w; 517 518 year = t->tm_year; 519 base = TM_YEAR_BASE; 520 yday = t->tm_yday; 521 wday = t->tm_wday; 522 for ( ; ; ) { 523 int len; 524 int bot; 525 int top; 526 527 len = year_sum_days(year, base); 528 /* 529 ** What yday (-3 ... 3) does 530 ** the ISO year begin on? 531 */ 532 bot = ((yday + 11 - wday) % 533 DAYSPERWEEK) - 3; 534 /* 535 ** What yday does the NEXT 536 ** ISO year begin on? 537 */ 538 top = bot - 539 (len % DAYSPERWEEK); 540 if (top < -3) 541 top += DAYSPERWEEK; 542 top += len; 543 if (yday >= top) { 544 ++base; 545 w = 1; 546 break; 547 } 548 if (yday >= bot) { 549 w = 1 + ((yday - bot) / 550 DAYSPERWEEK); 551 break; 552 } 553 --base; 554 yday += year_sum_days(year, 555 base); 556 } 557 #ifdef XPG4_1994_04_09 558 if ((w == 52 && 559 t->tm_mon == TM_JANUARY) || 560 (w == 1 && 561 t->tm_mon == TM_DECEMBER)) 562 w = 53; 563 #endif /* defined XPG4_1994_04_09 */ 564 if (*format == 'V') 565 pt = _conv(w, 566 fmt_padding[ 567 PAD_FMT_WEEKOFYEAR][ 568 PadIndex], pt, ptlim, loc); 569 else if (*format == 'g') { 570 *warnp = IN_ALL; 571 pt = _yconv(year, base, 572 false, true, 573 pt, ptlim, loc); 574 } else pt = _yconv(year, base, 575 true, true, 576 pt, ptlim, loc); 577 } 578 continue; 579 case 'v': 580 /* 581 ** From Arnold Robbins' strftime version 3.0: 582 ** "date as dd-bbb-YYYY" 583 ** (ado, 1993-05-24) 584 */ 585 pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp, 586 loc); 587 continue; 588 case 'W': 589 pt = _conv((t->tm_yday + DAYSPERWEEK - 590 (t->tm_wday ? 591 (t->tm_wday - 1) : 592 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 593 fmt_padding[PAD_FMT_WEEKOFYEAR][PadIndex], 594 pt, ptlim, loc); 595 continue; 596 case 'w': 597 pt = _conv(t->tm_wday, "%d", pt, ptlim, loc); 598 continue; 599 case 'X': 600 pt = _fmt(sp, tptr->t_fmt, t, pt, 601 ptlim, warnp, loc); 602 continue; 603 case 'x': 604 { 605 enum warn warn2 = IN_SOME; 606 607 pt = _fmt(sp, tptr->d_fmt, t, pt, 608 ptlim, &warn2, loc); 609 if (warn2 == IN_ALL) 610 warn2 = IN_THIS; 611 if (warn2 > *warnp) 612 *warnp = warn2; 613 } 614 continue; 615 case 'y': 616 *warnp = IN_ALL; 617 pt = _yconv(t->tm_year, TM_YEAR_BASE, 618 false, true, 619 pt, ptlim, loc); 620 continue; 621 case 'Y': 622 pt = _yconv(t->tm_year, TM_YEAR_BASE, 623 true, true, 624 pt, ptlim, loc); 625 continue; 626 case 'Z': 627 #ifdef TM_ZONE 628 pt = _add(t->TM_ZONE, pt, ptlim); 629 #elif HAVE_TZNAME 630 if (t->tm_isdst >= 0) { 631 int oerrno = errno, dst = t->tm_isdst; 632 const char *z = 633 tzgetname(sp, dst); 634 if (z == NULL) 635 z = tzgetname(sp, !dst); 636 if (z != NULL) 637 pt = _add(z, pt, ptlim); 638 errno = oerrno; 639 } 640 #endif 641 /* 642 ** C99 and later say that %Z must be 643 ** replaced by the empty string if the 644 ** time zone abbreviation is not 645 ** determinable. 646 */ 647 continue; 648 case 'z': 649 #if defined TM_GMTOFF || USG_COMPAT || ALTZONE 650 { 651 long diff; 652 char const * sign; 653 bool negative; 654 655 if (t->tm_isdst < 0) 656 continue; 657 # ifdef TM_GMTOFF 658 diff = (int)t->TM_GMTOFF; 659 # else 660 /* 661 ** C99 and later say that the UT offset must 662 ** be computed by looking only at 663 ** tm_isdst. This requirement is 664 ** incorrect, since it means the code 665 ** must rely on magic (in this case 666 ** altzone and timezone), and the 667 ** magic might not have the correct 668 ** offset. Doing things correctly is 669 ** tricky and requires disobeying the standard; 670 ** see GNU C strftime for details. 671 ** For now, punt and conform to the 672 ** standard, even though it's incorrect. 673 ** 674 ** C99 and later say that %z must be replaced by 675 ** the empty string if the time zone is not 676 ** determinable, so output nothing if the 677 ** appropriate variables are not available. 678 */ 679 # ifndef STD_INSPIRED 680 if (t->tm_isdst == 0) 681 # if USG_COMPAT 682 diff = -timezone; 683 # else 684 continue; 685 # endif 686 else 687 # if ALTZONE 688 diff = -altzone; 689 # else 690 continue; 691 # endif 692 # else 693 { 694 struct tm tmp; 695 time_t lct, gct; 696 697 /* 698 ** Get calendar time from t 699 ** being treated as local. 700 */ 701 tmp = *t; /* mktime discards const */ 702 lct = mktime_z(sp, &tmp); 703 704 if (lct == (time_t)-1) 705 continue; 706 707 /* 708 ** Get calendar time from t 709 ** being treated as GMT. 710 **/ 711 tmp = *t; /* mktime discards const */ 712 gct = timegm(&tmp); 713 714 if (gct == (time_t)-1) 715 continue; 716 717 /* LINTED difference will fit int */ 718 diff = (intmax_t)gct - (intmax_t)lct; 719 } 720 # endif 721 # endif 722 negative = diff < 0; 723 if (diff == 0) { 724 # ifdef TM_ZONE 725 negative = t->TM_ZONE[0] == '-'; 726 # else 727 negative = t->tm_isdst < 0; 728 # if HAVE_TZNAME 729 if (tzname[t->tm_isdst != 0][0] == '-') 730 negative = true; 731 # endif 732 # endif 733 } 734 if (negative) { 735 sign = "-"; 736 diff = -diff; 737 } else sign = "+"; 738 pt = _add(sign, pt, ptlim); 739 diff /= SECSPERMIN; 740 diff = (diff / MINSPERHOUR) * 100 + 741 (diff % MINSPERHOUR); 742 pt = _conv((int)diff, 743 fmt_padding[PAD_FMT_YEAR][PadIndex], 744 pt, ptlim, loc); 745 } 746 #endif 747 continue; 748 case '+': 749 #ifdef notyet 750 /* XXX: no date_fmt in _TimeLocale */ 751 pt = _fmt(sp, tptr->date_fmt, t, 752 pt, ptlim, warnp, loc); 753 #else 754 pt = _fmt(sp, "%a %b %e %H:%M:%S %Z %Y", t, 755 pt, ptlim, warnp, loc); 756 #endif 757 continue; 758 case '-': 759 if (PadIndex != PAD_DEFAULT) 760 break; 761 PadIndex = PAD_LESS; 762 goto label; 763 case '_': 764 if (PadIndex != PAD_DEFAULT) 765 break; 766 PadIndex = PAD_SPACE; 767 goto label; 768 case '0': 769 if (PadIndex != PAD_DEFAULT) 770 break; 771 PadIndex = PAD_ZERO; 772 goto label; 773 case '%': 774 break; 775 } 776 } 777 if (pt == ptlim) 778 break; 779 *pt++ = *format; 780 } 781 return pt; 782 } 783 784 size_t 785 strftime(char *restrict s, size_t maxsize, char const *restrict format, 786 struct tm const *restrict t) 787 { 788 size_t r; 789 time_t now = __lcl_get_monotonic_time(); 790 int err = __lcl_lock(); 791 if (0 < err) { 792 errno = err; 793 return 0; 794 } 795 tzset_unlocked(!err, false, now); 796 797 r = strftime_z(__lcl_ptr, s, maxsize, format, t); 798 799 __lcl_unlock(!err); 800 801 return r; 802 } 803 804 size_t 805 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format, 806 const struct tm * __restrict t, locale_t loc) 807 { 808 size_t r; 809 time_t now = __lcl_get_monotonic_time(); 810 int err = __lcl_lock(); 811 if (0 < err) { 812 errno = err; 813 return 0; 814 } 815 tzset_unlocked(!err, false, now); 816 817 r = strftime_lz(__lcl_ptr, s, maxsize, format, t, loc); 818 819 __lcl_unlock(!err); 820 821 return r; 822 } 823 824 static char * 825 _conv(int n, const char *format, char *pt, const char *ptlim, locale_t loc) 826 { 827 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 828 829 (void) snprintf_l(buf, sizeof(buf), loc, format, n); 830 return _add(buf, pt, ptlim); 831 } 832 833 static char * 834 _add(const char *str, char *pt, const char *ptlim) 835 { 836 while (pt < ptlim && (*pt = *str++) != '\0') 837 ++pt; 838 return pt; 839 } 840 841 /* 842 ** POSIX and the C Standard are unclear or inconsistent about 843 ** what %C and %y do if the year is negative or exceeds 9999. 844 ** Use the convention that %C concatenated with %y yields the 845 ** same output as %Y, and that %Y contains at least 4 bytes, 846 ** with more only if necessary. 847 */ 848 849 static char * 850 _yconv(int a, int b, bool convert_top, bool convert_yy, 851 char *pt, const char * ptlim, locale_t loc) 852 { 853 int lead; 854 int trail; 855 856 int DIVISOR = 100; 857 trail = a % DIVISOR + b % DIVISOR; 858 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; 859 trail %= DIVISOR; 860 if (trail < 0 && lead > 0) { 861 trail += DIVISOR; 862 --lead; 863 } else if (lead < 0 && trail > 0) { 864 trail -= DIVISOR; 865 ++lead; 866 } 867 if (convert_top) { 868 if (lead == 0 && trail < 0) 869 pt = _add("-0", pt, ptlim); 870 else pt = _conv(lead, "%02d", pt, ptlim, loc); 871 } 872 if (convert_yy) 873 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim, 874 loc); 875 return pt; 876 } 877