X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fstrftime.c;h=13c8acc8518f12e82b70d6e221afe37884557eab;hb=04b302b5addeca4d476007e27c2ad8be6bacceee;hp=2ef27c22a42fc8c743a3bc03b794b6ae7cc52866;hpb=fdc514fe1ec097070facba3f7a7112749b3a7789;p=gnulib.git diff --git a/lib/strftime.c b/lib/strftime.c index 2ef27c22a..13c8acc85 100644 --- a/lib/strftime.c +++ b/lib/strftime.c @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-1999, 2000, 2001, 2003, 2004 Free Software +/* Copyright (C) 1991-1999, 2000, 2001, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU C Library. @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H # include @@ -47,7 +47,7 @@ # include # endif #endif -#if HAVE_TZNAME +#if HAVE_TZNAME && ! defined tzname extern char *tzname[]; #endif @@ -72,6 +72,7 @@ extern char *tzname[]; #endif #include +#include #include #include #include @@ -104,15 +105,27 @@ extern char *tzname[]; # endif #endif -#define TYPE_SIGNED(t) ((t) -1 < 0) - -/* Bound on length of the string representing an integer value of type t. - Subtract one for the sign bit if t is signed; - 302 / 1000 is log10 (2) rounded up; - add one for integer division truncation; - add one more for a minus sign if t is signed. */ +/* Shift A right by B bits portably, by dividing A by 2**B and + truncating towards minus infinity. A and B should be free of side + effects, and B should be in the range 0 <= B <= INT_BITS - 2, where + INT_BITS is the number of useful bits in an int. GNU code can + assume that INT_BITS is at least 32. + + ISO C99 says that A >> B is implementation-defined if A < 0. Some + implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift + right in the usual way when A < 0, so SHR falls back on division if + ordinary A >> B doesn't seem to be the usual signed shift. */ +#define SHR(a, b) \ + (-1 >> 1 == -1 \ + ? (a) >> (b) \ + : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0)) + +/* Bound on length of the string representing an integer type or expression T. + Subtract 1 for the sign bit if t is signed; log10 (2.0) < 146/485; + add 1 for integer division truncation; add 1 more for a minus sign + if needed. */ #define INT_STRLEN_BOUND(t) \ - ((sizeof (t) * CHAR_BIT - TYPE_SIGNED (t)) * 302 / 1000 + 1 + TYPE_SIGNED (t)) + ((sizeof (t) * CHAR_BIT - 1) * 146 / 485 + 2) #define TM_YEAR_BASE 1900 @@ -142,7 +155,24 @@ extern char *tzname[]; #endif -#ifdef COMPILE_WIDE +#ifndef FPRINTFTIME +# define FPRINTFTIME 0 +#endif + +#if FPRINTFTIME +# define STREAM_OR_CHAR_T FILE +# define STRFTIME_ARG(x) /* empty */ +#else +# define STREAM_OR_CHAR_T CHAR_T +# define STRFTIME_ARG(x) x, +#endif + +#if FPRINTFTIME +# define memset_byte(P, Len, Byte) \ + do { size_t _i; for (_i = 0; _i < Len; _i++) fputc (Byte, P); } while (0) +# define memset_space(P, Len) memset_byte (P, Len, ' ') +# define memset_zero(P, Len) memset_byte (P, Len, '0') +#elif defined COMPILE_WIDE # define memset_space(P, Len) (wmemset (P, L' ', Len), (P) += (Len)) # define memset_zero(P, Len) (wmemset (P, L'0', Len), (P) += (Len)) #else @@ -160,7 +190,7 @@ extern char *tzname[]; return 0; \ if (p) \ { \ - if (_delta > 0) \ + if (digits == 0 && _delta > 0) \ { \ if (pad == L_('0')) \ memset_zero (p, _delta); \ @@ -168,12 +198,28 @@ extern char *tzname[]; memset_space (p, _delta); \ } \ f; \ - p += _n; \ + p += FPRINTFTIME ? 0 : _n; \ } \ i += _incr; \ } while (0) -#define cpy(n, s) \ +#if FPRINTFTIME +# define add1(C) add (1, fputc (C, p)) +#else +# define add1(C) add (1, *p = C) +#endif + +#if FPRINTFTIME +# define cpy(n, s) \ + add ((n), \ + if (to_lowcase) \ + fwrite_lowcase (p, (s), _n); \ + else if (to_uppcase) \ + fwrite_uppcase (p, (s), _n); \ + else \ + fwrite ((s), _n, 1, p)) +#else +# define cpy(n, s) \ add ((n), \ if (to_lowcase) \ memcpy_lowcase (p, (s), _n LOCALE_ARG); \ @@ -181,6 +227,7 @@ extern char *tzname[]; memcpy_uppcase (p, (s), _n LOCALE_ARG); \ else \ MEMCPY ((void *) p, (void const *) (s), _n)) +#endif #ifdef COMPILE_WIDE # ifndef USE_IN_EXTENDED_LOCALE_MODEL @@ -250,6 +297,27 @@ extern char *tzname[]; more reliable way to accept other sets of digits. */ #define ISDIGIT(Ch) ((unsigned int) (Ch) - L_('0') <= 9) +#if FPRINTFTIME +static void +fwrite_lowcase (FILE *fp, const CHAR_T *src, size_t len) +{ + while (len-- > 0) + { + fputc (TOLOWER ((UCHAR_T) *src, loc), fp); + ++src; + } +} + +static void +fwrite_uppcase (FILE *fp, const CHAR_T *src, size_t len) +{ + while (len-- > 0) + { + fputc (TOUPPER ((UCHAR_T) *src, loc), fp); + ++src; + } +} +#else static CHAR_T * memcpy_lowcase (CHAR_T *dest, const CHAR_T *src, size_t len LOCALE_PARAM_PROTO) @@ -267,6 +335,7 @@ memcpy_uppcase (CHAR_T *dest, const CHAR_T *src, dest[len] = TOUPPER ((UCHAR_T) src[len], loc); return dest; } +#endif #if ! HAVE_TM_GMTOFF @@ -279,12 +348,12 @@ tm_diff (const struct tm *a, const struct tm *b) /* Compute intervening leap days correctly even if year is negative. Take care to avoid int overflow in leap day calculations, but it's OK to assume that A and B are close to each other. */ - int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3); - int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3); + int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3); + int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3); int a100 = a4 / 25 - (a4 % 25 < 0); int b100 = b4 / 25 - (b4 % 25 < 0); - int a400 = a100 >> 2; - int b400 = b100 >> 2; + int a400 = SHR (a100, 2); + int b400 = SHR (b100, 2); int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400); int years = a->tm_year - b->tm_year; int days = (365 * years + intervening_leap_days @@ -342,11 +411,16 @@ static CHAR_T const month_name[][10] = # define my_strftime nstrftime #endif +#if FPRINTFTIME +# undef my_strftime +# define my_strftime fprintftime +#endif + #ifdef my_strftime # define extra_args , ut, ns # define extra_args_spec , int ut, int ns #else -# ifdef COMPILE_WIDE +# if defined COMPILE_WIDE # define my_strftime wcsftime # define nl_get_alt_digit _nl_get_walt_digit # else @@ -360,27 +434,21 @@ static CHAR_T const month_name[][10] = # define ns 0 #endif -#if ! defined _LIBC && ! HAVE_RUN_TZSET_TEST -/* Solaris 2.5.x and 2.6 tzset sometimes modify the storage returned - by localtime. On such systems, we must use the tzset and localtime - wrappers to work around the bug. */ -"you must run the autoconf test for a working tzset function" -#endif - -/* Write information from TP into S according to the format - string FORMAT, writing no more that MAXSIZE characters - (including the terminating '\0') and returning number of - characters written. If S is NULL, nothing will be written - anywhere, so to determine how many characters would be - written, use NULL for S and (size_t) UINT_MAX for MAXSIZE. */ -size_t -my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, - const struct tm *tp extra_args_spec LOCALE_PARAM_PROTO) +/* Just like my_strftime, below, but with one more parameter, UPCASE, + to indicate that the result should be converted to upper case. */ +static size_t +strftime_case_ (bool upcase, STREAM_OR_CHAR_T *s, + STRFTIME_ARG (size_t maxsize) + const CHAR_T *format, + const struct tm *tp extra_args_spec LOCALE_PARAM_PROTO) { #if defined _LIBC && defined USE_IN_EXTENDED_LOCALE_MODEL struct locale_data *const current = loc->__locales[LC_TIME]; #endif +#if FPRINTFTIME + size_t maxsize = (size_t) -1; +#endif int hour12 = tp->tm_hour; #ifdef _NL_CURRENT @@ -420,12 +488,21 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, #endif const char *zone; size_t i = 0; - CHAR_T *p = s; + STREAM_OR_CHAR_T *p = s; const CHAR_T *f; #if DO_MULTIBYTE && !defined COMPILE_WIDE const char *format_end = NULL; #endif +#if ! defined _LIBC && ! HAVE_RUN_TZSET_TEST + /* Solaris 2.5.x and 2.6 tzset sometimes modify the storage returned + by localtime. On such systems, we must either use the tzset and + localtime wrappers to work around the bug (which sets + HAVE_RUN_TZSET_TEST) or make a copy of the structure. */ + struct tm copy = *tp; + tp = © +#endif + zone = NULL; #if HAVE_TM_ZONE /* The POSIX test suite assumes that setting @@ -462,18 +539,25 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, { int pad = 0; /* Padding for number ('-', '_', or 0). */ int modifier; /* Field modifier ('E', 'O', or 0). */ - int digits; /* Max digits for numeric format. */ + int digits = 0; /* Max digits for numeric format. */ int number_value; /* Numeric value to be printed. */ - int negative_number; /* 1 if the number is negative. */ + unsigned int u_number_value; /* (unsigned int) number_value. */ + bool negative_number; /* The number is negative. */ + bool always_output_a_sign; /* +/- should always be output. */ + int tz_colon_mask; /* Bitmask of where ':' should appear. */ const CHAR_T *subfmt; + CHAR_T sign_char; CHAR_T *bufp; - CHAR_T buf[1 + (sizeof (int) < sizeof (time_t) - ? INT_STRLEN_BOUND (time_t) - : INT_STRLEN_BOUND (int))]; + CHAR_T buf[1 + + 2 /* for the two colons in a %::z or %:::z time zone */ + + (sizeof (int) < sizeof (time_t) + ? INT_STRLEN_BOUND (time_t) + : INT_STRLEN_BOUND (int))]; int width = -1; - int to_lowcase = 0; - int to_uppcase = 0; - int change_case = 0; + bool to_lowcase = false; + bool to_uppcase = upcase; + size_t colons; + bool change_case = false; int format_char; #if DO_MULTIBYTE && !defined COMPILE_WIDE @@ -507,7 +591,7 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, be in the basic execution character set. None of these characters can start a multibyte sequence, so they need not be analyzed further. */ - add (1, *p = *f); + add1 (*f); continue; default: @@ -558,7 +642,7 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, or this is the wide character version. */ if (*f != L_('%')) { - add (1, *p = *f); + add1 (*f); continue; } @@ -578,10 +662,10 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, /* This changes textual output. */ case L_('^'): - to_uppcase = 1; + to_uppcase = true; continue; case L_('#'): - change_case = 1; + change_case = true; continue; default: @@ -628,16 +712,29 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, switch (format_char) { #define DO_NUMBER(d, v) \ - digits = d > width ? d : width; \ + digits = d; \ number_value = v; goto do_number +#define DO_SIGNED_NUMBER(d, negative, v) \ + digits = d; \ + negative_number = negative; \ + u_number_value = v; goto do_signed_number + + /* The mask is not what you might think. + When the ordinal i'th bit is set, insert a colon + before the i'th digit of the time zone representation. */ +#define DO_TZ_OFFSET(d, negative, mask, v) \ + digits = d; \ + negative_number = negative; \ + tz_colon_mask = mask; \ + u_number_value = v; goto do_tz_offset #define DO_NUMBER_SPACEPAD(d, v) \ - digits = d > width ? d : width; \ + digits = d; \ number_value = v; goto do_number_spacepad case L_('%'): if (modifier != 0) goto bad_format; - add (1, *p = *f); + add1 (*f); break; case L_('a'): @@ -645,8 +742,8 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, goto bad_format; if (change_case) { - to_uppcase = 1; - to_lowcase = 0; + to_uppcase = true; + to_lowcase = false; } #if defined _NL_CURRENT || !HAVE_STRFTIME cpy (aw_len, a_wkday); @@ -660,8 +757,8 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, goto bad_format; if (change_case) { - to_uppcase = 1; - to_lowcase = 0; + to_uppcase = true; + to_lowcase = false; } #if defined _NL_CURRENT || !HAVE_STRFTIME cpy (STRLEN (f_wkday), f_wkday); @@ -674,8 +771,8 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, case L_('h'): if (change_case) { - to_uppcase = 1; - to_lowcase = 0; + to_uppcase = true; + to_lowcase = false; } if (modifier != 0) goto bad_format; @@ -691,8 +788,8 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, goto bad_format; if (change_case) { - to_uppcase = 1; - to_lowcase = 0; + to_uppcase = true; + to_lowcase = false; } #if defined _NL_CURRENT || !HAVE_STRFTIME cpy (STRLEN (f_month), f_month); @@ -721,18 +818,14 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, subformat: { - CHAR_T *old_start = p; - size_t len = my_strftime (NULL, (size_t) -1, subfmt, - tp extra_args LOCALE_ARG); - add (len, my_strftime (p, maxsize - i, subfmt, - tp extra_args LOCALE_ARG)); - - if (to_uppcase) - while (old_start < p) - { - *old_start = TOUPPER ((UCHAR_T) *old_start, loc); - ++old_start; - } + size_t len = strftime_case_ (to_uppcase, + NULL, STRFTIME_ARG ((size_t) -1) + subfmt, + tp extra_args LOCALE_ARG); + add (len, strftime_case_ (to_uppcase, p, + STRFTIME_ARG (maxsize - i) + subfmt, + tp extra_args LOCALE_ARG)); } break; @@ -741,7 +834,7 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, { /* The relevant information is available only via the underlying strftime implementation, so use that. */ - char ufmt[4]; + char ufmt[5]; char *u = ufmt; char ubuf[1024]; /* enough for any single format in practice */ size_t len; @@ -753,15 +846,17 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, size_t strftime (); # endif + /* The space helps distinguish strftime failure from empty + output. */ + *u++ = ' '; *u++ = '%'; if (modifier != 0) *u++ = modifier; *u++ = format_char; *u = '\0'; len = strftime (ubuf, sizeof ubuf, ufmt, tp); - if (len == 0 && ubuf[0] != '\0') - return 0; - cpy (len, ubuf); + if (len != 0) + cpy (len - 1, ubuf + 1); } break; #endif @@ -792,8 +887,9 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, } { - int year = tp->tm_year + TM_YEAR_BASE; - DO_NUMBER (1, year / 100 - (year % 100 < 0)); + int century = tp->tm_year / 100 + TM_YEAR_BASE / 100; + century -= tp->tm_year % 100 < 0 && 0 < century; + DO_SIGNED_NUMBER (2, tp->tm_year < - TM_YEAR_BASE, century); } case L_('x'): @@ -831,8 +927,12 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, DO_NUMBER_SPACEPAD (2, tp->tm_mday); - /* All numeric formats set DIGITS and NUMBER_VALUE and then - jump to one of these two labels. */ + /* All numeric formats set DIGITS and NUMBER_VALUE (or U_NUMBER_VALUE) + and then jump to one of these labels. */ + + do_tz_offset: + always_output_a_sign = true; + goto do_number_body; do_number_spacepad: /* Force `_' flag unless overridden by `0' or `-' flag. */ @@ -840,14 +940,26 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, pad = L_('_'); do_number: - /* Format the number according to the MODIFIER flag. */ - - if (modifier == L_('O') && 0 <= number_value) + /* Format NUMBER_VALUE according to the MODIFIER flag. */ + negative_number = number_value < 0; + u_number_value = number_value; + + do_signed_number: + always_output_a_sign = false; + tz_colon_mask = 0; + + do_number_body: + /* Format U_NUMBER_VALUE according to the MODIFIER flag. + NEGATIVE_NUMBER is nonzero if the original number was + negative; in this case it was converted directly to + unsigned int (i.e., modulo (UINT_MAX + 1)) without + negating it. */ + if (modifier == L_('O') && !negative_number) { #ifdef _NL_CURRENT /* Get the locale specific alternate representation of - the number NUMBER_VALUE. If none exist NULL is returned. */ - const CHAR_T *cp = nl_get_alt_digit (number_value + the number. If none exist NULL is returned. */ + const CHAR_T *cp = nl_get_alt_digit (u_number_value HELPER_LOCALE_ARG); if (cp != NULL) @@ -865,28 +977,39 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, # endif #endif } - { - unsigned int u = number_value; - bufp = buf + sizeof (buf) / sizeof (buf[0]); - negative_number = number_value < 0; + bufp = buf + sizeof (buf) / sizeof (buf[0]); - if (negative_number) - u = -u; + if (negative_number) + u_number_value = - u_number_value; - do - *--bufp = u % 10 + L_('0'); - while ((u /= 10) != 0); - } + do + { + if (tz_colon_mask & 1) + *--bufp = ':'; + tz_colon_mask >>= 1; + *--bufp = u_number_value % 10 + L_('0'); + u_number_value /= 10; + } + while (u_number_value != 0 || tz_colon_mask != 0); do_number_sign_and_padding: - if (negative_number) - *--bufp = L_('-'); + if (digits < width) + digits = width; + + sign_char = (negative_number ? L_('-') + : always_output_a_sign ? L_('+') + : 0); - if (pad != L_('-')) + if (pad == L_('-')) + { + if (sign_char) + add1 (sign_char); + } + else { int padding = digits - (buf + (sizeof (buf) / sizeof (buf[0])) - - bufp); + - bufp) - !!sign_char; if (padding > 0) { @@ -899,20 +1022,16 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, memset_space (p, padding); i += padding; width = width > padding ? width - padding : 0; + if (sign_char) + add1 (sign_char); } else { if ((size_t) digits >= maxsize - i) return 0; - if (negative_number) - { - ++bufp; - - if (p) - *p++ = L_('-'); - ++i; - } + if (sign_char) + add1 (sign_char); if (p) memset_zero (p, padding); @@ -920,6 +1039,11 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, width = 0; } } + else + { + if (sign_char) + add1 (sign_char); + } } cpy (buf + sizeof (buf) / sizeof (buf[0]) - bufp, bufp); @@ -959,7 +1083,7 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, if (modifier == L_('E')) goto bad_format; - DO_NUMBER (3, 1 + tp->tm_yday); + DO_SIGNED_NUMBER (3, tp->tm_yday < -1, tp->tm_yday + 1U); case L_('M'): if (modifier == L_('E')) @@ -971,7 +1095,7 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, if (modifier == L_('E')) goto bad_format; - DO_NUMBER (2, tp->tm_mon + 1); + DO_SIGNED_NUMBER (2, tp->tm_mon < -1, tp->tm_mon + 1U); #ifndef _LIBC case L_('N'): /* GNU extension. */ @@ -979,7 +1103,9 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, goto bad_format; number_value = ns; - if (width != -1) + if (width == -1) + width = 9; + else { /* Take an explicit width less than 9 as a precision. */ int j; @@ -987,15 +1113,15 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, number_value /= 10; } - DO_NUMBER (9, number_value); + DO_NUMBER (width, number_value); #endif case L_('n'): - add (1, *p = L_('\n')); + add1 (L_('\n')); break; case L_('P'): - to_lowcase = 1; + to_lowcase = true; #if !defined _NL_CURRENT && HAVE_STRFTIME format_char = L_('p'); #endif @@ -1004,8 +1130,8 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, case L_('p'): if (change_case) { - to_uppcase = 0; - to_lowcase = 1; + to_uppcase = false; + to_lowcase = true; } #if defined _NL_CURRENT || !HAVE_STRFTIME cpy (ap_len, ampm); @@ -1055,24 +1181,12 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, { int d = t % 10; t /= 10; - - if (negative_number) - { - d = -d; - - /* Adjust if division truncates to minus infinity. */ - if (0 < -1 % 10 && d < 0) - { - t++; - d += 10; - } - } - - *--bufp = d + L_('0'); + *--bufp = (negative_number ? -d : d) + L_('0'); } while (t != 0); digits = 1; + always_output_a_sign = false; goto do_number_sign_and_padding; } @@ -1098,7 +1212,7 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, goto subformat; case L_('t'): - add (1, *p = L_('\t')); + add1 (L_('\t')); break; case L_('u'): @@ -1116,14 +1230,22 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, if (modifier == L_('E')) goto bad_format; { - int year = tp->tm_year + TM_YEAR_BASE; + /* YEAR is a leap year if and only if (tp->tm_year + TM_YEAR_BASE) + is a leap year, except that YEAR and YEAR - 1 both work + correctly even when (tp->tm_year + TM_YEAR_BASE) would + overflow. */ + int year = (tp->tm_year + + (tp->tm_year < 0 + ? TM_YEAR_BASE % 400 + : TM_YEAR_BASE % 400 - 400)); + int year_adjust = 0; int days = iso_week_days (tp->tm_yday, tp->tm_wday); if (days < 0) { /* This ISO week belongs to the previous year. */ - year--; - days = iso_week_days (tp->tm_yday + (365 + __isleap (year)), + year_adjust = -1; + days = iso_week_days (tp->tm_yday + (365 + __isleap (year - 1)), tp->tm_wday); } else @@ -1133,7 +1255,7 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, if (0 <= d) { /* This ISO week belongs to the next year. */ - year++; + year_adjust = 1; days = d; } } @@ -1141,10 +1263,19 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, switch (*f) { case L_('g'): - DO_NUMBER (2, (year % 100 + 100) % 100); + { + int yy = (tp->tm_year % 100 + year_adjust) % 100; + DO_NUMBER (2, (0 <= yy + ? yy + : tp->tm_year < -TM_YEAR_BASE - year_adjust + ? -yy + : yy + 100)); + } case L_('G'): - DO_NUMBER (1, year); + DO_SIGNED_NUMBER (4, tp->tm_year < -TM_YEAR_BASE - year_adjust, + (tp->tm_year + (unsigned int) TM_YEAR_BASE + + year_adjust)); default: DO_NUMBER (2, days / 7 + 1); @@ -1186,7 +1317,8 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, if (modifier == L_('O')) goto bad_format; else - DO_NUMBER (1, tp->tm_year + TM_YEAR_BASE); + DO_SIGNED_NUMBER (4, tp->tm_year < -TM_YEAR_BASE, + tp->tm_year + (unsigned int) TM_YEAR_BASE); case L_('y'): if (modifier == L_('E')) @@ -1205,19 +1337,25 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, # endif #endif } - DO_NUMBER (2, (tp->tm_year % 100 + 100) % 100); + + { + int yy = tp->tm_year % 100; + if (yy < 0) + yy = tp->tm_year < - TM_YEAR_BASE ? -yy : yy + 100; + DO_NUMBER (2, yy); + } case L_('Z'): if (change_case) { - to_uppcase = 0; - to_lowcase = 1; + to_uppcase = false; + to_lowcase = true; } #if HAVE_TZNAME /* The tzset() call might have changed the value. */ if (!(zone && *zone) && tp->tm_isdst >= 0) - zone = tzname[tp->tm_isdst]; + zone = tzname[tp->tm_isdst != 0]; #endif if (! zone) zone = ""; @@ -1236,12 +1374,28 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, #endif break; + case L_(':'): + /* :, ::, and ::: are valid only just before 'z'. + :::: etc. are rejected later. */ + for (colons = 1; f[colons] == L_(':'); colons++) + continue; + if (f[colons] != L_('z')) + goto bad_format; + f += colons; + goto do_z_conversion; + case L_('z'): + colons = 0; + + do_z_conversion: if (tp->tm_isdst < 0) break; { int diff; + int hour_diff; + int min_diff; + int sec_diff; #if HAVE_TM_GMTOFF diff = tp->tm_gmtoff; #else @@ -1280,16 +1434,32 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, } #endif - if (diff < 0) + hour_diff = diff / 60 / 60; + min_diff = diff / 60 % 60; + sec_diff = diff % 60; + + switch (colons) { - add (1, *p = L_('-')); - diff = -diff; - } - else - add (1, *p = L_('+')); + case 0: /* +hhmm */ + DO_TZ_OFFSET (5, diff < 0, 0, hour_diff * 100 + min_diff); - diff /= 60; - DO_NUMBER (4, (diff / 60) * 100 + diff % 60); + case 1: tz_hh_mm: /* +hh:mm */ + DO_TZ_OFFSET (6, diff < 0, 04, hour_diff * 100 + min_diff); + + case 2: tz_hh_mm_ss: /* +hh:mm:ss */ + DO_TZ_OFFSET (9, diff < 0, 024, + hour_diff * 10000 + min_diff * 100 + sec_diff); + + case 3: /* +hh if possible, else +hh:mm, else +hh:mm:ss */ + if (sec_diff != 0) + goto tz_hh_mm_ss; + if (min_diff != 0) + goto tz_hh_mm; + DO_TZ_OFFSET (3, diff < 0, 0, hour_diff); + + default: + goto bad_format; + } } case L_('\0'): /* GNU extension: % at end of format. */ @@ -1310,16 +1480,35 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, } } +#if ! FPRINTFTIME if (p && maxsize != 0) *p = L_('\0'); +#endif + return i; } -#ifdef _LIBC + +/* Write information from TP into S according to the format + string FORMAT, writing no more that MAXSIZE characters + (including the terminating '\0') and returning number of + characters written. If S is NULL, nothing will be written + anywhere, so to determine how many characters would be + written, use NULL for S and (size_t) -1 for MAXSIZE. */ +size_t +my_strftime (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) + const CHAR_T *format, + const struct tm *tp extra_args_spec LOCALE_PARAM_PROTO) +{ + return strftime_case_ (false, s, STRFTIME_ARG (maxsize) + format, tp extra_args LOCALE_ARG); +} + +#if defined _LIBC && ! FPRINTFTIME libc_hidden_def (my_strftime) #endif -#ifdef emacs +#if defined emacs && ! FPRINTFTIME /* For Emacs we have a separate interface which corresponds to the normal strftime function plus the ut argument, but without the ns argument. */ size_t