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