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 = year_sum_days(year, base); 468 /* 469 ** What yday (-3 ... 3) does 470 ** the ISO year begin on? 471 */ 472 bot = ((yday + 11 - wday) % 473 DAYSPERWEEK) - 3; 474 /* 475 ** What yday does the NEXT 476 ** ISO year begin on? 477 */ 478 top = bot - 479 (len % DAYSPERWEEK); 480 if (top < -3) 481 top += DAYSPERWEEK; 482 top += len; 483 if (yday >= top) { 484 ++base; 485 w = 1; 486 break; 487 } 488 if (yday >= bot) { 489 w = 1 + ((yday - bot) / 490 DAYSPERWEEK); 491 break; 492 } 493 --base; 494 yday += year_sum_days(year, 495 base); 496 } 497 #ifdef XPG4_1994_04_09 498 if ((w == 52 && 499 t->tm_mon == TM_JANUARY) || 500 (w == 1 && 501 t->tm_mon == TM_DECEMBER)) 502 w = 53; 503 #endif /* defined XPG4_1994_04_09 */ 504 if (*format == 'V') 505 pt = _conv(w, "%02d", 506 pt, ptlim); 507 else if (*format == 'g') { 508 *warnp = IN_ALL; 509 pt = _yconv(year, base, 510 false, true, 511 pt, ptlim); 512 } else pt = _yconv(year, base, 513 true, true, 514 pt, ptlim); 515 } 516 continue; 517 case 'v': 518 /* 519 ** From Arnold Robbins' strftime version 3.0: 520 ** "date as dd-bbb-YYYY" 521 ** (ado, 1993-05-24) 522 */ 523 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp); 524 continue; 525 case 'W': 526 pt = _conv((t->tm_yday + DAYSPERWEEK - 527 (t->tm_wday ? 528 (t->tm_wday - 1) : 529 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 530 "%02d", pt, ptlim); 531 continue; 532 case 'w': 533 pt = _conv(t->tm_wday, "%d", pt, ptlim); 534 continue; 535 case 'X': 536 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); 537 continue; 538 case 'x': 539 { 540 enum warn warn2 = IN_SOME; 541 542 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); 543 if (warn2 == IN_ALL) 544 warn2 = IN_THIS; 545 if (warn2 > *warnp) 546 *warnp = warn2; 547 } 548 continue; 549 case 'y': 550 *warnp = IN_ALL; 551 pt = _yconv(t->tm_year, TM_YEAR_BASE, 552 false, true, 553 pt, ptlim); 554 continue; 555 case 'Y': 556 pt = _yconv(t->tm_year, TM_YEAR_BASE, 557 true, true, 558 pt, ptlim); 559 continue; 560 case 'Z': 561 #ifdef TM_ZONE 562 pt = _add(t->TM_ZONE, pt, ptlim); 563 #elif HAVE_TZNAME 564 if (t->tm_isdst >= 0) 565 pt = _add(tzname[t->tm_isdst != 0], 566 pt, ptlim); 567 #endif 568 /* 569 ** C99 and later say that %Z must be 570 ** replaced by the empty string if the 571 ** time zone abbreviation is not 572 ** determinable. 573 */ 574 continue; 575 case 'z': 576 #if defined TM_GMTOFF || USG_COMPAT || ALTZONE 577 { 578 long diff; 579 char const * sign; 580 bool negative; 581 582 # ifdef TM_GMTOFF 583 diff = t->TM_GMTOFF; 584 # else 585 /* 586 ** C99 and later say that the UT offset must 587 ** be computed by looking only at 588 ** tm_isdst. This requirement is 589 ** incorrect, since it means the code 590 ** must rely on magic (in this case 591 ** altzone and timezone), and the 592 ** magic might not have the correct 593 ** offset. Doing things correctly is 594 ** tricky and requires disobeying the standard; 595 ** see GNU C strftime for details. 596 ** For now, punt and conform to the 597 ** standard, even though it's incorrect. 598 ** 599 ** C99 and later say that %z must be replaced by 600 ** the empty string if the time zone is not 601 ** determinable, so output nothing if the 602 ** appropriate variables are not available. 603 */ 604 if (t->tm_isdst < 0) 605 continue; 606 if (t->tm_isdst == 0) 607 # if USG_COMPAT 608 diff = -timezone; 609 # else 610 continue; 611 # endif 612 else 613 # if ALTZONE 614 diff = -altzone; 615 # else 616 continue; 617 # endif 618 # endif 619 negative = diff < 0; 620 if (diff == 0) { 621 # ifdef TM_ZONE 622 negative = t->TM_ZONE[0] == '-'; 623 # else 624 negative = t->tm_isdst < 0; 625 # if HAVE_TZNAME 626 if (tzname[t->tm_isdst != 0][0] == '-') 627 negative = true; 628 # endif 629 # endif 630 } 631 if (negative) { 632 sign = "-"; 633 diff = -diff; 634 } else sign = "+"; 635 pt = _add(sign, pt, ptlim); 636 diff /= SECSPERMIN; 637 diff = (diff / MINSPERHOUR) * 100 + 638 (diff % MINSPERHOUR); 639 pt = _conv(diff, "%04d", pt, ptlim); 640 } 641 #endif 642 continue; 643 case '+': 644 pt = _fmt(Locale->date_fmt, t, pt, ptlim, 645 warnp); 646 continue; 647 case '%': 648 break; 649 } 650 } 651 if (pt == ptlim) 652 break; 653 *pt++ = *format; 654 } 655 return pt; 656 } 657 658 static char * 659 _conv(int n, const char *format, char *pt, const char *ptlim) 660 { 661 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 662 663 sprintf(buf, format, n); 664 return _add(buf, pt, ptlim); 665 } 666 667 static char * 668 _add(const char *str, char *pt, const char *ptlim) 669 { 670 while (pt < ptlim && (*pt = *str++) != '\0') 671 ++pt; 672 return pt; 673 } 674 675 /* 676 ** POSIX and the C Standard are unclear or inconsistent about 677 ** what %C and %y do if the year is negative or exceeds 9999. 678 ** Use the convention that %C concatenated with %y yields the 679 ** same output as %Y, and that %Y contains at least 4 bytes, 680 ** with more only if necessary. 681 */ 682 683 static char * 684 _yconv(int a, int b, bool convert_top, bool convert_yy, 685 char *pt, const char *ptlim) 686 { 687 register int lead; 688 register int trail; 689 690 int DIVISOR = 100; 691 trail = a % DIVISOR + b % DIVISOR; 692 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; 693 trail %= DIVISOR; 694 if (trail < 0 && lead > 0) { 695 trail += DIVISOR; 696 --lead; 697 } else if (lead < 0 && trail > 0) { 698 trail -= DIVISOR; 699 ++lead; 700 } 701 if (convert_top) { 702 if (lead == 0 && trail < 0) 703 pt = _add("-0", pt, ptlim); 704 else pt = _conv(lead, "%02d", pt, ptlim); 705 } 706 if (convert_yy) 707 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); 708 return pt; 709 } 710