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