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 #ifdef TM_GMTOFF 331 mkt = timeoff(&tm, t->TM_GMTOFF); 332 #else 333 tm.tm_isdst = t->tm_isdst; 334 mkt = mktime(&tm); 335 #endif 336 /* If mktime fails, %s expands to the 337 value of (time_t) -1 as a failure 338 marker; this is better in practice 339 than strftime failing. */ 340 if (TYPE_SIGNED(time_t)) { 341 intmax_t n = mkt; 342 sprintf(buf, "%"PRIdMAX, n); 343 } else { 344 uintmax_t n = mkt; 345 sprintf(buf, "%"PRIuMAX, n); 346 } 347 pt = _add(buf, pt, ptlim); 348 } 349 continue; 350 case 'T': 351 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp); 352 continue; 353 case 't': 354 pt = _add("\t", pt, ptlim); 355 continue; 356 case 'U': 357 pt = _conv((t->tm_yday + DAYSPERWEEK - 358 t->tm_wday) / DAYSPERWEEK, 359 "%02d", pt, ptlim); 360 continue; 361 case 'u': 362 /* 363 ** From Arnold Robbins' strftime version 3.0: 364 ** "ISO 8601: Weekday as a decimal number 365 ** [1 (Monday) - 7]" 366 ** (ado, 1993-05-24) 367 */ 368 pt = _conv((t->tm_wday == 0) ? 369 DAYSPERWEEK : t->tm_wday, 370 "%d", pt, ptlim); 371 continue; 372 case 'V': /* ISO 8601 week number */ 373 case 'G': /* ISO 8601 year (four digits) */ 374 case 'g': /* ISO 8601 year (two digits) */ 375 /* 376 ** From Arnold Robbins' strftime version 3.0: "the week number of the 377 ** year (the first Monday as the first day of week 1) as a decimal number 378 ** (01-53)." 379 ** (ado, 1993-05-24) 380 ** 381 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn: 382 ** "Week 01 of a year is per definition the first week which has the 383 ** Thursday in this year, which is equivalent to the week which contains 384 ** the fourth day of January. In other words, the first week of a new year 385 ** is the week which has the majority of its days in the new year. Week 01 386 ** might also contain days from the previous year and the week before week 387 ** 01 of a year is the last week (52 or 53) of the previous year even if 388 ** it contains days from the new year. A week starts with Monday (day 1) 389 ** and ends with Sunday (day 7). For example, the first week of the year 390 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 391 ** (ado, 1996-01-02) 392 */ 393 { 394 int year; 395 int base; 396 int yday; 397 int wday; 398 int w; 399 400 year = t->tm_year; 401 base = TM_YEAR_BASE; 402 yday = t->tm_yday; 403 wday = t->tm_wday; 404 for ( ; ; ) { 405 int len; 406 int bot; 407 int top; 408 409 len = isleap_sum(year, base) ? 410 DAYSPERLYEAR : 411 DAYSPERNYEAR; 412 /* 413 ** What yday (-3 ... 3) does 414 ** the ISO year begin on? 415 */ 416 bot = ((yday + 11 - wday) % 417 DAYSPERWEEK) - 3; 418 /* 419 ** What yday does the NEXT 420 ** ISO year begin on? 421 */ 422 top = bot - 423 (len % DAYSPERWEEK); 424 if (top < -3) 425 top += DAYSPERWEEK; 426 top += len; 427 if (yday >= top) { 428 ++base; 429 w = 1; 430 break; 431 } 432 if (yday >= bot) { 433 w = 1 + ((yday - bot) / 434 DAYSPERWEEK); 435 break; 436 } 437 --base; 438 yday += isleap_sum(year, base) ? 439 DAYSPERLYEAR : 440 DAYSPERNYEAR; 441 } 442 #ifdef XPG4_1994_04_09 443 if ((w == 52 && 444 t->tm_mon == TM_JANUARY) || 445 (w == 1 && 446 t->tm_mon == TM_DECEMBER)) 447 w = 53; 448 #endif /* defined XPG4_1994_04_09 */ 449 if (*format == 'V') 450 pt = _conv(w, "%02d", 451 pt, ptlim); 452 else if (*format == 'g') { 453 *warnp = IN_ALL; 454 pt = _yconv(year, base, 455 false, true, 456 pt, ptlim); 457 } else pt = _yconv(year, base, 458 true, true, 459 pt, ptlim); 460 } 461 continue; 462 case 'v': 463 /* 464 ** From Arnold Robbins' strftime version 3.0: 465 ** "date as dd-bbb-YYYY" 466 ** (ado, 1993-05-24) 467 */ 468 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp); 469 continue; 470 case 'W': 471 pt = _conv((t->tm_yday + DAYSPERWEEK - 472 (t->tm_wday ? 473 (t->tm_wday - 1) : 474 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 475 "%02d", pt, ptlim); 476 continue; 477 case 'w': 478 pt = _conv(t->tm_wday, "%d", pt, ptlim); 479 continue; 480 case 'X': 481 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); 482 continue; 483 case 'x': 484 { 485 enum warn warn2 = IN_SOME; 486 487 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); 488 if (warn2 == IN_ALL) 489 warn2 = IN_THIS; 490 if (warn2 > *warnp) 491 *warnp = warn2; 492 } 493 continue; 494 case 'y': 495 *warnp = IN_ALL; 496 pt = _yconv(t->tm_year, TM_YEAR_BASE, 497 false, true, 498 pt, ptlim); 499 continue; 500 case 'Y': 501 pt = _yconv(t->tm_year, TM_YEAR_BASE, 502 true, true, 503 pt, ptlim); 504 continue; 505 case 'Z': 506 #ifdef TM_ZONE 507 pt = _add(t->TM_ZONE, pt, ptlim); 508 #elif HAVE_TZNAME 509 if (t->tm_isdst >= 0) 510 pt = _add(tzname[t->tm_isdst != 0], 511 pt, ptlim); 512 #endif 513 /* 514 ** C99 and later say that %Z must be 515 ** replaced by the empty string if the 516 ** time zone abbreviation is not 517 ** determinable. 518 */ 519 continue; 520 case 'z': 521 #if defined TM_GMTOFF || USG_COMPAT || ALTZONE 522 { 523 long diff; 524 char const * sign; 525 bool negative; 526 527 # ifdef TM_GMTOFF 528 diff = t->TM_GMTOFF; 529 # else 530 /* 531 ** C99 and later say that the UT offset must 532 ** be computed by looking only at 533 ** tm_isdst. This requirement is 534 ** incorrect, since it means the code 535 ** must rely on magic (in this case 536 ** altzone and timezone), and the 537 ** magic might not have the correct 538 ** offset. Doing things correctly is 539 ** tricky and requires disobeying the standard; 540 ** see GNU C strftime for details. 541 ** For now, punt and conform to the 542 ** standard, even though it's incorrect. 543 ** 544 ** C99 and later say that %z must be replaced by 545 ** the empty string if the time zone is not 546 ** determinable, so output nothing if the 547 ** appropriate variables are not available. 548 */ 549 if (t->tm_isdst < 0) 550 continue; 551 if (t->tm_isdst == 0) 552 # if USG_COMPAT 553 diff = -timezone; 554 # else 555 continue; 556 # endif 557 else 558 # if ALTZONE 559 diff = -altzone; 560 # else 561 continue; 562 # endif 563 # endif 564 negative = diff < 0; 565 if (diff == 0) { 566 # ifdef TM_ZONE 567 negative = t->TM_ZONE[0] == '-'; 568 # else 569 negative = t->tm_isdst < 0; 570 # if HAVE_TZNAME 571 if (tzname[t->tm_isdst != 0][0] == '-') 572 negative = true; 573 # endif 574 # endif 575 } 576 if (negative) { 577 sign = "-"; 578 diff = -diff; 579 } else sign = "+"; 580 pt = _add(sign, pt, ptlim); 581 diff /= SECSPERMIN; 582 diff = (diff / MINSPERHOUR) * 100 + 583 (diff % MINSPERHOUR); 584 pt = _conv(diff, "%04d", pt, ptlim); 585 } 586 #endif 587 continue; 588 case '+': 589 pt = _fmt(Locale->date_fmt, t, pt, ptlim, 590 warnp); 591 continue; 592 case '%': 593 /* 594 ** X311J/88-090 (4.12.3.5): if conversion char is 595 ** undefined, behavior is undefined. Print out the 596 ** character itself as printf(3) also does. 597 */ 598 default: 599 break; 600 } 601 } 602 if (pt == ptlim) 603 break; 604 *pt++ = *format; 605 } 606 return pt; 607 } 608 609 static char * 610 _conv(int n, const char *format, char *pt, const char *ptlim) 611 { 612 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 613 614 sprintf(buf, format, n); 615 return _add(buf, pt, ptlim); 616 } 617 618 static char * 619 _add(const char *str, char *pt, const char *ptlim) 620 { 621 while (pt < ptlim && (*pt = *str++) != '\0') 622 ++pt; 623 return pt; 624 } 625 626 /* 627 ** POSIX and the C Standard are unclear or inconsistent about 628 ** what %C and %y do if the year is negative or exceeds 9999. 629 ** Use the convention that %C concatenated with %y yields the 630 ** same output as %Y, and that %Y contains at least 4 bytes, 631 ** with more only if necessary. 632 */ 633 634 static char * 635 _yconv(int a, int b, bool convert_top, bool convert_yy, 636 char *pt, const char *ptlim) 637 { 638 register int lead; 639 register int trail; 640 641 int DIVISOR = 100; 642 trail = a % DIVISOR + b % DIVISOR; 643 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; 644 trail %= DIVISOR; 645 if (trail < 0 && lead > 0) { 646 trail += DIVISOR; 647 --lead; 648 } else if (lead < 0 && trail > 0) { 649 trail -= DIVISOR; 650 ++lead; 651 } 652 if (convert_top) { 653 if (lead == 0 && trail < 0) 654 pt = _add("-0", pt, ptlim); 655 else pt = _conv(lead, "%02d", pt, ptlim); 656 } 657 if (convert_yy) 658 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); 659 return pt; 660 } 661