X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fstrftime.c;h=b68b4cf5addfabcd45ce41b06f95f9ae08eedfc5;hb=5131be5b019b1110cd597012b63e48886aaaafc7;hp=2ef27c22a42fc8c743a3bc03b794b6ae7cc52866;hpb=fdc514fe1ec097070facba3f7a7112749b3a7789;p=gnulib.git diff --git a/lib/strftime.c b/lib/strftime.c index 2ef27c22a..b68b4cf5a 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 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 @@ -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 @@ -279,12 +292,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 @@ -360,20 +373,13 @@ 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. */ + written, use NULL for S and (size_t) -1 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) @@ -426,6 +432,15 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, 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 @@ -464,16 +479,17 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, int modifier; /* Field modifier ('E', 'O', or 0). */ int digits; /* 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; /* 1 if the number is negative. */ const CHAR_T *subfmt; CHAR_T *bufp; CHAR_T buf[1 + (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 = false; + bool change_case = false; int format_char; #if DO_MULTIBYTE && !defined COMPILE_WIDE @@ -578,10 +594,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,10 +644,14 @@ 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 #define DO_NUMBER_SPACEPAD(d, v) \ - digits = d > width ? d : width; \ + digits = d; \ number_value = v; goto do_number_spacepad case L_('%'): @@ -645,8 +665,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 +680,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 +694,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 +711,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); @@ -741,7 +761,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 +773,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 +814,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 +854,8 @@ 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 three labels. */ do_number_spacepad: /* Force `_' flag unless overridden by `0' or `-' flag. */ @@ -840,14 +863,22 @@ 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: + /* 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,21 +896,23 @@ 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 + { + *--bufp = u_number_value % 10 + L_('0'); + u_number_value /= 10; + } + while (u_number_value != 0); do_number_sign_and_padding: + if (digits < width) + digits = width; + if (negative_number) *--bufp = L_('-'); @@ -959,7 +992,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 +1004,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. */ @@ -995,7 +1028,7 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, break; case L_('P'): - to_lowcase = 1; + to_lowcase = true; #if !defined _NL_CURRENT && HAVE_STRFTIME format_char = L_('p'); #endif @@ -1004,8 +1037,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,20 +1088,7 @@ 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); @@ -1116,14 +1136,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 +1161,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 +1169,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 +1223,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 +1243,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 = "";