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