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