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