"Fossies" - the Fresh Open Source Software Archive

Member "strftime.c" (20 Nov 2022, 16725 Bytes) of package /linux/misc/tzcode2022g.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "strftime.c": 2022f_vs_2022g.

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