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