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