• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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