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