From c0e63657aced0f505bab0d91a2d5db472d7d1013 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Thu, 22 Sep 2005 23:37:18 +0000 Subject: [PATCH] * strftime.c (my_strftime): Rewrite the previous change slightly, to make it a bit faster and (I hope) clearer. * strftime.c (my_strftime): Add support for %:z, %::z, %:::z. Fix bug in formats like %2N. * strftime.c [FPRINTFTIME] (fprintftime): Provide a new interface: size_t fprintftime (FILE *fp, char const *fmt, struct tm const *tm, int utc, int nanoseconds); Background: date should not have to allocate a megabyte of virtual memory to handle a format argument like +%1048575T. When implemented with strftime, it must allocate such a buffer, use strftime to fill it in, print it, then free it. With fprintftime, it simply prints everything and exits. With no need for memory allocation, that's one fewer way to fail. * strftime.c (my_strftime): Parse the colons of %:::z *after* the optional field width, not before, so we accept %9:z, not %:9z. (my_strftime): Be sure to use L_('x') for literals. --- lib/strftime.c | 273 +++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 209 insertions(+), 64 deletions(-) diff --git a/lib/strftime.c b/lib/strftime.c index b68b4cf5a..d740ae018 100644 --- a/lib/strftime.c +++ b/lib/strftime.c @@ -155,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 @@ -173,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); \ @@ -181,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); \ @@ -194,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 @@ -263,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) @@ -280,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 @@ -355,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 @@ -374,19 +435,20 @@ static CHAR_T const month_name[][10] = #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) -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) +/* 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 @@ -426,7 +488,7 @@ 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; @@ -477,18 +539,24 @@ 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. */ unsigned int u_number_value; /* (unsigned int) number_value. */ - bool negative_number; /* 1 if the number is negative. */ + 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; bool to_lowcase = false; - bool to_uppcase = false; + bool to_uppcase = upcase; + size_t colons; bool change_case = false; int format_char; @@ -523,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: @@ -574,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; } @@ -650,6 +718,15 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, 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; \ number_value = v; goto do_number_spacepad @@ -657,7 +734,7 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, case L_('%'): if (modifier != 0) goto bad_format; - add (1, *p = *f); + add1 (*f); break; case L_('a'): @@ -741,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; @@ -855,7 +928,11 @@ 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 (or U_NUMBER_VALUE) - and then jump to one of these three labels. */ + 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. */ @@ -868,6 +945,10 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, 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 @@ -904,22 +985,31 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, 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); + while (u_number_value != 0 || tz_colon_mask != 0); do_number_sign_and_padding: if (digits < width) digits = width; - if (negative_number) - *--bufp = L_('-'); + 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) { @@ -932,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); @@ -953,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); @@ -1012,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; @@ -1020,11 +1113,11 @@ 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'): @@ -1093,6 +1186,7 @@ my_strftime (CHAR_T *s, size_t maxsize, const CHAR_T *format, while (t != 0); digits = 1; + always_output_a_sign = false; goto do_number_sign_and_padding; } @@ -1118,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'): @@ -1280,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 @@ -1324,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. */ @@ -1354,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 -- 2.11.0