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 /* If true, the value returned by an idealized unlimited-range mktime 43 always fits into an integer type with bounds MIN and MAX. 44 If false, the value might not fit. 45 This macro is usable in #if if its arguments are. 46 Add or subtract 2**31 - 1 for the maximum UT offset allowed in a TZif file, 47 divide by the maximum number of non-leap seconds in a year, 48 divide again by two just to be safe, 49 and account for the tm_year origin (1900) and time_t origin (1970). */ 50 #define MKTIME_FITS_IN(min, max) \ 51 ((min) < 0 \ 52 && (((min) + TWO_31_MINUS_1) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900 \ 53 < INT_MIN) \ 54 && INT_MAX < ((max) - TWO_31_MINUS_1) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900) 55 56 /* MKTIME_MIGHT_OVERFLOW is true if mktime can fail due to time_t overflow 57 or if it is not known whether mktime can fail, 58 and is false if mktime definitely cannot fail. 59 This macro is usable in #if, and so does not use TIME_T_MAX or sizeof. 60 If the builder has not configured this macro, guess based on what 61 known platforms do. When in doubt, guess true. */ 62 #ifndef MKTIME_MIGHT_OVERFLOW 63 # if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ 64 # include <sys/param.h> 65 # endif 66 # if ((/* The following heuristics assume native time_t. */ \ 67 defined_time_tz) \ 68 || ((/* Traditional time_t is 'long', so if 'long' is not wide enough \ 69 assume overflow unless we're on a known-safe host. */ \ 70 !MKTIME_FITS_IN(LONG_MIN, LONG_MAX)) \ 71 && (/* GNU C Library 2.29 (2019-02-01) and later has 64-bit time_t \ 72 if __TIMESIZE is 64. */ \ 73 !defined __TIMESIZE || __TIMESIZE < 64) \ 74 && (/* FreeBSD 12 r320347 (__FreeBSD_version 1200036; 2017-06-26), \ 75 and later has 64-bit time_t on all platforms but i386 which \ 76 is currently scheduled for end-of-life on 2028-11-30. */ \ 77 !defined __FreeBSD_version || __FreeBSD_version < 1200036 \ 78 || defined __i386) \ 79 && (/* NetBSD 6.0 (2012-10-17) and later has 64-bit time_t. */ \ 80 !defined __NetBSD_Version__ || __NetBSD_Version__ < 600000000) \ 81 && (/* OpenBSD 5.5 (2014-05-01) and later has 64-bit time_t. */ \ 82 !defined OpenBSD || OpenBSD < 201405))) 83 # define MKTIME_MIGHT_OVERFLOW 1 84 # else 85 # define MKTIME_MIGHT_OVERFLOW 0 86 # endif 87 #endif 88 /* Check that MKTIME_MIGHT_OVERFLOW is consistent with time_t's range. */ 89 static_assert(MKTIME_MIGHT_OVERFLOW 90 || MKTIME_FITS_IN(TIME_T_MIN, TIME_T_MAX)); 91 92 #if MKTIME_MIGHT_OVERFLOW 93 /* Do this after system includes as it redefines time_t, mktime, timeoff. */ 94 # define USE_TIMEX_T true 95 # include "localtime.c" 96 #endif 97 98 #ifndef DEPRECATE_TWO_DIGIT_YEARS 99 # define DEPRECATE_TWO_DIGIT_YEARS 0 100 #endif 101 102 struct lc_time_T { 103 const char * mon[MONSPERYEAR]; 104 const char * month[MONSPERYEAR]; 105 const char * wday[DAYSPERWEEK]; 106 const char * weekday[DAYSPERWEEK]; 107 const char * X_fmt; 108 const char * x_fmt; 109 const char * c_fmt; 110 const char * am; 111 const char * pm; 112 const char * date_fmt; 113 }; 114 115 static const struct lc_time_T C_time_locale = { 116 { 117 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 118 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 119 }, { 120 "January", "February", "March", "April", "May", "June", 121 "July", "August", "September", "October", "November", "December" 122 }, { 123 "Sun", "Mon", "Tue", "Wed", 124 "Thu", "Fri", "Sat" 125 }, { 126 "Sunday", "Monday", "Tuesday", "Wednesday", 127 "Thursday", "Friday", "Saturday" 128 }, 129 130 /* X_fmt */ 131 "%H:%M:%S", 132 133 /* 134 ** x_fmt 135 ** C99 and later require this format. 136 ** Using just numbers (as here) makes Quakers happier; 137 ** it's also compatible with SVR4. 138 */ 139 "%m/%d/%y", 140 141 /* 142 ** c_fmt 143 ** C99 and later require this format. 144 ** Previously this code used "%D %X", but we now conform to C99. 145 ** Note that 146 ** "%a %b %d %H:%M:%S %Y" 147 ** is used by Solaris 2.3. 148 */ 149 "%a %b %e %T %Y", 150 151 /* am */ 152 "AM", 153 154 /* pm */ 155 "PM", 156 157 /* date_fmt */ 158 "%a %b %e %H:%M:%S %Z %Y" 159 }; 160 161 enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL }; 162 163 static char * _add(const char *, char *, const char *); 164 static char * _conv(int, const char *, char *, const char *); 165 static char * _fmt(const char *, const struct tm *, char *, const char *, 166 enum warn *); 167 static char * _yconv(int, int, bool, bool, char *, char const *); 168 169 #ifndef YEAR_2000_NAME 170 # define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" 171 #endif /* !defined YEAR_2000_NAME */ 172 173 #if HAVE_STRFTIME_L 174 size_t 175 strftime_l(char *restrict s, size_t maxsize, char const *restrict format, 176 struct tm const *restrict t, 177 ATTRIBUTE_MAYBE_UNUSED locale_t locale) 178 { 179 /* Just call strftime, as only the C locale is supported. */ 180 return strftime(s, maxsize, format, t); 181 } 182 #endif 183 184 size_t 185 strftime(char *restrict s, size_t maxsize, char const *restrict format, 186 struct tm const *restrict t) 187 { 188 char * p; 189 int saved_errno = errno; 190 enum warn warn = IN_NONE; 191 192 tzset(); 193 p = _fmt(format, t, s, s + maxsize, &warn); 194 if (DEPRECATE_TWO_DIGIT_YEARS 195 && warn != IN_NONE && getenv(YEAR_2000_NAME)) { 196 fprintf(stderr, "\n"); 197 fprintf(stderr, "strftime format \"%s\" ", format); 198 fprintf(stderr, "yields only two digits of years in "); 199 if (warn == IN_SOME) 200 fprintf(stderr, "some locales"); 201 else if (warn == IN_THIS) 202 fprintf(stderr, "the current locale"); 203 else fprintf(stderr, "all locales"); 204 fprintf(stderr, "\n"); 205 } 206 if (p == s + maxsize) { 207 errno = ERANGE; 208 return 0; 209 } 210 *p = '\0'; 211 errno = saved_errno; 212 return p - s; 213 } 214 215 static char * 216 _fmt(const char *format, const struct tm *t, char *pt, 217 const char *ptlim, enum warn *warnp) 218 { 219 struct lc_time_T const *Locale = &C_time_locale; 220 221 for ( ; *format; ++format) { 222 if (*format == '%') { 223 label: 224 switch (*++format) { 225 default: 226 /* Output unknown conversion specifiers as-is, 227 to aid debugging. This includes '%' at 228 format end. This conforms to C23 section 229 7.29.3.5 paragraph 6, which says behavior 230 is undefined here. */ 231 --format; 232 break; 233 case 'A': 234 pt = _add((t->tm_wday < 0 || 235 t->tm_wday >= DAYSPERWEEK) ? 236 "?" : Locale->weekday[t->tm_wday], 237 pt, ptlim); 238 continue; 239 case 'a': 240 pt = _add((t->tm_wday < 0 || 241 t->tm_wday >= DAYSPERWEEK) ? 242 "?" : Locale->wday[t->tm_wday], 243 pt, ptlim); 244 continue; 245 case 'B': 246 pt = _add((t->tm_mon < 0 || 247 t->tm_mon >= MONSPERYEAR) ? 248 "?" : Locale->month[t->tm_mon], 249 pt, ptlim); 250 continue; 251 case 'b': 252 case 'h': 253 pt = _add((t->tm_mon < 0 || 254 t->tm_mon >= MONSPERYEAR) ? 255 "?" : Locale->mon[t->tm_mon], 256 pt, ptlim); 257 continue; 258 case 'C': 259 /* 260 ** %C used to do a... 261 ** _fmt("%a %b %e %X %Y", t); 262 ** ...whereas now POSIX 1003.2 calls for 263 ** something completely different. 264 ** (ado, 1993-05-24) 265 */ 266 pt = _yconv(t->tm_year, TM_YEAR_BASE, 267 true, false, pt, ptlim); 268 continue; 269 case 'c': 270 { 271 enum warn warn2 = IN_SOME; 272 273 pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2); 274 if (warn2 == IN_ALL) 275 warn2 = IN_THIS; 276 if (warn2 > *warnp) 277 *warnp = warn2; 278 } 279 continue; 280 case 'D': 281 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp); 282 continue; 283 case 'd': 284 pt = _conv(t->tm_mday, "%02d", pt, ptlim); 285 continue; 286 case 'E': 287 case 'O': 288 /* 289 ** Locale modifiers of C99 and later. 290 ** The sequences 291 ** %Ec %EC %Ex %EX %Ey %EY 292 ** %Od %oe %OH %OI %Om %OM 293 ** %OS %Ou %OU %OV %Ow %OW %Oy 294 ** are supposed to provide alternative 295 ** representations. 296 */ 297 goto label; 298 case 'e': 299 pt = _conv(t->tm_mday, "%2d", pt, ptlim); 300 continue; 301 case 'F': 302 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp); 303 continue; 304 case 'H': 305 pt = _conv(t->tm_hour, "%02d", pt, ptlim); 306 continue; 307 case 'I': 308 pt = _conv((t->tm_hour % 12) ? 309 (t->tm_hour % 12) : 12, 310 "%02d", pt, ptlim); 311 continue; 312 case 'j': 313 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); 314 continue; 315 case 'k': 316 /* 317 ** This used to be... 318 ** _conv(t->tm_hour % 12 ? 319 ** t->tm_hour % 12 : 12, 2, ' '); 320 ** ...and has been changed to the below to 321 ** match SunOS 4.1.1 and Arnold Robbins' 322 ** strftime version 3.0. That is, "%k" and 323 ** "%l" have been swapped. 324 ** (ado, 1993-05-24) 325 */ 326 pt = _conv(t->tm_hour, "%2d", pt, ptlim); 327 continue; 328 #ifdef KITCHEN_SINK 329 case 'K': 330 /* 331 ** After all this time, still unclaimed! 332 */ 333 pt = _add("kitchen sink", pt, ptlim); 334 continue; 335 #endif /* defined KITCHEN_SINK */ 336 case 'l': 337 /* 338 ** This used to be... 339 ** _conv(t->tm_hour, 2, ' '); 340 ** ...and has been changed to the below to 341 ** match SunOS 4.1.1 and Arnold Robbin's 342 ** strftime version 3.0. That is, "%k" and 343 ** "%l" have been swapped. 344 ** (ado, 1993-05-24) 345 */ 346 pt = _conv((t->tm_hour % 12) ? 347 (t->tm_hour % 12) : 12, 348 "%2d", pt, ptlim); 349 continue; 350 case 'M': 351 pt = _conv(t->tm_min, "%02d", pt, ptlim); 352 continue; 353 case 'm': 354 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); 355 continue; 356 case 'n': 357 pt = _add("\n", pt, ptlim); 358 continue; 359 case 'p': 360 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? 361 Locale->pm : 362 Locale->am, 363 pt, ptlim); 364 continue; 365 case 'R': 366 pt = _fmt("%H:%M", t, pt, ptlim, warnp); 367 continue; 368 case 'r': 369 pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp); 370 continue; 371 case 'S': 372 pt = _conv(t->tm_sec, "%02d", pt, ptlim); 373 continue; 374 case 's': 375 { 376 struct tm tm; 377 char buf[INT_STRLEN_MAXIMUM( 378 time_t) + 1]; 379 time_t mkt; 380 381 tm.tm_sec = t->tm_sec; 382 tm.tm_min = t->tm_min; 383 tm.tm_hour = t->tm_hour; 384 tm.tm_mday = t->tm_mday; 385 tm.tm_mon = t->tm_mon; 386 tm.tm_year = t->tm_year; 387 388 /* Get the time_t value for TM. 389 Native time_t, or its redefinition 390 by localtime.c above, is wide enough 391 so that this cannot overflow. */ 392 #ifdef TM_GMTOFF 393 mkt = timeoff(&tm, t->TM_GMTOFF); 394 #else 395 tm.tm_isdst = t->tm_isdst; 396 mkt = mktime(&tm); 397 #endif 398 if (TYPE_SIGNED(time_t)) { 399 intmax_t n = mkt; 400 sprintf(buf, "%"PRIdMAX, n); 401 } else { 402 uintmax_t n = mkt; 403 sprintf(buf, "%"PRIuMAX, n); 404 } 405 pt = _add(buf, pt, ptlim); 406 } 407 continue; 408 case 'T': 409 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp); 410 continue; 411 case 't': 412 pt = _add("\t", pt, ptlim); 413 continue; 414 case 'U': 415 pt = _conv((t->tm_yday + DAYSPERWEEK - 416 t->tm_wday) / DAYSPERWEEK, 417 "%02d", pt, ptlim); 418 continue; 419 case 'u': 420 /* 421 ** From Arnold Robbins' strftime version 3.0: 422 ** "ISO 8601: Weekday as a decimal number 423 ** [1 (Monday) - 7]" 424 ** (ado, 1993-05-24) 425 */ 426 pt = _conv((t->tm_wday == 0) ? 427 DAYSPERWEEK : t->tm_wday, 428 "%d", pt, ptlim); 429 continue; 430 case 'V': /* ISO 8601 week number */ 431 case 'G': /* ISO 8601 year (four digits) */ 432 case 'g': /* ISO 8601 year (two digits) */ 433 /* 434 ** From Arnold Robbins' strftime version 3.0: "the week number of the 435 ** year (the first Monday as the first day of week 1) as a decimal number 436 ** (01-53)." 437 ** (ado, 1993-05-24) 438 ** 439 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn: 440 ** "Week 01 of a year is per definition the first week which has the 441 ** Thursday in this year, which is equivalent to the week which contains 442 ** the fourth day of January. In other words, the first week of a new year 443 ** is the week which has the majority of its days in the new year. Week 01 444 ** might also contain days from the previous year and the week before week 445 ** 01 of a year is the last week (52 or 53) of the previous year even if 446 ** it contains days from the new year. A week starts with Monday (day 1) 447 ** and ends with Sunday (day 7). For example, the first week of the year 448 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 449 ** (ado, 1996-01-02) 450 */ 451 { 452 int year; 453 int base; 454 int yday; 455 int wday; 456 int w; 457 458 year = t->tm_year; 459 base = TM_YEAR_BASE; 460 yday = t->tm_yday; 461 wday = t->tm_wday; 462 for ( ; ; ) { 463 int len; 464 int bot; 465 int top; 466 467 len = isleap_sum(year, base) ? 468 DAYSPERLYEAR : 469 DAYSPERNYEAR; 470 /* 471 ** What yday (-3 ... 3) does 472 ** the ISO year begin on? 473 */ 474 bot = ((yday + 11 - wday) % 475 DAYSPERWEEK) - 3; 476 /* 477 ** What yday does the NEXT 478 ** ISO year begin on? 479 */ 480 top = bot - 481 (len % DAYSPERWEEK); 482 if (top < -3) 483 top += DAYSPERWEEK; 484 top += len; 485 if (yday >= top) { 486 ++base; 487 w = 1; 488 break; 489 } 490 if (yday >= bot) { 491 w = 1 + ((yday - bot) / 492 DAYSPERWEEK); 493 break; 494 } 495 --base; 496 yday += isleap_sum(year, base) ? 497 DAYSPERLYEAR : 498 DAYSPERNYEAR; 499 } 500 #ifdef XPG4_1994_04_09 501 if ((w == 52 && 502 t->tm_mon == TM_JANUARY) || 503 (w == 1 && 504 t->tm_mon == TM_DECEMBER)) 505 w = 53; 506 #endif /* defined XPG4_1994_04_09 */ 507 if (*format == 'V') 508 pt = _conv(w, "%02d", 509 pt, ptlim); 510 else if (*format == 'g') { 511 *warnp = IN_ALL; 512 pt = _yconv(year, base, 513 false, true, 514 pt, ptlim); 515 } else pt = _yconv(year, base, 516 true, true, 517 pt, ptlim); 518 } 519 continue; 520 case 'v': 521 /* 522 ** From Arnold Robbins' strftime version 3.0: 523 ** "date as dd-bbb-YYYY" 524 ** (ado, 1993-05-24) 525 */ 526 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp); 527 continue; 528 case 'W': 529 pt = _conv((t->tm_yday + DAYSPERWEEK - 530 (t->tm_wday ? 531 (t->tm_wday - 1) : 532 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 533 "%02d", pt, ptlim); 534 continue; 535 case 'w': 536 pt = _conv(t->tm_wday, "%d", pt, ptlim); 537 continue; 538 case 'X': 539 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); 540 continue; 541 case 'x': 542 { 543 enum warn warn2 = IN_SOME; 544 545 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); 546 if (warn2 == IN_ALL) 547 warn2 = IN_THIS; 548 if (warn2 > *warnp) 549 *warnp = warn2; 550 } 551 continue; 552 case 'y': 553 *warnp = IN_ALL; 554 pt = _yconv(t->tm_year, TM_YEAR_BASE, 555 false, true, 556 pt, ptlim); 557 continue; 558 case 'Y': 559 pt = _yconv(t->tm_year, TM_YEAR_BASE, 560 true, true, 561 pt, ptlim); 562 continue; 563 case 'Z': 564 #ifdef TM_ZONE 565 pt = _add(t->TM_ZONE, pt, ptlim); 566 #elif HAVE_TZNAME 567 if (t->tm_isdst >= 0) 568 pt = _add(tzname[t->tm_isdst != 0], 569 pt, ptlim); 570 #endif 571 /* 572 ** C99 and later say that %Z must be 573 ** replaced by the empty string if the 574 ** time zone abbreviation is not 575 ** determinable. 576 */ 577 continue; 578 case 'z': 579 #if defined TM_GMTOFF || USG_COMPAT || ALTZONE 580 { 581 long diff; 582 char const * sign; 583 bool negative; 584 585 # ifdef TM_GMTOFF 586 diff = t->TM_GMTOFF; 587 # else 588 /* 589 ** C99 and later say that the UT offset must 590 ** be computed by looking only at 591 ** tm_isdst. This requirement is 592 ** incorrect, since it means the code 593 ** must rely on magic (in this case 594 ** altzone and timezone), and the 595 ** magic might not have the correct 596 ** offset. Doing things correctly is 597 ** tricky and requires disobeying the standard; 598 ** see GNU C strftime for details. 599 ** For now, punt and conform to the 600 ** standard, even though it's incorrect. 601 ** 602 ** C99 and later say that %z must be replaced by 603 ** the empty string if the time zone is not 604 ** determinable, so output nothing if the 605 ** appropriate variables are not available. 606 */ 607 if (t->tm_isdst < 0) 608 continue; 609 if (t->tm_isdst == 0) 610 # if USG_COMPAT 611 diff = -timezone; 612 # else 613 continue; 614 # endif 615 else 616 # if ALTZONE 617 diff = -altzone; 618 # else 619 continue; 620 # endif 621 # endif 622 negative = diff < 0; 623 if (diff == 0) { 624 # ifdef TM_ZONE 625 negative = t->TM_ZONE[0] == '-'; 626 # else 627 negative = t->tm_isdst < 0; 628 # if HAVE_TZNAME 629 if (tzname[t->tm_isdst != 0][0] == '-') 630 negative = true; 631 # endif 632 # endif 633 } 634 if (negative) { 635 sign = "-"; 636 diff = -diff; 637 } else sign = "+"; 638 pt = _add(sign, pt, ptlim); 639 diff /= SECSPERMIN; 640 diff = (diff / MINSPERHOUR) * 100 + 641 (diff % MINSPERHOUR); 642 pt = _conv(diff, "%04d", pt, ptlim); 643 } 644 #endif 645 continue; 646 case '+': 647 pt = _fmt(Locale->date_fmt, t, pt, ptlim, 648 warnp); 649 continue; 650 case '%': 651 break; 652 } 653 } 654 if (pt == ptlim) 655 break; 656 *pt++ = *format; 657 } 658 return pt; 659 } 660 661 static char * 662 _conv(int n, const char *format, char *pt, const char *ptlim) 663 { 664 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 665 666 sprintf(buf, format, n); 667 return _add(buf, pt, ptlim); 668 } 669 670 static char * 671 _add(const char *str, char *pt, const char *ptlim) 672 { 673 while (pt < ptlim && (*pt = *str++) != '\0') 674 ++pt; 675 return pt; 676 } 677 678 /* 679 ** POSIX and the C Standard are unclear or inconsistent about 680 ** what %C and %y do if the year is negative or exceeds 9999. 681 ** Use the convention that %C concatenated with %y yields the 682 ** same output as %Y, and that %Y contains at least 4 bytes, 683 ** with more only if necessary. 684 */ 685 686 static char * 687 _yconv(int a, int b, bool convert_top, bool convert_yy, 688 char *pt, const char *ptlim) 689 { 690 register int lead; 691 register int trail; 692 693 int DIVISOR = 100; 694 trail = a % DIVISOR + b % DIVISOR; 695 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; 696 trail %= DIVISOR; 697 if (trail < 0 && lead > 0) { 698 trail += DIVISOR; 699 --lead; 700 } else if (lead < 0 && trail > 0) { 701 trail -= DIVISOR; 702 ++lead; 703 } 704 if (convert_top) { 705 if (lead == 0 && trail < 0) 706 pt = _add("-0", pt, ptlim); 707 else pt = _conv(lead, "%02d", pt, ptlim); 708 } 709 if (convert_yy) 710 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); 711 return pt; 712 } 713