1 /* $NetBSD: strftime.c,v 1.39 2017/03/11 18:23:14 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.39 2017/03/11 18:23:14 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 71 #ifdef __weak_alias 72 __weak_alias(strftime_l, _strftime_l) 73 __weak_alias(strftime_lz, _strftime_lz) 74 __weak_alias(strftime_z, _strftime_z) 75 #endif 76 77 #include "sys/localedef.h" 78 #define _TIME_LOCALE(loc) \ 79 ((_TimeLocale *)((loc)->part_impl[(size_t)LC_TIME])) 80 #define c_fmt d_t_fmt 81 82 static char * _add(const char *, char *, const char *); 83 static char * _conv(int, const char *, char *, const char *); 84 static char * _fmt(const timezone_t, const char *, const struct tm *, char *, 85 const char *, int *, locale_t); 86 static char * _yconv(int, int, bool, bool, char *, const char *); 87 88 #if !HAVE_POSIX_DECLS 89 extern char * tzname[]; 90 #endif 91 92 #ifndef YEAR_2000_NAME 93 #define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" 94 #endif /* !defined YEAR_2000_NAME */ 95 96 #define IN_NONE 0 97 #define IN_SOME 1 98 #define IN_THIS 2 99 #define IN_ALL 3 100 101 size_t 102 strftime_z(const timezone_t sp, char * __restrict s, size_t maxsize, 103 const char * __restrict format, const struct tm * __restrict t) 104 { 105 return strftime_lz(sp, s, maxsize, format, t, _current_locale()); 106 } 107 108 #if HAVE_STRFTIME_L 109 size_t 110 strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t, 111 locale_t locale) 112 { 113 /* Just call strftime, as only the C locale is supported. */ 114 return strftime(s, maxsize, format, t); 115 } 116 #endif 117 118 size_t 119 strftime_lz(const timezone_t sp, char *const s, const size_t maxsize, 120 const char *const format, const struct tm *const t, locale_t loc) 121 { 122 char * p; 123 int warn; 124 125 warn = IN_NONE; 126 p = _fmt(sp, format, t, s, s + maxsize, &warn, loc); 127 #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU 128 if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) { 129 (void) fprintf(stderr, "\n"); 130 (void) fprintf(stderr, "strftime format \"%s\" ", format); 131 (void) fprintf(stderr, "yields only two digits of years in "); 132 if (warn == IN_SOME) 133 (void) fprintf(stderr, "some locales"); 134 else if (warn == IN_THIS) 135 (void) fprintf(stderr, "the current locale"); 136 else (void) fprintf(stderr, "all locales"); 137 (void) fprintf(stderr, "\n"); 138 } 139 #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */ 140 if (p == s + maxsize) 141 return 0; 142 *p = '\0'; 143 return p - s; 144 } 145 146 static char * 147 _fmt(const timezone_t sp, const char *format, const struct tm *t, char *pt, 148 const char *ptlim, int *warnp, locale_t loc) 149 { 150 for ( ; *format; ++format) { 151 if (*format == '%') { 152 label: 153 switch (*++format) { 154 case '\0': 155 --format; 156 break; 157 case 'A': 158 pt = _add((t->tm_wday < 0 || 159 t->tm_wday >= DAYSPERWEEK) ? 160 "?" : _TIME_LOCALE(loc)->day[t->tm_wday], 161 pt, ptlim); 162 continue; 163 case 'a': 164 pt = _add((t->tm_wday < 0 || 165 t->tm_wday >= DAYSPERWEEK) ? 166 "?" : _TIME_LOCALE(loc)->abday[t->tm_wday], 167 pt, ptlim); 168 continue; 169 case 'B': 170 pt = _add((t->tm_mon < 0 || 171 t->tm_mon >= MONSPERYEAR) ? 172 "?" : _TIME_LOCALE(loc)->mon[t->tm_mon], 173 pt, ptlim); 174 continue; 175 case 'b': 176 case 'h': 177 pt = _add((t->tm_mon < 0 || 178 t->tm_mon >= MONSPERYEAR) ? 179 "?" : _TIME_LOCALE(loc)->abmon[t->tm_mon], 180 pt, ptlim); 181 continue; 182 case 'C': 183 /* 184 ** %C used to do a... 185 ** _fmt("%a %b %e %X %Y", t); 186 ** ...whereas now POSIX 1003.2 calls for 187 ** something completely different. 188 ** (ado, 1993-05-24) 189 */ 190 pt = _yconv(t->tm_year, TM_YEAR_BASE, 191 true, false, pt, ptlim); 192 continue; 193 case 'c': 194 { 195 int warn2 = IN_SOME; 196 197 pt = _fmt(sp, _TIME_LOCALE(loc)->c_fmt, t, pt, 198 ptlim, &warn2, loc); 199 if (warn2 == IN_ALL) 200 warn2 = IN_THIS; 201 if (warn2 > *warnp) 202 *warnp = warn2; 203 } 204 continue; 205 case 'D': 206 pt = _fmt(sp, "%m/%d/%y", t, pt, ptlim, warnp, 207 loc); 208 continue; 209 case 'd': 210 pt = _conv(t->tm_mday, "%02d", pt, ptlim); 211 continue; 212 case 'E': 213 case 'O': 214 /* 215 ** C99 locale modifiers. 216 ** The sequences 217 ** %Ec %EC %Ex %EX %Ey %EY 218 ** %Od %oe %OH %OI %Om %OM 219 ** %OS %Ou %OU %OV %Ow %OW %Oy 220 ** are supposed to provide alternate 221 ** representations. 222 */ 223 goto label; 224 case 'e': 225 pt = _conv(t->tm_mday, "%2d", pt, ptlim); 226 continue; 227 case 'F': 228 pt = _fmt(sp, "%Y-%m-%d", t, pt, ptlim, warnp, 229 loc); 230 continue; 231 case 'H': 232 pt = _conv(t->tm_hour, "%02d", pt, ptlim); 233 continue; 234 case 'I': 235 pt = _conv((t->tm_hour % 12) ? 236 (t->tm_hour % 12) : 12, 237 "%02d", pt, ptlim); 238 continue; 239 case 'j': 240 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); 241 continue; 242 case 'k': 243 /* 244 ** This used to be... 245 ** _conv(t->tm_hour % 12 ? 246 ** t->tm_hour % 12 : 12, 2, ' '); 247 ** ...and has been changed to the below to 248 ** match SunOS 4.1.1 and Arnold Robbins' 249 ** strftime version 3.0. That is, "%k" and 250 ** "%l" have been swapped. 251 ** (ado, 1993-05-24) 252 */ 253 pt = _conv(t->tm_hour, "%2d", pt, ptlim); 254 continue; 255 #ifdef KITCHEN_SINK 256 case 'K': 257 /* 258 ** After all this time, still unclaimed! 259 */ 260 pt = _add("kitchen sink", pt, ptlim); 261 continue; 262 #endif /* defined KITCHEN_SINK */ 263 case 'l': 264 /* 265 ** This used to be... 266 ** _conv(t->tm_hour, 2, ' '); 267 ** ...and has been changed to the below to 268 ** match SunOS 4.1.1 and Arnold Robbin's 269 ** strftime version 3.0. That is, "%k" and 270 ** "%l" have been swapped. 271 ** (ado, 1993-05-24) 272 */ 273 pt = _conv((t->tm_hour % 12) ? 274 (t->tm_hour % 12) : 12, 275 "%2d", pt, ptlim); 276 continue; 277 case 'M': 278 pt = _conv(t->tm_min, "%02d", pt, ptlim); 279 continue; 280 case 'm': 281 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); 282 continue; 283 case 'n': 284 pt = _add("\n", pt, ptlim); 285 continue; 286 case 'p': 287 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? 288 _TIME_LOCALE(loc)->am_pm[1] : 289 _TIME_LOCALE(loc)->am_pm[0], 290 pt, ptlim); 291 continue; 292 case 'R': 293 pt = _fmt(sp, "%H:%M", t, pt, ptlim, warnp, 294 loc); 295 continue; 296 case 'r': 297 pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt_ampm, t, 298 pt, ptlim, warnp, loc); 299 continue; 300 case 'S': 301 pt = _conv(t->tm_sec, "%02d", pt, ptlim); 302 continue; 303 case 's': 304 { 305 struct tm tm; 306 char buf[INT_STRLEN_MAXIMUM( 307 time_t) + 1]; 308 time_t mkt; 309 310 tm = *t; 311 mkt = mktime(&tm); 312 /* CONSTCOND */ 313 if (TYPE_SIGNED(time_t)) 314 (void)snprintf(buf, sizeof(buf), 315 "%jd", (intmax_t) mkt); 316 else (void)snprintf(buf, sizeof(buf), 317 "%ju", (uintmax_t) mkt); 318 pt = _add(buf, pt, ptlim); 319 } 320 continue; 321 case 'T': 322 pt = _fmt(sp, "%H:%M:%S", t, pt, ptlim, warnp, 323 loc); 324 continue; 325 case 't': 326 pt = _add("\t", pt, ptlim); 327 continue; 328 case 'U': 329 pt = _conv((t->tm_yday + DAYSPERWEEK - 330 t->tm_wday) / DAYSPERWEEK, 331 "%02d", pt, ptlim); 332 continue; 333 case 'u': 334 /* 335 ** From Arnold Robbins' strftime version 3.0: 336 ** "ISO 8601: Weekday as a decimal number 337 ** [1 (Monday) - 7]" 338 ** (ado, 1993-05-24) 339 */ 340 pt = _conv((t->tm_wday == 0) ? 341 DAYSPERWEEK : t->tm_wday, 342 "%d", pt, ptlim); 343 continue; 344 case 'V': /* ISO 8601 week number */ 345 case 'G': /* ISO 8601 year (four digits) */ 346 case 'g': /* ISO 8601 year (two digits) */ 347 /* 348 ** From Arnold Robbins' strftime version 3.0: "the week number of the 349 ** year (the first Monday as the first day of week 1) as a decimal number 350 ** (01-53)." 351 ** (ado, 1993-05-24) 352 ** 353 ** From <http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html> by Markus Kuhn: 354 ** "Week 01 of a year is per definition the first week which has the 355 ** Thursday in this year, which is equivalent to the week which contains 356 ** the fourth day of January. In other words, the first week of a new year 357 ** is the week which has the majority of its days in the new year. Week 01 358 ** might also contain days from the previous year and the week before week 359 ** 01 of a year is the last week (52 or 53) of the previous year even if 360 ** it contains days from the new year. A week starts with Monday (day 1) 361 ** and ends with Sunday (day 7). For example, the first week of the year 362 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 363 ** (ado, 1996-01-02) 364 */ 365 { 366 int year; 367 int base; 368 int yday; 369 int wday; 370 int w; 371 372 year = t->tm_year; 373 base = TM_YEAR_BASE; 374 yday = t->tm_yday; 375 wday = t->tm_wday; 376 for ( ; ; ) { 377 int len; 378 int bot; 379 int top; 380 381 len = isleap_sum(year, base) ? 382 DAYSPERLYEAR : 383 DAYSPERNYEAR; 384 /* 385 ** What yday (-3 ... 3) does 386 ** the ISO year begin on? 387 */ 388 bot = ((yday + 11 - wday) % 389 DAYSPERWEEK) - 3; 390 /* 391 ** What yday does the NEXT 392 ** ISO year begin on? 393 */ 394 top = bot - 395 (len % DAYSPERWEEK); 396 if (top < -3) 397 top += DAYSPERWEEK; 398 top += len; 399 if (yday >= top) { 400 ++base; 401 w = 1; 402 break; 403 } 404 if (yday >= bot) { 405 w = 1 + ((yday - bot) / 406 DAYSPERWEEK); 407 break; 408 } 409 --base; 410 yday += isleap_sum(year, base) ? 411 DAYSPERLYEAR : 412 DAYSPERNYEAR; 413 } 414 #ifdef XPG4_1994_04_09 415 if ((w == 52 && 416 t->tm_mon == TM_JANUARY) || 417 (w == 1 && 418 t->tm_mon == TM_DECEMBER)) 419 w = 53; 420 #endif /* defined XPG4_1994_04_09 */ 421 if (*format == 'V') 422 pt = _conv(w, "%02d", 423 pt, ptlim); 424 else if (*format == 'g') { 425 *warnp = IN_ALL; 426 pt = _yconv(year, base, 427 false, true, 428 pt, ptlim); 429 } else pt = _yconv(year, base, 430 true, true, 431 pt, ptlim); 432 } 433 continue; 434 case 'v': 435 /* 436 ** From Arnold Robbins' strftime version 3.0: 437 ** "date as dd-bbb-YYYY" 438 ** (ado, 1993-05-24) 439 */ 440 pt = _fmt(sp, "%e-%b-%Y", t, pt, ptlim, warnp, 441 loc); 442 continue; 443 case 'W': 444 pt = _conv((t->tm_yday + DAYSPERWEEK - 445 (t->tm_wday ? 446 (t->tm_wday - 1) : 447 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 448 "%02d", pt, ptlim); 449 continue; 450 case 'w': 451 pt = _conv(t->tm_wday, "%d", pt, ptlim); 452 continue; 453 case 'X': 454 pt = _fmt(sp, _TIME_LOCALE(loc)->t_fmt, t, pt, 455 ptlim, warnp, loc); 456 continue; 457 case 'x': 458 { 459 int warn2 = IN_SOME; 460 461 pt = _fmt(sp, _TIME_LOCALE(loc)->d_fmt, t, pt, 462 ptlim, &warn2, loc); 463 if (warn2 == IN_ALL) 464 warn2 = IN_THIS; 465 if (warn2 > *warnp) 466 *warnp = warn2; 467 } 468 continue; 469 case 'y': 470 *warnp = IN_ALL; 471 pt = _yconv(t->tm_year, TM_YEAR_BASE, 472 false, true, 473 pt, ptlim); 474 continue; 475 case 'Y': 476 pt = _yconv(t->tm_year, TM_YEAR_BASE, 477 true, true, 478 pt, ptlim); 479 continue; 480 case 'Z': 481 #ifdef TM_ZONE 482 pt = _add(t->TM_ZONE, pt, ptlim); 483 #endif /* defined TM_ZONE */ 484 if (t->tm_isdst >= 0) 485 pt = _add((sp ? 486 tzgetname(sp, t->tm_isdst) : 487 tzname[t->tm_isdst != 0]), 488 pt, ptlim); 489 /* 490 ** C99 says that %Z must be replaced by the 491 ** empty string if the time zone is not 492 ** determinable. 493 */ 494 continue; 495 case 'z': 496 #if defined TM_GMTOFF || defined USG_COMPAT || defined ALTZONE 497 { 498 long diff; 499 char const * sign; 500 bool negative; 501 502 if (t->tm_isdst < 0) 503 continue; 504 # ifdef TM_GMTOFF 505 diff = (int)t->TM_GMTOFF; 506 # else 507 /* 508 ** C99 says that the UT offset must 509 ** be computed by looking only at 510 ** tm_isdst. This requirement is 511 ** incorrect, since it means the code 512 ** must rely on magic (in this case 513 ** altzone and timezone), and the 514 ** magic might not have the correct 515 ** offset. Doing things correctly is 516 ** tricky and requires disobeying C99; 517 ** see GNU C strftime for details. 518 ** For now, punt and conform to the 519 ** standard, even though it's incorrect. 520 ** 521 ** C99 says that %z must be replaced by the 522 ** empty string if the time zone is not 523 ** determinable, so output nothing if the 524 ** appropriate variables are not available. 525 */ 526 # ifndef STD_INSPIRED 527 if (t->tm_isdst == 0) 528 # ifdef USG_COMPAT 529 diff = -timezone; 530 # else 531 continue; 532 # endif 533 else 534 # ifdef ALTZONE 535 diff = -altzone; 536 # else 537 continue; 538 # endif 539 # else 540 { 541 struct tm tmp; 542 time_t lct, gct; 543 544 /* 545 ** Get calendar time from t 546 ** being treated as local. 547 */ 548 tmp = *t; /* mktime discards const */ 549 lct = mktime(&tmp); 550 551 if (lct == (time_t)-1) 552 continue; 553 554 /* 555 ** Get calendar time from t 556 ** being treated as GMT. 557 **/ 558 tmp = *t; /* mktime discards const */ 559 gct = timegm(&tmp); 560 561 if (gct == (time_t)-1) 562 continue; 563 564 /* LINTED difference will fit int */ 565 diff = (intmax_t)gct - (intmax_t)lct; 566 } 567 # endif 568 # endif 569 negative = diff < 0; 570 if (diff == 0) { 571 #ifdef TM_ZONE 572 negative = t->TM_ZONE[0] == '-'; 573 #else 574 negative 575 = (t->tm_isdst < 0 576 || tzname[t->tm_isdst != 0][0] == '-'); 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