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 #if defined(__BIONIC__)
47
48 /* LP32 had a 32-bit time_t, so we need to work around that here. */
49 #if defined(__LP64__)
50 #define time64_t time_t
51 #define mktime64 mktime
52 #define localtime64_r localtime_r
53 #else
54 #include <time64.h>
55 #endif
56
57 #include <ctype.h>
58
59 #endif
60
61 struct lc_time_T {
62 const char * mon[MONSPERYEAR];
63 const char * month[MONSPERYEAR];
64 const char * wday[DAYSPERWEEK];
65 const char * weekday[DAYSPERWEEK];
66 const char * X_fmt;
67 const char * x_fmt;
68 const char * c_fmt;
69 const char * am;
70 const char * pm;
71 const char * date_fmt;
72 };
73
74 static const struct lc_time_T C_time_locale = {
75 {
76 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
77 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
78 }, {
79 "January", "February", "March", "April", "May", "June",
80 "July", "August", "September", "October", "November", "December"
81 }, {
82 "Sun", "Mon", "Tue", "Wed",
83 "Thu", "Fri", "Sat"
84 }, {
85 "Sunday", "Monday", "Tuesday", "Wednesday",
86 "Thursday", "Friday", "Saturday"
87 },
88
89 /* X_fmt */
90 "%H:%M:%S",
91
92 /*
93 ** x_fmt
94 ** C99 and later require this format.
95 ** Using just numbers (as here) makes Quakers happier;
96 ** it's also compatible with SVR4.
97 */
98 "%m/%d/%y",
99
100 /*
101 ** c_fmt
102 ** C99 and later require this format.
103 ** Previously this code used "%D %X", but we now conform to C99.
104 ** Note that
105 ** "%a %b %d %H:%M:%S %Y"
106 ** is used by Solaris 2.3.
107 */
108 "%a %b %e %T %Y",
109
110 /* am */
111 "AM",
112
113 /* pm */
114 "PM",
115
116 /* date_fmt */
117 "%a %b %e %H:%M:%S %Z %Y"
118 };
119
120 enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL };
121
122 static char * _add(const char *, char *, const char *, int);
123 static char * _conv(int, const char *, char *, const char *);
124 static char * _fmt(const char *, const struct tm *, char *, const char *,
125 enum warn *);
126 static char * _yconv(int, int, bool, bool, char *, const char *, int);
127
128 #ifndef YEAR_2000_NAME
129 # define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
130 #endif /* !defined YEAR_2000_NAME */
131
132 #if HAVE_STRFTIME_L
133 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)134 strftime_l(char *restrict s, size_t maxsize, char const *restrict format,
135 struct tm const *restrict t,
136 ATTRIBUTE_MAYBE_UNUSED locale_t locale)
137 {
138 /* Just call strftime, as only the C locale is supported. */
139 return strftime(s, maxsize, format, t);
140 }
141 #endif
142
143 #define FORCE_LOWER_CASE 0x100 /* Android extension. */
144
145 size_t
strftime(char * restrict s,size_t maxsize,char const * restrict format,struct tm const * restrict t)146 strftime(char *restrict s, size_t maxsize, char const *restrict format,
147 struct tm const *restrict t)
148 {
149 char * p;
150 int saved_errno = errno;
151 enum warn warn = IN_NONE;
152
153 tzset();
154 p = _fmt(format, t, s, s + maxsize, &warn);
155 if (!p) {
156 errno = EOVERFLOW;
157 return 0;
158 }
159 if (DEPRECATE_TWO_DIGIT_YEARS
160 && warn != IN_NONE && getenv(YEAR_2000_NAME)) {
161 fprintf(stderr, "\n");
162 fprintf(stderr, "strftime format \"%s\" ", format);
163 fprintf(stderr, "yields only two digits of years in ");
164 if (warn == IN_SOME)
165 fprintf(stderr, "some locales");
166 else if (warn == IN_THIS)
167 fprintf(stderr, "the current locale");
168 else fprintf(stderr, "all locales");
169 fprintf(stderr, "\n");
170 }
171 if (p == s + maxsize) {
172 errno = ERANGE;
173 return 0;
174 }
175 *p = '\0';
176 errno = saved_errno;
177 return p - s;
178 }
179
getformat(int modifier,char * normal,char * underscore,char * dash,char * zero)180 static char *getformat(int modifier, char *normal, char *underscore,
181 char *dash, char *zero) {
182 switch (modifier) {
183 case '_':
184 return underscore;
185 case '-':
186 return dash;
187 case '0':
188 return zero;
189 }
190 return normal;
191 }
192
193 // Android-added: fall back mechanism when TM_ZONE is not initialized.
194 #ifdef TM_ZONE
_safe_tm_zone(const struct tm * tm)195 static const char* _safe_tm_zone(const struct tm* tm) {
196 const char* zone = tm->TM_ZONE;
197 if (!zone || !*zone) {
198 // "The value of tm_isdst shall be positive if Daylight Savings Time is
199 // in effect, 0 if Daylight Savings Time is not in effect, and negative
200 // if the information is not available."
201 if (tm->tm_isdst == 0) {
202 zone = tzname[0];
203 } else if (tm->tm_isdst > 0) {
204 zone = tzname[1];
205 }
206
207 // "Replaced by the timezone name or abbreviation, or by no bytes if no
208 // timezone information exists."
209 if (!zone || !*zone) zone = "";
210 }
211
212 return zone;
213 }
214 #endif
215
216 static char *
_fmt(const char * format,const struct tm * t,char * pt,const char * ptlim,enum warn * warnp)217 _fmt(const char *format, const struct tm *t, char *pt,
218 const char *ptlim, enum warn *warnp)
219 {
220 struct lc_time_T const *Locale = &C_time_locale;
221
222 for ( ; *format; ++format) {
223 if (*format == '%') {
224 int modifier = 0;
225 label:
226 switch (*++format) {
227 case '\0':
228 --format;
229 break;
230 case 'A':
231 pt = _add((t->tm_wday < 0 ||
232 t->tm_wday >= DAYSPERWEEK) ?
233 "?" : Locale->weekday[t->tm_wday],
234 pt, ptlim, modifier);
235 continue;
236 case 'a':
237 pt = _add((t->tm_wday < 0 ||
238 t->tm_wday >= DAYSPERWEEK) ?
239 "?" : Locale->wday[t->tm_wday],
240 pt, ptlim, modifier);
241 continue;
242 case 'B':
243 pt = _add((t->tm_mon < 0 ||
244 t->tm_mon >= MONSPERYEAR) ?
245 "?" : Locale->month[t->tm_mon],
246 pt, ptlim, modifier);
247 continue;
248 case 'b':
249 case 'h':
250 pt = _add((t->tm_mon < 0 ||
251 t->tm_mon >= MONSPERYEAR) ?
252 "?" : Locale->mon[t->tm_mon],
253 pt, ptlim, modifier);
254 continue;
255 case 'C':
256 /*
257 ** %C used to do a...
258 ** _fmt("%a %b %e %X %Y", t);
259 ** ...whereas now POSIX 1003.2 calls for
260 ** something completely different.
261 ** (ado, 1993-05-24)
262 */
263 pt = _yconv(t->tm_year, TM_YEAR_BASE,
264 true, false, pt, ptlim, modifier);
265 continue;
266 case 'c':
267 {
268 enum warn warn2 = IN_SOME;
269
270 pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
271 if (warn2 == IN_ALL)
272 warn2 = IN_THIS;
273 if (warn2 > *warnp)
274 *warnp = warn2;
275 }
276 continue;
277 case 'D':
278 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
279 continue;
280 case 'd':
281 pt = _conv(t->tm_mday, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
282 continue;
283 case 'E':
284 case 'O':
285 /*
286 ** Locale modifiers of C99 and later.
287 ** The sequences
288 ** %Ec %EC %Ex %EX %Ey %EY
289 ** %Od %oe %OH %OI %Om %OM
290 ** %OS %Ou %OU %OV %Ow %OW %Oy
291 ** are supposed to provide alternative
292 ** representations.
293 */
294 goto label;
295 case '_':
296 case '-':
297 case '0':
298 case '^':
299 case '#':
300 modifier = *format;
301 goto label;
302 case 'e':
303 pt = _conv(t->tm_mday, getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
304 continue;
305 case 'F':
306 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
307 continue;
308 case 'H':
309 pt = _conv(t->tm_hour, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
310 continue;
311 case 'I':
312 pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
313 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
314 continue;
315 case 'j':
316 pt = _conv(t->tm_yday + 1, getformat(modifier, "03", " 3", " ", "03"), pt, ptlim);
317 continue;
318 case 'k':
319 /*
320 ** This used to be...
321 ** _conv(t->tm_hour % 12 ?
322 ** t->tm_hour % 12 : 12, 2, ' ');
323 ** ...and has been changed to the below to
324 ** match SunOS 4.1.1 and Arnold Robbins'
325 ** strftime version 3.0. That is, "%k" and
326 ** "%l" have been swapped.
327 ** (ado, 1993-05-24)
328 */
329 pt = _conv(t->tm_hour, getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
330 continue;
331 #ifdef KITCHEN_SINK
332 case 'K':
333 /*
334 ** After all this time, still unclaimed!
335 */
336 pt = _add("kitchen sink", pt, ptlim);
337 continue;
338 #endif /* defined KITCHEN_SINK */
339 case 'l':
340 /*
341 ** This used to be...
342 ** _conv(t->tm_hour, 2, ' ');
343 ** ...and has been changed to the below to
344 ** match SunOS 4.1.1 and Arnold Robbin's
345 ** strftime version 3.0. That is, "%k" and
346 ** "%l" have been swapped.
347 ** (ado, 1993-05-24)
348 */
349 pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
350 getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
351 continue;
352 case 'M':
353 pt = _conv(t->tm_min, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
354 continue;
355 case 'm':
356 pt = _conv(t->tm_mon + 1, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
357 continue;
358 case 'n':
359 pt = _add("\n", pt, ptlim, modifier);
360 continue;
361 case 'P':
362 case 'p':
363 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
364 Locale->pm :
365 Locale->am,
366 pt, ptlim, (*format == 'P') ? FORCE_LOWER_CASE : modifier);
367 continue;
368 case 'R':
369 pt = _fmt("%H:%M", t, pt, ptlim, warnp);
370 continue;
371 case 'r':
372 pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
373 continue;
374 case 'S':
375 pt = _conv(t->tm_sec, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
376 continue;
377 case 's':
378 {
379 struct tm tm;
380 char buf[INT_STRLEN_MAXIMUM(time64_t) + 1] __attribute__((__uninitialized__));
381 time64_t mkt;
382
383 tm.tm_sec = t->tm_sec;
384 tm.tm_min = t->tm_min;
385 tm.tm_hour = t->tm_hour;
386 tm.tm_mday = t->tm_mday;
387 tm.tm_mon = t->tm_mon;
388 tm.tm_year = t->tm_year;
389 tm.tm_isdst = t->tm_isdst;
390 #if defined TM_GMTOFF && ! UNINIT_TRAP
391 tm.TM_GMTOFF = t->TM_GMTOFF;
392 #endif
393 mkt = mktime64(&tm);
394 /* If mktime fails, %s expands to the
395 value of (time_t) -1 as a failure
396 marker; this is better in practice
397 than strftime failing. */
398 if (TYPE_SIGNED(time64_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, modifier);
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, modifier);
413 continue;
414 case 'U':
415 pt = _conv((t->tm_yday + DAYSPERWEEK - t->tm_wday) / DAYSPERWEEK,
416 getformat(modifier, "02", " 2", " ", "02"), 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) ? DAYSPERWEEK : t->tm_wday, " ", pt, ptlim);
426 continue;
427 case 'V': /* ISO 8601 week number */
428 case 'G': /* ISO 8601 year (four digits) */
429 case 'g': /* ISO 8601 year (two digits) */
430 /*
431 ** From Arnold Robbins' strftime version 3.0: "the week number of the
432 ** year (the first Monday as the first day of week 1) as a decimal number
433 ** (01-53)."
434 ** (ado, 1993-05-24)
435 **
436 ** From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
437 ** "Week 01 of a year is per definition the first week which has the
438 ** Thursday in this year, which is equivalent to the week which contains
439 ** the fourth day of January. In other words, the first week of a new year
440 ** is the week which has the majority of its days in the new year. Week 01
441 ** might also contain days from the previous year and the week before week
442 ** 01 of a year is the last week (52 or 53) of the previous year even if
443 ** it contains days from the new year. A week starts with Monday (day 1)
444 ** and ends with Sunday (day 7). For example, the first week of the year
445 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
446 ** (ado, 1996-01-02)
447 */
448 {
449 int year;
450 int base;
451 int yday;
452 int wday;
453 int w;
454
455 year = t->tm_year;
456 base = TM_YEAR_BASE;
457 yday = t->tm_yday;
458 wday = t->tm_wday;
459 for ( ; ; ) {
460 int len;
461 int bot;
462 int top;
463
464 len = isleap_sum(year, base) ?
465 DAYSPERLYEAR :
466 DAYSPERNYEAR;
467 /*
468 ** What yday (-3 ... 3) does
469 ** the ISO year begin on?
470 */
471 bot = ((yday + 11 - wday) %
472 DAYSPERWEEK) - 3;
473 /*
474 ** What yday does the NEXT
475 ** ISO year begin on?
476 */
477 top = bot -
478 (len % DAYSPERWEEK);
479 if (top < -3)
480 top += DAYSPERWEEK;
481 top += len;
482 if (yday >= top) {
483 ++base;
484 w = 1;
485 break;
486 }
487 if (yday >= bot) {
488 w = 1 + ((yday - bot) /
489 DAYSPERWEEK);
490 break;
491 }
492 --base;
493 yday += isleap_sum(year, base) ?
494 DAYSPERLYEAR :
495 DAYSPERNYEAR;
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, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
506 else if (*format == 'g') {
507 *warnp = IN_ALL;
508 pt = _yconv(year, base,
509 false, true,
510 pt, ptlim, modifier);
511 } else pt = _yconv(year, base,
512 true, true,
513 pt, ptlim, modifier);
514 }
515 continue;
516 case 'v':
517 /*
518 ** From Arnold Robbins' strftime version 3.0:
519 ** "date as dd-bbb-YYYY"
520 ** (ado, 1993-05-24)
521 */
522 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
523 continue;
524 case 'W':
525 pt = _conv(
526 (t->tm_yday + DAYSPERWEEK - (t->tm_wday ? (t->tm_wday - 1) : (DAYSPERWEEK - 1))) /
527 DAYSPERWEEK,
528 getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
529 continue;
530 case 'w':
531 pt = _conv(t->tm_wday, " ", pt, ptlim);
532 continue;
533 case 'X':
534 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
535 continue;
536 case 'x':
537 {
538 enum warn warn2 = IN_SOME;
539
540 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
541 if (warn2 == IN_ALL)
542 warn2 = IN_THIS;
543 if (warn2 > *warnp)
544 *warnp = warn2;
545 }
546 continue;
547 case 'y':
548 *warnp = IN_ALL;
549 pt = _yconv(t->tm_year, TM_YEAR_BASE,
550 false, true,
551 pt, ptlim, modifier);
552 continue;
553 case 'Y':
554 pt = _yconv(t->tm_year, TM_YEAR_BASE,
555 true, true,
556 pt, ptlim, modifier);
557 continue;
558 case 'Z':
559 #ifdef TM_ZONE
560 // BEGIN: Android-changed.
561 pt = _add(_safe_tm_zone(t), pt, ptlim, modifier);
562 // END: Android-changed.
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 // Android-changed: do not use TM_ZONE as it is as it may be null.
623 {
624 const char* zone = _safe_tm_zone(t);
625 negative = zone[0] == '-';
626 }
627 # else
628 negative = t->tm_isdst < 0;
629 # if HAVE_TZNAME
630 if (tzname[t->tm_isdst != 0][0] == '-')
631 negative = true;
632 # endif
633 # endif
634 }
635 if (negative) {
636 sign = "-";
637 diff = -diff;
638 } else sign = "+";
639 pt = _add(sign, pt, ptlim, modifier);
640 diff /= SECSPERMIN;
641 diff = (diff / MINSPERHOUR) * 100 +
642 (diff % MINSPERHOUR);
643 pt = _conv(diff, getformat(modifier, "04", " 4", " ", "04"), pt, ptlim);
644 }
645 #endif
646 continue;
647 case '+':
648 pt = _fmt(Locale->date_fmt, t, pt, ptlim,
649 warnp);
650 continue;
651 case '%':
652 /*
653 ** X311J/88-090 (4.12.3.5): if conversion char is
654 ** undefined, behavior is undefined. Print out the
655 ** character itself as printf(3) also does.
656 */
657 default:
658 break;
659 }
660 }
661 if (pt == ptlim)
662 break;
663 *pt++ = *format;
664 }
665 return pt;
666 }
667
668 static char *
_conv(int n,const char * format,char * pt,const char * ptlim)669 _conv(int n, const char *format, char *pt, const char *ptlim)
670 {
671 // The original implementation used snprintf(3) here, but rolling our own is
672 // about 5x faster. Seems like a good trade-off for so little code, especially
673 // for users like logcat that have a habit of formatting 10k times all at
674 // once...
675
676 // Format is '0' or ' ' for the fill character, followed by a single-digit
677 // width or ' ' for "whatever".
678 // %d -> " "
679 // %2d -> " 2"
680 // %02d -> "02"
681 char fill = format[0];
682 int width = format[1] == ' ' ? 0 : format[1] - '0';
683
684 char buf[32] __attribute__((__uninitialized__));
685
686 // Terminate first, so we can walk backwards from the least-significant digit
687 // without having to later reverse the result.
688 char* p = &buf[31];
689 *--p = '\0';
690 char* end = p;
691
692 // Output digits backwards, from least-significant to most.
693 while (n >= 10) {
694 *--p = '0' + (n % 10);
695 n /= 10;
696 }
697 *--p = '0' + n;
698
699 // Fill if more digits are required by the format.
700 while ((end - p) < width) {
701 *--p = fill;
702 }
703
704 return _add(p, pt, ptlim, 0);
705 }
706
707 static char *
_add(const char * str,char * pt,const char * const ptlim,int modifier)708 _add(const char *str, char *pt, const char *const ptlim, int modifier)
709 {
710 int c;
711
712 switch (modifier) {
713 case FORCE_LOWER_CASE:
714 while (pt < ptlim && (*pt = tolower(*str++)) != '\0') {
715 ++pt;
716 }
717 break;
718
719 case '^':
720 while (pt < ptlim && (*pt = toupper(*str++)) != '\0') {
721 ++pt;
722 }
723 break;
724
725 case '#':
726 while (pt < ptlim && (c = *str++) != '\0') {
727 if (isupper(c)) {
728 c = tolower(c);
729 } else if (islower(c)) {
730 c = toupper(c);
731 }
732 *pt = c;
733 ++pt;
734 }
735
736 break;
737
738 default:
739 while (pt < ptlim && (*pt = *str++) != '\0') {
740 ++pt;
741 }
742 }
743
744 return pt;
745 }
746
747 /*
748 ** POSIX and the C Standard are unclear or inconsistent about
749 ** what %C and %y do if the year is negative or exceeds 9999.
750 ** Use the convention that %C concatenated with %y yields the
751 ** same output as %Y, and that %Y contains at least 4 bytes,
752 ** with more only if necessary.
753 */
754
755 static char *
_yconv(int a,int b,bool convert_top,bool convert_yy,char * pt,const char * ptlim,int modifier)756 _yconv(int a, int b, bool convert_top, bool convert_yy,
757 char *pt, const char *ptlim, int modifier)
758 {
759 register int lead;
760 register int trail;
761
762 int DIVISOR = 100;
763 trail = a % DIVISOR + b % DIVISOR;
764 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
765 trail %= DIVISOR;
766 if (trail < 0 && lead > 0) {
767 trail += DIVISOR;
768 --lead;
769 } else if (lead < 0 && trail > 0) {
770 trail -= DIVISOR;
771 ++lead;
772 }
773 if (convert_top) {
774 if (lead == 0 && trail < 0)
775 pt = _add("-0", pt, ptlim, modifier);
776 else
777 pt = _conv(lead, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
778 }
779 if (convert_yy)
780 pt = _conv(((trail < 0) ? -trail : trail), getformat(modifier, "02", " 2", " ", "02"), pt,
781 ptlim);
782 return pt;
783 }
784