1 /* Convert a broken-down timestamp to a string. */ 2 3 /* Copyright 1989 The Regents of the University of California. 4 All rights reserved. 5 6 Redistribution and use in source and binary forms, with or without 7 modification, are permitted provided that the following conditions 8 are met: 9 1. Redistributions of source code must retain the above copyright 10 notice, this list of conditions and the following disclaimer. 11 2. Redistributions in binary form must reproduce the above copyright 12 notice, this list of conditions and the following disclaimer in the 13 documentation and/or other materials provided with the distribution. 14 3. Neither the name of the University nor the names of its contributors 15 may be used to endorse or promote products derived from this software 16 without specific prior written permission. 17 18 THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND 19 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 22 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 SUCH DAMAGE. */ 29 30 /* 31 ** Based on the UCB version with the copyright notice appearing above. 32 ** 33 ** This is ANSIish only when "multibyte character == plain character". 34 */ 35 36 #include "private.h" 37 38 #include <fcntl.h> 39 #include <locale.h> 40 #include <stdio.h> 41 42 #ifndef DEPRECATE_TWO_DIGIT_YEARS 43 # define DEPRECATE_TWO_DIGIT_YEARS false 44 #endif 45 46 struct lc_time_T { 47 const char * mon[MONSPERYEAR]; 48 const char * month[MONSPERYEAR]; 49 const char * wday[DAYSPERWEEK]; 50 const char * weekday[DAYSPERWEEK]; 51 const char * X_fmt; 52 const char * x_fmt; 53 const char * c_fmt; 54 const char * am; 55 const char * pm; 56 const char * date_fmt; 57 }; 58 59 static const struct lc_time_T C_time_locale = { 60 { 61 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 62 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 63 }, { 64 "January", "February", "March", "April", "May", "June", 65 "July", "August", "September", "October", "November", "December" 66 }, { 67 "Sun", "Mon", "Tue", "Wed", 68 "Thu", "Fri", "Sat" 69 }, { 70 "Sunday", "Monday", "Tuesday", "Wednesday", 71 "Thursday", "Friday", "Saturday" 72 }, 73 74 /* X_fmt */ 75 "%H:%M:%S", 76 77 /* 78 ** x_fmt 79 ** C99 and later require this format. 80 ** Using just numbers (as here) makes Quakers happier; 81 ** it's also compatible with SVR4. 82 */ 83 "%m/%d/%y", 84 85 /* 86 ** c_fmt 87 ** C99 and later require this format. 88 ** Previously this code used "%D %X", but we now conform to C99. 89 ** Note that 90 ** "%a %b %d %H:%M:%S %Y" 91 ** is used by Solaris 2.3. 92 */ 93 "%a %b %e %T %Y", 94 95 /* am */ 96 "AM", 97 98 /* pm */ 99 "PM", 100 101 /* date_fmt */ 102 "%a %b %e %H:%M:%S %Z %Y" 103 }; 104 105 enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL }; 106 107 static char * _add(const char *, char *, const char *); 108 static char * _conv(int, const char *, char *, const char *); 109 static char * _fmt(const char *, const struct tm *, char *, const char *, 110 enum warn *); 111 static char * _yconv(int, int, bool, bool, char *, char const *); 112 113 #ifndef YEAR_2000_NAME 114 # define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" 115 #endif /* !defined YEAR_2000_NAME */ 116 117 #if HAVE_STRFTIME_L 118 size_t 119 strftime_l(char *restrict s, size_t maxsize, char const *restrict format, 120 struct tm const *restrict t, 121 ATTRIBUTE_MAYBE_UNUSED locale_t locale) 122 { 123 /* Just call strftime, as only the C locale is supported. */ 124 return strftime(s, maxsize, format, t); 125 } 126 #endif 127 128 size_t 129 strftime(char *restrict s, size_t maxsize, char const *restrict format, 130 struct tm const *restrict t) 131 { 132 char * p; 133 int saved_errno = errno; 134 enum warn warn = IN_NONE; 135 136 tzset(); 137 p = _fmt(format, t, s, s + maxsize, &warn); 138 if (!p) { 139 errno = EOVERFLOW; 140 return 0; 141 } 142 if (DEPRECATE_TWO_DIGIT_YEARS 143 && warn != IN_NONE && getenv(YEAR_2000_NAME)) { 144 fprintf(stderr, "\n"); 145 fprintf(stderr, "strftime format \"%s\" ", format); 146 fprintf(stderr, "yields only two digits of years in "); 147 if (warn == IN_SOME) 148 fprintf(stderr, "some locales"); 149 else if (warn == IN_THIS) 150 fprintf(stderr, "the current locale"); 151 else fprintf(stderr, "all locales"); 152 fprintf(stderr, "\n"); 153 } 154 if (p == s + maxsize) { 155 errno = ERANGE; 156 return 0; 157 } 158 *p = '\0'; 159 errno = saved_errno; 160 return p - s; 161 } 162 163 static char * 164 _fmt(const char *format, const struct tm *t, char *pt, 165 const char *ptlim, enum warn *warnp) 166 { 167 struct lc_time_T const *Locale = &C_time_locale; 168 169 for ( ; *format; ++format) { 170 if (*format == '%') { 171 label: 172 switch (*++format) { 173 case '\0': 174 --format; 175 break; 176 case 'A': 177 pt = _add((t->tm_wday < 0 || 178 t->tm_wday >= DAYSPERWEEK) ? 179 "?" : Locale->weekday[t->tm_wday], 180 pt, ptlim); 181 continue; 182 case 'a': 183 pt = _add((t->tm_wday < 0 || 184 t->tm_wday >= DAYSPERWEEK) ? 185 "?" : Locale->wday[t->tm_wday], 186 pt, ptlim); 187 continue; 188 case 'B': 189 pt = _add((t->tm_mon < 0 || 190 t->tm_mon >= MONSPERYEAR) ? 191 "?" : Locale->month[t->tm_mon], 192 pt, ptlim); 193 continue; 194 case 'b': 195 case 'h': 196 pt = _add((t->tm_mon < 0 || 197 t->tm_mon >= MONSPERYEAR) ? 198 "?" : Locale->mon[t->tm_mon], 199 pt, ptlim); 200 continue; 201 case 'C': 202 /* 203 ** %C used to do a... 204 ** _fmt("%a %b %e %X %Y", t); 205 ** ...whereas now POSIX 1003.2 calls for 206 ** something completely different. 207 ** (ado, 1993-05-24) 208 */ 209 pt = _yconv(t->tm_year, TM_YEAR_BASE, 210 true, false, pt, ptlim); 211 continue; 212 case 'c': 213 { 214 enum warn warn2 = IN_SOME; 215 216 pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2); 217 if (warn2 == IN_ALL) 218 warn2 = IN_THIS; 219 if (warn2 > *warnp) 220 *warnp = warn2; 221 } 222 continue; 223 case 'D': 224 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp); 225 continue; 226 case 'd': 227 pt = _conv(t->tm_mday, "%02d", pt, ptlim); 228 continue; 229 case 'E': 230 case 'O': 231 /* 232 ** Locale modifiers of C99 and later. 233 ** The sequences 234 ** %Ec %EC %Ex %EX %Ey %EY 235 ** %Od %oe %OH %OI %Om %OM 236 ** %OS %Ou %OU %OV %Ow %OW %Oy 237 ** are supposed to provide alternative 238 ** representations. 239 */ 240 goto label; 241 case 'e': 242 pt = _conv(t->tm_mday, "%2d", pt, ptlim); 243 continue; 244 case 'F': 245 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp); 246 continue; 247 case 'H': 248 pt = _conv(t->tm_hour, "%02d", pt, ptlim); 249 continue; 250 case 'I': 251 pt = _conv((t->tm_hour % 12) ? 252 (t->tm_hour % 12) : 12, 253 "%02d", pt, ptlim); 254 continue; 255 case 'j': 256 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); 257 continue; 258 case 'k': 259 /* 260 ** This used to be... 261 ** _conv(t->tm_hour % 12 ? 262 ** t->tm_hour % 12 : 12, 2, ' '); 263 ** ...and has been changed to the below to 264 ** match SunOS 4.1.1 and Arnold Robbins' 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, "%2d", pt, ptlim); 270 continue; 271 #ifdef KITCHEN_SINK 272 case 'K': 273 /* 274 ** After all this time, still unclaimed! 275 */ 276 pt = _add("kitchen sink", pt, ptlim); 277 continue; 278 #endif /* defined KITCHEN_SINK */ 279 case 'l': 280 /* 281 ** This used to be... 282 ** _conv(t->tm_hour, 2, ' '); 283 ** ...and has been changed to the below to 284 ** match SunOS 4.1.1 and Arnold Robbin's 285 ** strftime version 3.0. That is, "%k" and 286 ** "%l" have been swapped. 287 ** (ado, 1993-05-24) 288 */ 289 pt = _conv((t->tm_hour % 12) ? 290 (t->tm_hour % 12) : 12, 291 "%2d", pt, ptlim); 292 continue; 293 case 'M': 294 pt = _conv(t->tm_min, "%02d", pt, ptlim); 295 continue; 296 case 'm': 297 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); 298 continue; 299 case 'n': 300 pt = _add("\n", pt, ptlim); 301 continue; 302 case 'p': 303 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? 304 Locale->pm : 305 Locale->am, 306 pt, ptlim); 307 continue; 308 case 'R': 309 pt = _fmt("%H:%M", t, pt, ptlim, warnp); 310 continue; 311 case 'r': 312 pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp); 313 continue; 314 case 'S': 315 pt = _conv(t->tm_sec, "%02d", pt, ptlim); 316 continue; 317 case 's': 318 { 319 struct tm tm; 320 char buf[INT_STRLEN_MAXIMUM( 321 time_t) + 1]; 322 time_t mkt; 323 324 tm.tm_sec = t->tm_sec; 325 tm.tm_min = t->tm_min; 326 tm.tm_hour = t->tm_hour; 327 tm.tm_mday = t->tm_mday; 328 tm.tm_mon = t->tm_mon; 329 tm.tm_year = t->tm_year; 330 tm.tm_isdst = t->tm_isdst; 331 #if defined TM_GMTOFF && ! UNINIT_TRAP 332 tm.TM_GMTOFF = t->TM_GMTOFF; 333 #endif 334 mkt = mktime(&tm); 335 /* If mktime fails, %s expands to the 336 value of (time_t) -1 as a failure 337 marker; this is better in practice 338 than strftime failing. */ 339 if (TYPE_SIGNED(time_t)) { 340 intmax_t n = mkt; 341 sprintf(buf, "%"PRIdMAX, n); 342 } else { 343 uintmax_t n = mkt; 344 sprintf(buf, "%"PRIuMAX, n); 345 } 346 pt = _add(buf, pt, ptlim); 347 } 348 continue; 349 case 'T': 350 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp); 351 continue; 352 case 't': 353 pt = _add("\t", pt, ptlim); 354 continue; 355 case 'U': 356 pt = _conv((t->tm_yday + DAYSPERWEEK - 357 t->tm_wday) / DAYSPERWEEK, 358 "%02d", pt, ptlim); 359 continue; 360 case 'u': 361 /* 362 ** From Arnold Robbins' strftime version 3.0: 363 ** "ISO 8601: Weekday as a decimal number 364 ** [1 (Monday) - 7]" 365 ** (ado, 1993-05-24) 366 */ 367 pt = _conv((t->tm_wday == 0) ? 368 DAYSPERWEEK : t->tm_wday, 369 "%d", pt, ptlim); 370 continue; 371 case 'V': /* ISO 8601 week number */ 372 case 'G': /* ISO 8601 year (four digits) */ 373 case 'g': /* ISO 8601 year (two digits) */ 374 /* 375 ** From Arnold Robbins' strftime version 3.0: "the week number of the 376 ** year (the first Monday as the first day of week 1) as a decimal number 377 ** (01-53)." 378 ** (ado, 1993-05-24) 379 ** 380 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn: 381 ** "Week 01 of a year is per definition the first week which has the 382 ** Thursday in this year, which is equivalent to the week which contains 383 ** the fourth day of January. In other words, the first week of a new year 384 ** is the week which has the majority of its days in the new year. Week 01 385 ** might also contain days from the previous year and the week before week 386 ** 01 of a year is the last week (52 or 53) of the previous year even if 387 ** it contains days from the new year. A week starts with Monday (day 1) 388 ** and ends with Sunday (day 7). For example, the first week of the year 389 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 390 ** (ado, 1996-01-02) 391 */ 392 { 393 int year; 394 int base; 395 int yday; 396 int wday; 397 int w; 398 399 year = t->tm_year; 400 base = TM_YEAR_BASE; 401 yday = t->tm_yday; 402 wday = t->tm_wday; 403 for ( ; ; ) { 404 int len; 405 int bot; 406 int top; 407 408 len = isleap_sum(year, base) ? 409 DAYSPERLYEAR : 410 DAYSPERNYEAR; 411 /* 412 ** What yday (-3 ... 3) does 413 ** the ISO year begin on? 414 */ 415 bot = ((yday + 11 - wday) % 416 DAYSPERWEEK) - 3; 417 /* 418 ** What yday does the NEXT 419 ** ISO year begin on? 420 */ 421 top = bot - 422 (len % DAYSPERWEEK); 423 if (top < -3) 424 top += DAYSPERWEEK; 425 top += len; 426 if (yday >= top) { 427 ++base; 428 w = 1; 429 break; 430 } 431 if (yday >= bot) { 432 w = 1 + ((yday - bot) / 433 DAYSPERWEEK); 434 break; 435 } 436 --base; 437 yday += isleap_sum(year, base) ? 438 DAYSPERLYEAR : 439 DAYSPERNYEAR; 440 } 441 #ifdef XPG4_1994_04_09 442 if ((w == 52 && 443 t->tm_mon == TM_JANUARY) || 444 (w == 1 && 445 t->tm_mon == TM_DECEMBER)) 446 w = 53; 447 #endif /* defined XPG4_1994_04_09 */ 448 if (*format == 'V') 449 pt = _conv(w, "%02d", 450 pt, ptlim); 451 else if (*format == 'g') { 452 *warnp = IN_ALL; 453 pt = _yconv(year, base, 454 false, true, 455 pt, ptlim); 456 } else pt = _yconv(year, base, 457 true, true, 458 pt, ptlim); 459 } 460 continue; 461 case 'v': 462 /* 463 ** From Arnold Robbins' strftime version 3.0: 464 ** "date as dd-bbb-YYYY" 465 ** (ado, 1993-05-24) 466 */ 467 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp); 468 continue; 469 case 'W': 470 pt = _conv((t->tm_yday + DAYSPERWEEK - 471 (t->tm_wday ? 472 (t->tm_wday - 1) : 473 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 474 "%02d", pt, ptlim); 475 continue; 476 case 'w': 477 pt = _conv(t->tm_wday, "%d", pt, ptlim); 478 continue; 479 case 'X': 480 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); 481 continue; 482 case 'x': 483 { 484 enum warn warn2 = IN_SOME; 485 486 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); 487 if (warn2 == IN_ALL) 488 warn2 = IN_THIS; 489 if (warn2 > *warnp) 490 *warnp = warn2; 491 } 492 continue; 493 case 'y': 494 *warnp = IN_ALL; 495 pt = _yconv(t->tm_year, TM_YEAR_BASE, 496 false, true, 497 pt, ptlim); 498 continue; 499 case 'Y': 500 pt = _yconv(t->tm_year, TM_YEAR_BASE, 501 true, true, 502 pt, ptlim); 503 continue; 504 case 'Z': 505 #ifdef TM_ZONE 506 pt = _add(t->TM_ZONE, pt, ptlim); 507 #elif HAVE_TZNAME 508 if (t->tm_isdst >= 0) 509 pt = _add(tzname[t->tm_isdst != 0], 510 pt, ptlim); 511 #endif 512 /* 513 ** C99 and later say that %Z must be 514 ** replaced by the empty string if the 515 ** time zone abbreviation is not 516 ** determinable. 517 */ 518 continue; 519 case 'z': 520 #if defined TM_GMTOFF || USG_COMPAT || ALTZONE 521 { 522 long diff; 523 char const * sign; 524 bool negative; 525 526 # ifdef TM_GMTOFF 527 diff = t->TM_GMTOFF; 528 # else 529 /* 530 ** C99 and later say that the UT offset must 531 ** be computed by looking only at 532 ** tm_isdst. This requirement is 533 ** incorrect, since it means the code 534 ** must rely on magic (in this case 535 ** altzone and timezone), and the 536 ** magic might not have the correct 537 ** offset. Doing things correctly is 538 ** tricky and requires disobeying the standard; 539 ** see GNU C strftime for details. 540 ** For now, punt and conform to the 541 ** standard, even though it's incorrect. 542 ** 543 ** C99 and later say that %z must be replaced by 544 ** the empty string if the time zone is not 545 ** determinable, so output nothing if the 546 ** appropriate variables are not available. 547 */ 548 if (t->tm_isdst < 0) 549 continue; 550 if (t->tm_isdst == 0) 551 # if USG_COMPAT 552 diff = -timezone; 553 # else 554 continue; 555 # endif 556 else 557 # if ALTZONE 558 diff = -altzone; 559 # else 560 continue; 561 # endif 562 # endif 563 negative = diff < 0; 564 if (diff == 0) { 565 # ifdef TM_ZONE 566 negative = t->TM_ZONE[0] == '-'; 567 # else 568 negative = t->tm_isdst < 0; 569 # if HAVE_TZNAME 570 if (tzname[t->tm_isdst != 0][0] == '-') 571 negative = true; 572 # endif 573 # endif 574 } 575 if (negative) { 576 sign = "-"; 577 diff = -diff; 578 } else sign = "+"; 579 pt = _add(sign, pt, ptlim); 580 diff /= SECSPERMIN; 581 diff = (diff / MINSPERHOUR) * 100 + 582 (diff % MINSPERHOUR); 583 pt = _conv(diff, "%04d", pt, ptlim); 584 } 585 #endif 586 continue; 587 case '+': 588 pt = _fmt(Locale->date_fmt, t, pt, ptlim, 589 warnp); 590 continue; 591 case '%': 592 /* 593 ** X311J/88-090 (4.12.3.5): if conversion char is 594 ** undefined, behavior is undefined. Print out the 595 ** character itself as printf(3) also does. 596 */ 597 default: 598 break; 599 } 600 } 601 if (pt == ptlim) 602 break; 603 *pt++ = *format; 604 } 605 return pt; 606 } 607 608 static char * 609 _conv(int n, const char *format, char *pt, const char *ptlim) 610 { 611 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 612 613 sprintf(buf, format, n); 614 return _add(buf, pt, ptlim); 615 } 616 617 static char * 618 _add(const char *str, char *pt, const char *ptlim) 619 { 620 while (pt < ptlim && (*pt = *str++) != '\0') 621 ++pt; 622 return pt; 623 } 624 625 /* 626 ** POSIX and the C Standard are unclear or inconsistent about 627 ** what %C and %y do if the year is negative or exceeds 9999. 628 ** Use the convention that %C concatenated with %y yields the 629 ** same output as %Y, and that %Y contains at least 4 bytes, 630 ** with more only if necessary. 631 */ 632 633 static char * 634 _yconv(int a, int b, bool convert_top, bool convert_yy, 635 char *pt, const char *ptlim) 636 { 637 register int lead; 638 register int trail; 639 640 int DIVISOR = 100; 641 trail = a % DIVISOR + b % DIVISOR; 642 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; 643 trail %= DIVISOR; 644 if (trail < 0 && lead > 0) { 645 trail += DIVISOR; 646 --lead; 647 } else if (lead < 0 && trail > 0) { 648 trail -= DIVISOR; 649 ++lead; 650 } 651 if (convert_top) { 652 if (lead == 0 && trail < 0) 653 pt = _add("-0", pt, ptlim); 654 else pt = _conv(lead, "%02d", pt, ptlim); 655 } 656 if (convert_yy) 657 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); 658 return pt; 659 } 660