1 /* $NetBSD: strftime.c,v 1.42 2018/10/19 23:05: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.42 2018/10/19 23:05:35 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 *); 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 *); 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 size_t 100 strftime_z(const timezone_t sp, char * __restrict s, size_t maxsize, 101 const char * __restrict format, const struct tm * __restrict t) 102 { 103 return strftime_lz(sp, s, maxsize, format, t, _current_locale()); 104 } 105 106 #if HAVE_STRFTIME_L 107 size_t 108 strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t, 109 locale_t locale) 110 { 111 /* Just call strftime, as only the C locale is supported. */ 112 return strftime(s, maxsize, format, t); 113 } 114 #endif 115 116 size_t 117 strftime_lz(const timezone_t sp, char *const s, const size_t maxsize, 118 const char *const format, const struct tm *const t, locale_t loc) 119 { 120 char * p; 121 enum warn warn = IN_NONE; 122 123 p = _fmt(sp, format, t, s, s + maxsize, &warn, loc); 124 if (/*CONSTCOND*/DEPRECATE_TWO_DIGIT_YEARS 125 && warn != IN_NONE && getenv(YEAR_2000_NAME)) { 126 (void) fprintf(stderr, "\n"); 127 (void) fprintf(stderr, "strftime format \"%s\" ", format); 128 (void) fprintf(stderr, "yields only two digits of years in "); 129 if (warn == IN_SOME) 130 (void) fprintf(stderr, "some locales"); 131 else if (warn == IN_THIS) 132 (void) fprintf(stderr, "the current locale"); 133 else (void) fprintf(stderr, "all locales"); 134 (void) fprintf(stderr, "\n"); 135 } 136 if (p == s + maxsize) 137 return 0; 138 *p = '\0'; 139 return p - s; 140 } 141 142 static char * 143 _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt, 144 const char *ptlim, enum warn *warnp, locale_t loc) 145 { 146 for ( ; *format; ++format) { 147 if (*format == '%') { 148 label: 149 switch (*++format) { 150 case '\0': 151 --format; 152 break; 153 case 'A': 154 pt = _add((t->tm_wday < 0 || 155 t->tm_wday >= DAYSPERWEEK) ? 156 "?" : _TIME_LOCALE(loc)->day[t->tm_wday], 157 pt, ptlim); 158 continue; 159 case 'a': 160 pt = _add((t->tm_wday < 0 || 161 t->tm_wday >= DAYSPERWEEK) ? 162 "?" : _TIME_LOCALE(loc)->abday[t->tm_wday], 163 pt, ptlim); 164 continue; 165 case 'B': 166 pt = _add((t->tm_mon < 0 || 167 t->tm_mon >= MONSPERYEAR) ? 168 "?" : _TIME_LOCALE(loc)->mon[t->tm_mon], 169 pt, ptlim); 170 continue; 171 case 'b': 172 case 'h': 173 pt = _add((t->tm_mon < 0 || 174 t->tm_mon >= MONSPERYEAR) ? 175 "?" : _TIME_LOCALE(loc)->abmon[t->tm_mon], 176 pt, ptlim); 177 continue; 178 case 'C': 179 /* 180 ** %C used to do a... 181 ** _fmt("%a %b %e %X %Y", t); 182 ** ...whereas now POSIX 1003.2 calls for 183 ** something completely different. 184 ** (ado, 1993-05-24) 185 */ 186 pt = _yconv(t->tm_year, TM_YEAR_BASE, 187 true, false, pt, ptlim); 188 continue; 189 case 'c': 190 { 191 enum warn warn2 = IN_SOME; 192 193 pt = _fmt(sp, _TIME_LOCALE(loc)->c_fmt, t, pt, 194 ptlim, &warn2, loc); 195 if (warn2 == IN_ALL) 196 warn2 = IN_THIS; 197 if (warn2 > *warnp) 198 *warnp = warn2; 199 } 200 continue; 201 case 'D': 202 pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp, 203 loc); 204 continue; 205 case 'd': 206 pt = _conv(t->tm_mday, "%02d", pt, ptlim); 207 continue; 208 case 'E': 209 case 'O': 210 /* 211 ** Locale modifiers of C99 and later. 212 ** The sequences 213 ** %Ec %EC %Ex %EX %Ey %EY 214 ** %Od %oe %OH %OI %Om %OM 215 ** %OS %Ou %OU %OV %Ow %OW %Oy 216 ** are supposed to provide alternative 217 ** representations. 218 */ 219 goto label; 220 case 'e': 221 pt = _conv(t->tm_mday, "%2d", pt, ptlim); 222 continue; 223 case 'F': 224 pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp, 225 loc); 226 continue; 227 case 'H': 228 pt = _conv(t->tm_hour, "%02d", pt, ptlim); 229 continue; 230 case 'I': 231 pt = _conv((t->tm_hour % 12) ? 232 (t->tm_hour % 12) : 12, 233 "%02d", pt, ptlim); 234 continue; 235 case 'j': 236 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); 237 continue; 238 case 'k': 239 /* 240 ** This used to be... 241 ** _conv(t->tm_hour % 12 ? 242 ** t->tm_hour % 12 : 12, 2, ' '); 243 ** ...and has been changed to the below to 244 ** match SunOS 4.1.1 and Arnold Robbins' 245 ** strftime version 3.0. That is, "%k" and 246 ** "%l" have been swapped. 247 ** (ado, 1993-05-24) 248 */ 249 pt = _conv(t->tm_hour, "%2d", pt, ptlim); 250 continue; 251 #ifdef KITCHEN_SINK 252 case 'K': 253 /* 254 ** After all this time, still unclaimed! 255 */ 256 pt = _add("kitchen sink", pt, ptlim); 257 continue; 258 #endif /* defined KITCHEN_SINK */ 259 case 'l': 260 /* 261 ** This used to be... 262 ** _conv(t->tm_hour, 2, ' '); 263 ** ...and has been changed to the below to 264 ** match SunOS 4.1.1 and Arnold Robbin's 265 ** strftime version 3.0. That is, "%k" and 266 ** "%l" have been swapped. 267 ** (ado, 1993-05-24) 268 */ 269 pt = _conv((t->tm_hour % 12) ? 270 (t->tm_hour % 12) : 12, 271 "%2d", pt, ptlim); 272 continue; 273 case 'M': 274 pt = _conv(t->tm_min, "%02d", pt, ptlim); 275 continue; 276 case 'm': 277 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); 278 continue; 279 case 'n': 280 pt = _add("\n", pt, ptlim); 281 continue; 282 case 'p': 283 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? 284 _TIME_LOCALE(loc)->am_pm[1] : 285 _TIME_LOCALE(loc)->am_pm[0], 286 pt, ptlim); 287 continue; 288 case 'R': 289 pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp, 290 loc); 291 continue; 292 case 'r': 293 pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt_ampm, t, 294 pt, ptlim, warnp, loc); 295 continue; 296 case 'S': 297 pt = _conv(t->tm_sec, "%02d", pt, ptlim); 298 continue; 299 case 's': 300 { 301 struct tm tm; 302 char buf[INT_STRLEN_MAXIMUM( 303 time_t) + 1]; 304 time_t mkt; 305 306 tm = *t; 307 mkt = mktime(&tm); 308 /* CONSTCOND */ 309 if (TYPE_SIGNED(time_t)) 310 (void)snprintf(buf, sizeof(buf), 311 "%jd", (intmax_t) mkt); 312 else (void)snprintf(buf, sizeof(buf), 313 "%ju", (uintmax_t) mkt); 314 pt = _add(buf, pt, ptlim); 315 } 316 continue; 317 case 'T': 318 pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp, 319 loc); 320 continue; 321 case 't': 322 pt = _add("\t", pt, ptlim); 323 continue; 324 case 'U': 325 pt = _conv((t->tm_yday + DAYSPERWEEK - 326 t->tm_wday) / DAYSPERWEEK, 327 "%02d", pt, ptlim); 328 continue; 329 case 'u': 330 /* 331 ** From Arnold Robbins' strftime version 3.0: 332 ** "ISO 8601: Weekday as a decimal number 333 ** [1 (Monday) - 7]" 334 ** (ado, 1993-05-24) 335 */ 336 pt = _conv((t->tm_wday == 0) ? 337 DAYSPERWEEK : t->tm_wday, 338 "%d", pt, ptlim); 339 continue; 340 case 'V': /* ISO 8601 week number */ 341 case 'G': /* ISO 8601 year (four digits) */ 342 case 'g': /* ISO 8601 year (two digits) */ 343 /* 344 ** From Arnold Robbins' strftime version 3.0: "the week number of the 345 ** year (the first Monday as the first day of week 1) as a decimal number 346 ** (01-53)." 347 ** (ado, 1993-05-24) 348 ** 349 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn: 350 ** "Week 01 of a year is per definition the first week which has the 351 ** Thursday in this year, which is equivalent to the week which contains 352 ** the fourth day of January. In other words, the first week of a new year 353 ** is the week which has the majority of its days in the new year. Week 01 354 ** might also contain days from the previous year and the week before week 355 ** 01 of a year is the last week (52 or 53) of the previous year even if 356 ** it contains days from the new year. A week starts with Monday (day 1) 357 ** and ends with Sunday (day 7). For example, the first week of the year 358 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 359 ** (ado, 1996-01-02) 360 */ 361 { 362 int year; 363 int base; 364 int yday; 365 int wday; 366 int w; 367 368 year = t->tm_year; 369 base = TM_YEAR_BASE; 370 yday = t->tm_yday; 371 wday = t->tm_wday; 372 for ( ; ; ) { 373 int len; 374 int bot; 375 int top; 376 377 len = isleap_sum(year, base) ? 378 DAYSPERLYEAR : 379 DAYSPERNYEAR; 380 /* 381 ** What yday (-3 ... 3) does 382 ** the ISO year begin on? 383 */ 384 bot = ((yday + 11 - wday) % 385 DAYSPERWEEK) - 3; 386 /* 387 ** What yday does the NEXT 388 ** ISO year begin on? 389 */ 390 top = bot - 391 (len % DAYSPERWEEK); 392 if (top < -3) 393 top += DAYSPERWEEK; 394 top += len; 395 if (yday >= top) { 396 ++base; 397 w = 1; 398 break; 399 } 400 if (yday >= bot) { 401 w = 1 + ((yday - bot) / 402 DAYSPERWEEK); 403 break; 404 } 405 --base; 406 yday += isleap_sum(year, base) ? 407 DAYSPERLYEAR : 408 DAYSPERNYEAR; 409 } 410 #ifdef XPG4_1994_04_09 411 if ((w == 52 && 412 t->tm_mon == TM_JANUARY) || 413 (w == 1 && 414 t->tm_mon == TM_DECEMBER)) 415 w = 53; 416 #endif /* defined XPG4_1994_04_09 */ 417 if (*format == 'V') 418 pt = _conv(w, "%02d", 419 pt, ptlim); 420 else if (*format == 'g') { 421 *warnp = IN_ALL; 422 pt = _yconv(year, base, 423 false, true, 424 pt, ptlim); 425 } else pt = _yconv(year, base, 426 true, true, 427 pt, ptlim); 428 } 429 continue; 430 case 'v': 431 /* 432 ** From Arnold Robbins' strftime version 3.0: 433 ** "date as dd-bbb-YYYY" 434 ** (ado, 1993-05-24) 435 */ 436 pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp, 437 loc); 438 continue; 439 case 'W': 440 pt = _conv((t->tm_yday + DAYSPERWEEK - 441 (t->tm_wday ? 442 (t->tm_wday - 1) : 443 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 444 "%02d", pt, ptlim); 445 continue; 446 case 'w': 447 pt = _conv(t->tm_wday, "%d", pt, ptlim); 448 continue; 449 case 'X': 450 pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt, t, pt, 451 ptlim, warnp, loc); 452 continue; 453 case 'x': 454 { 455 enum warn warn2 = IN_SOME; 456 457 pt = _fmt(sp, _TIME_LOCALE(loc)->d_fmt, t, pt, 458 ptlim, &warn2, loc); 459 if (warn2 == IN_ALL) 460 warn2 = IN_THIS; 461 if (warn2 > *warnp) 462 *warnp = warn2; 463 } 464 continue; 465 case 'y': 466 *warnp = IN_ALL; 467 pt = _yconv(t->tm_year, TM_YEAR_BASE, 468 false, true, 469 pt, ptlim); 470 continue; 471 case 'Y': 472 pt = _yconv(t->tm_year, TM_YEAR_BASE, 473 true, true, 474 pt, ptlim); 475 continue; 476 case 'Z': 477 #ifdef TM_ZONE 478 pt = _add(t->TM_ZONE, pt, ptlim); 479 #elif HAVE_TZNAME 480 if (t->tm_isdst >= 0) 481 pt = _add((sp ? 482 tzgetname(sp, t->tm_isdst) : 483 tzname[t->tm_isdst != 0]), 484 pt, ptlim); 485 #endif 486 /* 487 ** C99 and later say that %Z must be 488 ** replaced by the empty string if the 489 ** time zone abbreviation is not 490 ** determinable. 491 */ 492 continue; 493 case 'z': 494 #if defined TM_GMTOFF || USG_COMPAT || defined ALTZONE 495 { 496 long diff; 497 char const * sign; 498 bool negative; 499 500 if (t->tm_isdst < 0) 501 continue; 502 # ifdef TM_GMTOFF 503 diff = (int)t->TM_GMTOFF; 504 # else 505 /* 506 ** C99 and later say that the UT offset must 507 ** be computed by looking only at 508 ** tm_isdst. This requirement is 509 ** incorrect, since it means the code 510 ** must rely on magic (in this case 511 ** altzone and timezone), and the 512 ** magic might not have the correct 513 ** offset. Doing things correctly is 514 ** tricky and requires disobeying the standard; 515 ** see GNU C strftime for details. 516 ** For now, punt and conform to the 517 ** standard, even though it's incorrect. 518 ** 519 ** C99 and later say that %z must be replaced by 520 ** the empty string if the time zone is not 521 ** determinable, so output nothing if the 522 ** appropriate variables are not available. 523 */ 524 # ifndef STD_INSPIRED 525 if (t->tm_isdst == 0) 526 # if USG_COMPAT 527 diff = -timezone; 528 # else 529 continue; 530 # endif 531 else 532 # ifdef ALTZONE 533 diff = -altzone; 534 # else 535 continue; 536 # endif 537 # else 538 { 539 struct tm tmp; 540 time_t lct, gct; 541 542 /* 543 ** Get calendar time from t 544 ** being treated as local. 545 */ 546 tmp = *t; /* mktime discards const */ 547 lct = mktime(&tmp); 548 549 if (lct == (time_t)-1) 550 continue; 551 552 /* 553 ** Get calendar time from t 554 ** being treated as GMT. 555 **/ 556 tmp = *t; /* mktime discards const */ 557 gct = timegm(&tmp); 558 559 if (gct == (time_t)-1) 560 continue; 561 562 /* LINTED difference will fit int */ 563 diff = (intmax_t)gct - (intmax_t)lct; 564 } 565 # endif 566 # endif 567 negative = diff < 0; 568 if (diff == 0) { 569 #ifdef TM_ZONE 570 negative = t->TM_ZONE[0] == '-'; 571 #else 572 negative = t->tm_isdst < 0; 573 # if HAVE_TZNAME 574 if (tzname[t->tm_isdst != 0][0] == '-') 575 negative = true; 576 # endif 577 #endif 578 } 579 if (negative) { 580 sign = "-"; 581 diff = -diff; 582 } else sign = "+"; 583 pt = _add(sign, pt, ptlim); 584 diff /= SECSPERMIN; 585 diff = (diff / MINSPERHOUR) * 100 + 586 (diff % MINSPERHOUR); 587 _DIAGASSERT(__type_fit(int, diff)); 588 pt = _conv((int)diff, "%04d", pt, ptlim); 589 } 590 #endif 591 continue; 592 #if 0 593 case '+': 594 pt = _fmt(sp, _TIME_LOCALE(loc)->date_fmt, t, 595 pt, ptlim, warnp, loc); 596 continue; 597 #endif 598 case '%': 599 /* 600 ** X311J/88-090 (4.12.3.5): if conversion char is 601 ** undefined, behavior is undefined. Print out the 602 ** character itself as printf(3) also does. 603 */ 604 default: 605 break; 606 } 607 } 608 if (pt == ptlim) 609 break; 610 *pt++ = *format; 611 } 612 return pt; 613 } 614 615 size_t 616 strftime(char *s, size_t maxsize, const char *format, const struct tm *t) 617 { 618 tzset(); 619 return strftime_z(NULL, s, maxsize, format, t); 620 } 621 622 size_t 623 strftime_l(char * __restrict s, size_t maxsize, const char * __restrict format, 624 const struct tm * __restrict t, locale_t loc) 625 { 626 tzset(); 627 return strftime_lz(NULL, s, maxsize, format, t, loc); 628 } 629 630 static char * 631 _conv(int n, const char *format, char *pt, const char *ptlim) 632 { 633 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 634 635 (void) snprintf(buf, sizeof(buf), format, n); 636 return _add(buf, pt, ptlim); 637 } 638 639 static char * 640 _add(const char *str, char *pt, const char *ptlim) 641 { 642 while (pt < ptlim && (*pt = *str++) != '\0') 643 ++pt; 644 return pt; 645 } 646 647 /* 648 ** POSIX and the C Standard are unclear or inconsistent about 649 ** what %C and %y do if the year is negative or exceeds 9999. 650 ** Use the convention that %C concatenated with %y yields the 651 ** same output as %Y, and that %Y contains at least 4 bytes, 652 ** with more only if necessary. 653 */ 654 655 static char * 656 _yconv(int a, int b, bool convert_top, bool convert_yy, 657 char *pt, const char * ptlim) 658 { 659 int lead; 660 int trail; 661 662 #define DIVISOR 100 663 trail = a % DIVISOR + b % DIVISOR; 664 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; 665 trail %= DIVISOR; 666 if (trail < 0 && lead > 0) { 667 trail += DIVISOR; 668 --lead; 669 } else if (lead < 0 && trail > 0) { 670 trail -= DIVISOR; 671 ++lead; 672 } 673 if (convert_top) { 674 if (lead == 0 && trail < 0) 675 pt = _add("-0", pt, ptlim); 676 else pt = _conv(lead, "%02d", pt, ptlim); 677 } 678 if (convert_yy) 679 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); 680 return pt; 681 } 682