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
strftime_l(char * restrict s,size_t maxsize,char const * restrict format,struct tm const * restrict t,ATTRIBUTE_MAYBE_UNUSED locale_t locale)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
strftime(char * restrict s,size_t maxsize,char const * restrict format,struct tm const * restrict 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 *
_fmt(const char * format,const struct tm * t,char * pt,const char * ptlim,enum warn * warnp)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 *
_conv(int n,const char * format,char * pt,const char * ptlim)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 *
_add(const char * str,char * pt,const char * ptlim)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 *
_yconv(int a,int b,bool convert_top,bool convert_yy,char * pt,const char * ptlim)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