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