1 /* Copyright (C) 1991, 92, 93, 94, 95, 96 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public License as
6 published by the Free Software Foundation; either version 2 of the
7 License, or (at your option) any later version.
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public
15 License along with the GNU C Library; see the file COPYING.LIB. If
16 not, write to the Free Software Foundation, Inc., 675 Mass Ave,
17 Cambridge, MA 02139, USA. */
24 # define HAVE_LIMITS_H 1
26 # define HAVE_MBRLEN 1
27 # define HAVE_TM_GMTOFF 1
28 # define HAVE_TM_ZONE 1
29 # define MULTIBYTE_IS_FORMAT_SAFE 1
30 # define STDC_HEADERS 1
31 # include <ansidecl.h>
32 # include "../locale/localeinfo.h"
35 #include <sys/types.h> /* Some systems define `time_t' here. */
37 #ifdef TIME_WITH_SYS_TIME
38 # include <sys/time.h>
41 # ifdef HAVE_SYS_TIME_H
42 # include <sys/time.h>
48 /* Do multibyte processing if multibytes are supported, unless
49 multibyte sequences are safe in formats. Multibyte sequences are
50 safe if they cannot contain byte sequences that look like format
51 conversion specifications. The GNU C Library uses UTF8 multibyte
52 encoding, which is safe for formats, but strftime.c can be used
53 with other C libraries that use unsafe encodings. */
54 #define DO_MULTIBYTE (HAVE_MBLEN && ! MULTIBYTE_IS_FORMAT_SAFE)
60 /* Simulate mbrlen with mblen as best we can. */
61 # define mbstate_t int
62 # define mbrlen(s, n, ps) mblen (s, n)
63 # define mbsinit(ps) (*(ps) == 0)
65 static const mbstate_t mbstate_zero;
77 # define memcpy(d, s, n) bcopy (s, d, n)
81 #if defined (__GNUC__) || (defined (__STDC__) && __STDC__)
82 #define __P(args) args
100 #define TYPE_SIGNED(t) ((t) -1 < 0)
102 /* Bound on length of the string representing an integer value of type t.
103 Subtract one for the sign bit if t is signed;
104 302 / 1000 is log10 (2) rounded up;
105 add one for integer division truncation;
106 add one more for a minus sign if t is signed. */
107 #define INT_STRLEN_BOUND(t) \
108 ((sizeof (t) * CHAR_BIT - TYPE_SIGNED (t)) * 302 / 100 + 1 + TYPE_SIGNED (t))
110 #define TM_YEAR_BASE 1900
113 /* Nonzero if YEAR is a leap year (every 4 years,
114 except every 100th isn't, and every 400th is). */
115 #define __isleap(year) \
116 ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
121 # define gmtime_r __gmtime_r
122 # define localtime_r __localtime_r
124 # if ! HAVE_LOCALTIME_R
125 # if ! HAVE_TM_GMTOFF
126 /* Approximate gmtime_r as best we can in its absence. */
127 #define gmtime_r my_gmtime_r
128 static struct tm *gmtime_r __P ((const time_t *, struct tm *));
134 struct tm *l = gmtime (t);
140 # endif /* ! HAVE_TM_GMTOFF */
142 /* Approximate localtime_r as best we can in its absence. */
143 #define localtime_r my_localtime_r
144 static struct tm *localtime_r __P ((const time_t *, struct tm *));
150 struct tm *l = localtime (t);
156 # endif /* ! HAVE_LOCALTIME_R */
157 #endif /* ! defined (_LIBC) */
173 #define cpy(n, s) add ((n), memcpy((PTR) p, (PTR) (s), (n)))
176 /* Yield the difference between *A and *B,
177 measured in seconds, ignoring leap seconds. */
178 static int tm_diff __P ((const struct tm *, const struct tm *));
184 /* Compute intervening leap days correctly even if year is negative.
185 Take care to avoid int overflow in leap day calculations,
186 but it's OK to assume that A and B are close to each other. */
187 int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
188 int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
189 int a100 = a4 / 25 - (a4 % 25 < 0);
190 int b100 = b4 / 25 - (b4 % 25 < 0);
191 int a400 = a100 >> 2;
192 int b400 = b100 >> 2;
193 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
194 int years = a->tm_year - b->tm_year;
195 int days = (365 * years + intervening_leap_days
196 + (a->tm_yday - b->tm_yday));
197 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
198 + (a->tm_min - b->tm_min))
199 + (a->tm_sec - b->tm_sec));
201 #endif /* ! HAVE_TM_GMTOFF */
205 /* The number of days from the first day of the first ISO week of this
206 year to the year day YDAY with week day WDAY. ISO weeks start on
207 Monday; the first ISO week has the year's first Thursday. YDAY may
208 be as small as YDAY_MINIMUM. */
209 #define ISO_WEEK_START_WDAY 1 /* Monday */
210 #define ISO_WEEK1_WDAY 4 /* Thursday */
211 #define YDAY_MINIMUM (-366)
212 static int iso_week_days __P ((int, int));
217 iso_week_days (yday, wday)
221 /* Add enough to the first operand of % to make it nonnegative. */
222 int big_enough_multiple_of_7 = (-YDAY_MINIMUM / 7 + 2) * 7;
224 - (yday - wday + ISO_WEEK1_WDAY + big_enough_multiple_of_7) % 7
225 + ISO_WEEK1_WDAY - ISO_WEEK_START_WDAY);
230 static char const weekday_name[][10] =
232 "Sunday", "Monday", "Tuesday", "Wednesday",
233 "Thursday", "Friday", "Saturday"
235 static char const month_name[][10] =
237 "January", "February", "March", "April", "May", "June",
238 "July", "August", "September", "October", "November", "December"
242 /* Write information from TP into S according to the format
243 string FORMAT, writing no more that MAXSIZE characters
244 (including the terminating '\0') and returning number of
245 characters written. If S is NULL, nothing will be written
246 anywhere, so to determine how many characters would be
247 written, use NULL for S and (size_t) UINT_MAX for MAXSIZE. */
249 strftime (s, maxsize, format, tp)
253 register const struct tm *tp;
255 int hour12 = tp->tm_hour;
257 const char *const a_wkday = _NL_CURRENT (LC_TIME, ABDAY_1 + tp->tm_wday);
258 const char *const f_wkday = _NL_CURRENT (LC_TIME, DAY_1 + tp->tm_wday);
259 const char *const a_month = _NL_CURRENT (LC_TIME, ABMON_1 + tp->tm_mon);
260 const char *const f_month = _NL_CURRENT (LC_TIME, MON_1 + tp->tm_mon);
261 const char *const ampm = _NL_CURRENT (LC_TIME,
262 hour12 > 11 ? PM_STR : AM_STR);
263 size_t aw_len = strlen(a_wkday);
264 size_t am_len = strlen(a_month);
265 size_t ap_len = strlen (ampm);
267 const char *alt_digits = _NL_CURRENT (LC_TIME, ALT_DIGITS);
268 const char *end_alt_digits = _NL_CURRENT (LC_TIME, ALT_DIGITS + 1);
270 const char *const f_wkday = weekday_name[tp->tm_wday];
271 const char *const f_month = month_name[tp->tm_mon];
272 const char *const a_wkday = f_wkday;
273 const char *const a_month = f_month;
274 const char *const ampm = "AMPM" + 2 * (hour12 > 11);
279 size_t wkday_len = strlen (f_wkday);
280 size_t month_len = strlen (f_month);
283 register size_t i = 0;
284 register char *p = s;
285 register const char *f;
289 zone = (const char *) tp->tm_zone;
292 if (!(zone && *zone) && tp->tm_isdst >= 0)
293 zone = tzname[tp->tm_isdst];
296 zone = ""; /* POSIX.2 requires the empty string here. */
298 zonelen = strlen (zone);
303 if (hour12 == 0) hour12 = 12;
305 for (f = format; *f != '\0'; ++f)
307 int pad; /* Padding for number ('-', '_', or 0). */
308 int modifier; /* Field modifier ('E', 'O', or 0). */
309 int digits; /* Max digits for numeric format. */
310 int number_value; /* Numeric value to be printed. */
311 int negative_number; /* 1 if the number is negative. */
314 char buf[1 + (sizeof (int) < sizeof (time_t)
315 ? INT_STRLEN_BOUND (time_t)
316 : INT_STRLEN_BOUND (int))];
325 case '\a': case '\b': case '\t': case '\n':
326 case '\v': case '\f': case '\r':
327 case ' ': case '!': case '"': case '#': case '&': case'\'':
328 case '(': case ')': case '*': case '+': case ',': case '-':
329 case '.': case '/': case '0': case '1': case '2': case '3':
330 case '4': case '5': case '6': case '7': case '8': case '9':
331 case ':': case ';': case '<': case '=': case '>': case '?':
332 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
333 case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
334 case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
335 case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
336 case 'Y': case 'Z': case '[': case'\\': case ']': case '^':
337 case '_': case 'a': case 'b': case 'c': case 'd': case 'e':
338 case 'f': case 'g': case 'h': case 'i': case 'j': case 'k':
339 case 'l': case 'm': case 'n': case 'o': case 'p': case 'q':
340 case 'r': case 's': case 't': case 'u': case 'v': case 'w':
341 case 'x': case 'y': case 'z': case '{': case '|': case '}':
343 /* The C Standard requires these 98 characters (plus '%') to
344 be in the basic execution character set. None of these
345 characters can start a multibyte sequence, so they need
346 not be analyzed further. */
351 /* Copy this multibyte sequence until we reach its end, find
352 an error, or come back to the initial shift state. */
354 mbstate_t mbstate = mbstate_zero;
359 size_t bytes = mbrlen (f + len, (size_t) -1, &mbstate);
364 if (bytes == (size_t) -2 || bytes == (size_t) -1)
372 while (! mbsinit (&mbstate));
379 #else /* ! DO_MULTIBYTE */
381 /* Either multibyte encodings are not supported, or they are
382 safe for formats, so any non-'%' byte can be copied through. */
389 #endif /* ! DO_MULTIBYTE */
391 /* Check for flags that can modify a number format. */
405 /* Check for modifiers. */
418 /* Now do the specified format. */
421 #define DO_NUMBER(d, v) \
422 digits = d; number_value = v; goto do_number
423 #define DO_NUMBER_SPACEPAD(d, v) \
424 digits = d; number_value = v; goto do_number_spacepad
426 case '\0': /* GNU extension: % at end of format. */
438 cpy (aw_len, a_wkday);
444 cpy (wkday_len, f_wkday);
448 case 'h': /* POSIX.2 extension. */
451 cpy (am_len, a_month);
457 cpy (month_len, f_month);
464 if (! (modifier == 'E'
465 && *(subfmt = _NL_CURRENT (LC_TIME, ERA_D_T_FMT)) != '\0'))
466 subfmt = _NL_CURRENT (LC_TIME, D_T_FMT);
468 subfmt = "%a %b %e %H:%M:%S %Z %Y";
473 size_t len = strftime (p, maxsize - i, subfmt, tp);
474 if (len == 0 && *subfmt)
480 case 'C': /* POSIX.2 extension. */
484 /* XXX %EC is not implemented yet. */
487 int year = tp->tm_year + TM_YEAR_BASE;
488 DO_NUMBER (1, year / 100 - (year % 100 < 0));
495 if (! (modifier == 'E'
496 && *(subfmt = _NL_CURRENT (LC_TIME, ERA_D_FMT)) != '\0'))
497 subfmt = _NL_CURRENT (LC_TIME, D_FMT);
501 case 'D': /* POSIX.2 extension. */
511 DO_NUMBER (2, tp->tm_mday);
513 case 'e': /* POSIX.2 extension. */
517 DO_NUMBER_SPACEPAD (2, tp->tm_mday);
519 /* All numeric formats set DIGITS and NUMBER_VALUE and then
520 jump to one of these two labels. */
523 /* Force `_' flag. */
527 /* Format the number according to the MODIFIER flag. */
530 if (modifier == 'O' && 0 <= number_value)
532 /* Get the locale specific alternate representation of
533 the number NUMBER_VALUE. If none exist NULL is returned. */
534 const char *cp = _nl_get_alt_digit (number_value);
538 size_t digitlen = strlen (cp);
548 unsigned int u = number_value;
550 bufp = buf + sizeof (buf);
551 negative_number = number_value < 0;
557 *--bufp = u % 10 + '0';
558 while ((u /= 10) != 0);
561 do_number_sign_and_padding:
567 int padding = digits - (buf + sizeof (buf) - bufp);
571 while (0 < padding--)
576 bufp += negative_number;
577 while (0 < padding--)
584 cpy (buf + sizeof (buf) - bufp, bufp);
592 DO_NUMBER (2, tp->tm_hour);
598 DO_NUMBER (2, hour12);
600 case 'k': /* GNU extension. */
604 DO_NUMBER_SPACEPAD (2, tp->tm_hour);
606 case 'l': /* GNU extension. */
610 DO_NUMBER_SPACEPAD (2, hour12);
616 DO_NUMBER (3, 1 + tp->tm_yday);
622 DO_NUMBER (2, tp->tm_min);
628 DO_NUMBER (2, tp->tm_mon + 1);
630 case 'n': /* POSIX.2 extension. */
638 case 'R': /* GNU extension. */
642 case 'r': /* POSIX.2 extension. */
644 if (*(subfmt = _NL_CURRENT (LC_TIME, T_FMT_AMPM)) == '\0')
646 subfmt = "%I:%M:%S %p";
653 DO_NUMBER (2, tp->tm_sec);
655 case 's': /* GNU extension. */
658 time_t t = mktime (<m);
660 /* Generate string value for T using time_t arithmetic;
661 this works even if sizeof (long) < sizeof (time_t). */
663 bufp = buf + sizeof (buf);
664 negative_number = t < 0;
675 /* Adjust if division truncates to minus infinity. */
676 if (0 < -1 % 10 && d < 0)
688 goto do_number_sign_and_padding;
695 if (! (modifier == 'E'
696 && *(subfmt = _NL_CURRENT (LC_TIME, ERA_T_FMT)) != '\0'))
697 subfmt = _NL_CURRENT (LC_TIME, T_FMT);
701 case 'T': /* POSIX.2 extension. */
705 case 't': /* POSIX.2 extension. */
709 case 'u': /* POSIX.2 extension. */
710 DO_NUMBER (1, (tp->tm_wday - 1 + 7) % 7 + 1);
716 DO_NUMBER (2, (tp->tm_yday - tp->tm_wday + 7) / 7);
719 case 'g': /* GNU extension. */
720 case 'G': /* GNU extension. */
724 int year = tp->tm_year + TM_YEAR_BASE;
725 int days = iso_week_days (tp->tm_yday, tp->tm_wday);
729 /* This ISO week belongs to the previous year. */
731 days = iso_week_days (tp->tm_yday + (365 + __isleap (year)),
736 int d = iso_week_days (tp->tm_yday - (365 + __isleap (year)),
740 /* This ISO week belongs to the next year. */
749 DO_NUMBER (2, (year % 100 + 100) % 100);
755 DO_NUMBER (2, days / 7 + 1);
763 DO_NUMBER (2, (tp->tm_yday - (tp->tm_wday - 1 + 7) % 7 + 7) / 7);
769 DO_NUMBER (1, tp->tm_wday);
774 && *(subfmt = _NL_CURRENT (LC_TIME, ERA_YEAR)) != '\0')
780 DO_NUMBER (1, tp->tm_year + TM_YEAR_BASE);
784 /* XXX %Ey is not implemented yet. */
786 DO_NUMBER (2, (tp->tm_year % 100 + 100) % 100);
792 case 'z': /* GNU extension. */
793 if (tp->tm_isdst < 0)
799 diff = tp->tm_gmtoff;
803 time_t lt = mktime (<m);
805 if (lt == (time_t) -1)
807 /* mktime returns -1 for errors, but -1 is also a
808 valid time_t value. Check whether an error really
811 localtime_r (<, &tm);
813 if ((ltm.tm_sec ^ tm.tm_sec)
814 | (ltm.tm_min ^ tm.tm_min)
815 | (ltm.tm_hour ^ tm.tm_hour)
816 | (ltm.tm_mday ^ tm.tm_mday)
817 | (ltm.tm_mon ^ tm.tm_mon)
818 | (ltm.tm_year ^ tm.tm_year))
822 if (! gmtime_r (<, >m))
825 diff = tm_diff (<m, >m);
837 DO_NUMBER (4, (diff / 60) * 100 + diff % 60);
841 /* Unknown format; output the format, including the '%',
842 since this is most likely the right thing to do if a
843 multibyte string has been misparsed. */
847 for (flen = 2; f[1 - flen] != '%'; flen++)
849 cpy (flen, &f[1 - flen]);