* lib/nstrftime.c (width_add, width_add1, width_cpy): New macros, which generalize ‘add’, ‘add1’, ‘cpy’ by adding a new WIDTH parameter. (add, add1, cpy): Use these macros. (width_add): Do not treat digits == 0 as a special case, do not pad if padding is ‘-’, and do not use a negative width. (__strftime_internal): Redo formatting of nanoseconds and numeric timezones to avoid buffer misuse in unusual cases, and so that widths make more sense. Add support for widths greater than 9 to the %N format; they are zero filled on the right. * tests/test-nstrftime.c (posixtm_test): Add a %12N test. --- ChangeLog | 15 +++++ lib/nstrftime.c | 138 ++++++++++++++++++----------------------- tests/test-nstrftime.c | 1 + 3 files changed, 75 insertions(+), 79 deletions(-)
diff --git a/ChangeLog b/ChangeLog index acfa55f56..493211d66 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2019-12-06 Paul Eggert <egg...@cs.ucla.edu> + + nstrftime: better width support for %N, %z + * lib/nstrftime.c (width_add, width_add1, width_cpy): + New macros, which generalize ‘add’, ‘add1’, ‘cpy’ by adding + a new WIDTH parameter. + (add, add1, cpy): Use these macros. + (width_add): Do not treat digits == 0 as a special case, + do not pad if padding is ‘-’, and do not use a negative width. + (__strftime_internal): Redo formatting of nanoseconds and numeric + timezones to avoid buffer misuse in unusual cases, and so that + widths make more sense. Add support for widths greater than 9 to + the %N format; they are zero filled on the right. + * tests/test-nstrftime.c (posixtm_test): Add a %12N test. + 2019-12-05 Bruno Haible <br...@clisp.org> Fix compilation errors in C++ mode on Solaris 10 and Solaris 11. diff --git a/lib/nstrftime.c b/lib/nstrftime.c index 11ad00b10..ada767613 100644 --- a/lib/nstrftime.c +++ b/lib/nstrftime.c @@ -162,19 +162,20 @@ extern char *tzname[]; # define advance(P, N) ((P) += (N)) #endif -#define add(n, f) \ +#define add(n, f) width_add (width, n, f) +#define width_add(width, n, f) \ do \ { \ size_t _n = (n); \ - size_t _w = (width < 0 ? 0 : width); \ + size_t _w = pad == L_('-') || width < 0 ? 0 : width; \ size_t _incr = _n < _w ? _w : _n; \ if (_incr >= maxsize - i) \ return 0; \ if (p) \ { \ - if (digits == 0 && _n < _w) \ + if (_n < _w) \ { \ - size_t _delta = width - _n; \ + size_t _delta = _w - _n; \ if (pad == L_('0') || pad == L_('+')) \ memset_zero (p, _delta); \ else \ @@ -186,15 +187,17 @@ extern char *tzname[]; i += _incr; \ } while (0) +#define add1(c) width_add1 (width, c) #if FPRINTFTIME -# define add1(C) add (1, fputc (C, p)) +# define width_add1(width, c) width_add (width, 1, fputc (c, p)) #else -# define add1(C) add (1, *p = C) +# define width_add1(width, c) width_add (width, 1, *p = c) #endif +#define cpy(n, s) width_cpy (width, n, s) #if FPRINTFTIME -# define cpy(n, s) \ - add ((n), \ +# define width_cpy(width, n, s) \ + width_add (width, n, \ do \ { \ if (to_lowcase) \ @@ -214,8 +217,8 @@ extern char *tzname[]; while (0) \ ) #else -# define cpy(n, s) \ - add ((n), \ +# define width_cpy(width, n, s) \ + width_add (width, n, \ if (to_lowcase) \ memcpy_lowcase (p, (s), _n LOCALE_ARG); \ else if (to_uppcase) \ @@ -435,9 +438,10 @@ my_strftime (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) libc_hidden_def (my_strftime) #endif -/* Just like my_strftime, above, but with two more parameters. - UPCASE indicate that the result should be converted to upper case, - and *TZSET_CALLED indicates whether tzset has been called here. */ +/* Just like my_strftime, above, but with more parameters. + UPCASE indicates that the result should be converted to upper case. + YR_SPEC and WIDTH specify the padding and width for the year. + *TZSET_CALLED indicates whether tzset has been called here. */ static size_t __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) const CHAR_T *format, @@ -556,7 +560,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) for (f = format; *f != '\0'; width = -1, f++) { - int pad = 0; /* Padding for number ('-', '_', or 0). */ + int pad = 0; /* Padding for number ('_', '-', '+', '0', or 0). */ int modifier; /* Field modifier ('E', 'O', or 0). */ int digits = 0; /* Max digits for numeric format. */ int number_value; /* Numeric value to be printed. */ @@ -565,7 +569,6 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) 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 + 2 /* for the two colons in a %::z or %:::z time zone */ @@ -1035,59 +1038,34 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) while (u_number_value != 0 || tz_colon_mask != 0); do_number_sign_and_padding: - if (digits < width) - digits = width; - - sign_char = (negative_number ? L_('-') - : always_output_a_sign ? L_('+') - : 0); - - if (pad == L_('-')) - { - if (sign_char) - add1 (sign_char); - } - else - { - int padding = digits - (buf + (sizeof (buf) / sizeof (buf[0])) - - bufp) - !!sign_char; - - if (padding > 0) - { - if (pad == L_('_')) - { - if ((size_t) padding >= maxsize - i) - return 0; - - if (p) - 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 (sign_char) - add1 (sign_char); + if (pad == 0) + pad = L_('0'); + if (width < 0) + width = digits; - if (p) - memset_zero (p, padding); - i += padding; - width = 0; - } - } - else - { - if (sign_char) - add1 (sign_char); - } - } + { + CHAR_T sign_char = (negative_number ? L_('-') + : always_output_a_sign ? L_('+') + : 0); + int numlen = buf + sizeof buf / sizeof buf[0] - bufp; + int shortage = width - !!sign_char - numlen; + int padding = pad == L_('-') || shortage <= 0 ? 0 : shortage; + + if (sign_char) + { + if (pad == L_('_')) + { + if (p) + memset_space (p, padding); + i += padding; + width -= padding; + } + width_add1 (0, sign_char); + width--; + } - cpy (buf + sizeof (buf) / sizeof (buf[0]) - bufp, bufp); + cpy (numlen, bufp); + } break; case L_('F'): @@ -1153,19 +1131,21 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) case L_('N'): /* GNU extension. */ if (modifier == L_('E')) goto bad_format; - - number_value = ns; - if (width == -1) - width = 9; - else - { - /* Take an explicit width less than 9 as a precision. */ - int j; - for (j = width; j < 9; j++) - number_value /= 10; - } - - DO_NUMBER (width, number_value); + { + int n = ns, ns_digits = 9; + if (width <= 0) + width = ns_digits; + int ndigs = ns_digits; + while (width < ndigs || (1 < ndigs && n % 10 == 0)) + ndigs--, n /= 10; + for (int i = ndigs; 0 < i; i--) + buf[i - 1] = n % 10 + L_('0'), n /= 10; + if (!pad) + pad = L_('0'); + width_cpy (0, ndigs, buf); + width_add (width - ndigs, 0, (void) 0); + } + break; #endif case L_('n'): diff --git a/tests/test-nstrftime.c b/tests/test-nstrftime.c index 33d92487c..7a7f22a76 100644 --- a/tests/test-nstrftime.c +++ b/tests/test-nstrftime.c @@ -44,6 +44,7 @@ static struct posixtm_test const T[] = { { 1300000000, 0, "%F", "2011-03-13" }, { 0, 10, "%T.%N", "00:00:00.000000010" }, + { 56, 123456789, "%T.%12N", "00:00:56.123456789000" }, { 0, 0, NULL, NULL } }; -- 2.23.0