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