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
45struct 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"
60static struct lc_time_T                localebuf;
61static 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
68static 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
114static char *   _add(const char *, char *, const char *, int);
115static char *   _conv(int, const char *, char *, const char *);
116static char *   _fmt(const char *, const struct tm *, char *, const char *,
117            int *);
118static char *   _yconv(int, int, int, int, char *, const char *, int);
119static char *   getformat(int, char *, char *, char *, char *);
120
121extern 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
134size_t
135strftime(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
169static 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
185static 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;
192label:
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
643static 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
653static 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
701static 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