From: Bruno Haible Date: Thu, 8 Feb 2024 14:34:34 +0000 (+0100) Subject: nstrtime: Fix %p, %P, %r directives' results on NetBSD, Solaris. X-Git-Tag: v1.0~448 X-Git-Url: https://gitweb.git.savannah.gnu.org/gitweb/?a=commitdiff_plain;h=fbafdff930180540fef0ad1e4ee2e8ac26bdf715;p=gnulib.git nstrtime: Fix %p, %P, %r directives' results on NetBSD, Solaris. * lib/strftime.c: Include , localename.h. (should_remove_ampm): New function. (__strftime_internal): On NetBSD and Solaris, remove the AM/PM indicator from the %p, %r directives' results in specific locales. * modules/nstrftime (Depends-on): Add localename. * tests/test-nstrftime.h (locales_test): Update the expected result of the %r directive. * doc/posix-functions/strftime.texi: Mention the problem of the %p and %r directives on NetBSD and Solaris. --- diff --git a/ChangeLog b/ChangeLog index 1c44d8dc48..a721e27609 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2024-02-08 Bruno Haible + + nstrtime: Fix %p, %P, %r directives' results on NetBSD, Solaris. + * lib/strftime.c: Include , localename.h. + (should_remove_ampm): New function. + (__strftime_internal): On NetBSD and Solaris, remove the AM/PM indicator + from the %p, %r directives' results in specific locales. + * modules/nstrftime (Depends-on): Add localename. + * tests/test-nstrftime.h (locales_test): Update the expected result of + the %r directive. + * doc/posix-functions/strftime.texi: Mention the problem of the %p and + %r directives on NetBSD and Solaris. + 2024-02-08 Bruno Haible nstrtime, c-nstrftime: Fix %c directive's result on NetBSD, Solaris. diff --git a/doc/posix-functions/strftime.texi b/doc/posix-functions/strftime.texi index b62ea37fdb..1edd08e7f0 100644 --- a/doc/posix-functions/strftime.texi +++ b/doc/posix-functions/strftime.texi @@ -25,6 +25,14 @@ The %r specifier produces empty output, at least in a French locale, on some platforms: macOS 12.5, FreeBSD 14.0. @item +The %r specifier includes an AM/PM indicator, at least in a French locale, +on some platforms: +NetBSD 9.3, Solaris 11.4. +@item +The %p specifier produces non-empty output, at least in a French locale, +on some platforms: +NetBSD 9.3, Solaris 11.4. +@item The Windows C runtime library (which is used by MinGW) does not support the %e specifier (and possibly the other more recent SUS specifiers too, i.e., %C, %D, %h, %n, %r, %R, %t, and %T). diff --git a/lib/strftime.c b/lib/strftime.c index b70494cf78..b6c0b11e00 100644 --- a/lib/strftime.c +++ b/lib/strftime.c @@ -83,6 +83,11 @@ extern char *tzname[]; # include #endif +#if (defined __NetBSD__ || defined __sun) && !USE_C_LOCALE +# include +# include "localename.h" +#endif + #include "attribute.h" #include @@ -370,6 +375,356 @@ c_locale (void) #endif +#if (defined __NetBSD__ || defined __sun) && !USE_C_LOCALE + +/* Return true if an AM/PM indicator should be removed. */ +static bool +should_remove_ampm (void) +{ + /* According to glibc's 'am_pm' attribute in the locale database, an AM/PM + indicator should be absent in the locales for the following languages: + ab an ast az be ber bg br bs ce cs csb cv da de dsb eo et eu fa fi fo fr + fur fy ga gl gv hr hsb ht hu hy it ka kk kl ku kv kw ky lb lg li lij ln + lt lv mg mhr mi mk mn ms mt nb nds nhn nl nn nr nso oc os pap pl pt ro + ru rw sah sc se sgs sk sl sm sr ss st su sv szl tg tk tn ts tt ug uk unm + uz ve wae wo xh zu */ + const char *loc = gl_locale_name (LC_TIME, "LC_TIME"); + bool remove_ampm = false; + switch (loc[0]) + { + case 'a': + switch (loc[1]) + { + case 'b': case 'n': case 'z': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + case 's': + if (loc[2] == 't' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + default: + break; + } + break; + case 'b': + switch (loc[1]) + { + case 'e': + if (loc[2] == '\0' || loc[2] == '_' + || (loc[2] == 'r' && (loc[3] == '\0' || loc[3] == '_'))) + remove_ampm = true; + break; + case 'g': case 'r': case 's': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + default: + break; + } + break; + case 'c': + switch (loc[1]) + { + case 'e': case 'v': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + case 's': + if (loc[2] == '\0' || loc[2] == '_' + || (loc[2] == 'b' && (loc[3] == '\0' || loc[3] == '_'))) + remove_ampm = true; + break; + default: + break; + } + break; + case 'd': + switch (loc[1]) + { + case 'a': case 'e': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + case 's': + if (loc[2] == 'b' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + default: + break; + } + break; + case 'e': + switch (loc[1]) + { + case 'o': case 't': case 'u': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + default: + break; + } + break; + case 'f': + switch (loc[1]) + { + case 'a': case 'i': case 'o': case 'r': case 'y': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + case 'u': + if (loc[2] == 'r' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + default: + break; + } + break; + case 'g': + switch (loc[1]) + { + case 'a': case 'l': case 'v': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + default: + break; + } + break; + case 'h': + switch (loc[1]) + { + case 'r': case 't': case 'u': case 'y': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + case 's': + if (loc[2] == 'b' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + default: + break; + } + break; + case 'i': + switch (loc[1]) + { + case 't': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + default: + break; + } + break; + case 'k': + switch (loc[1]) + { + case 'a': case 'k': case 'l': case 'u': case 'v': case 'w': case 'y': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + default: + break; + } + break; + case 'l': + switch (loc[1]) + { + case 'b': case 'g': case 'n': case 't': case 'v': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + case 'i': + if (loc[2] == 'j' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + default: + break; + } + break; + case 'm': + switch (loc[1]) + { + case 'g': case 'i': case 'k': case 'n': case 's': case 't': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + case 'h': + if (loc[2] == 'r' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + default: + break; + } + break; + case 'n': + switch (loc[1]) + { + case 'b': case 'l': case 'n': case 'r': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + case 'd': + if (loc[2] == 's' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + case 'h': + if (loc[2] == 'n' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + case 's': + if (loc[2] == 'o' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + default: + break; + } + break; + case 'o': + switch (loc[1]) + { + case 'c': case 's': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + default: + break; + } + break; + case 'p': + switch (loc[1]) + { + case 'l': case 't': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + case 'a': + if (loc[2] == 'p' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + default: + break; + } + break; + case 'r': + switch (loc[1]) + { + case 'o': case 'u': case 'w': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + default: + break; + } + break; + case 's': + switch (loc[1]) + { + case 'c': case 'e': case 'k': case 'l': case 'm': case 'r': case 's': + case 't': case 'u': case 'v': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + case 'a': + if (loc[2] == 'h' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + case 'g': + if (loc[2] == 's' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + case 'z': + if (loc[2] == 'l' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + default: + break; + } + break; + case 't': + switch (loc[1]) + { + case 'g': case 'k': case 'n': case 's': case 't': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + default: + break; + } + break; + case 'u': + switch (loc[1]) + { + case 'g': case 'k': case 'z': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + case 'n': + if (loc[2] == 'm'&& (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + default: + break; + } + break; + case 'v': + switch (loc[1]) + { + case 'e': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + default: + break; + } + break; + case 'w': + switch (loc[1]) + { + case 'a': + if (loc[2] == 'e' && (loc[3] == '\0' || loc[3] == '_')) + remove_ampm = true; + break; + case 'o': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + default: + break; + } + break; + case 'x': + switch (loc[1]) + { + case 'h': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + default: + break; + } + break; + case 'z': + switch (loc[1]) + { + case 'u': + if (loc[2] == '\0' || loc[2] == '_') + remove_ampm = true; + break; + default: + break; + } + break; + default: + break; + } + return remove_ampm; +} + +#endif + + #if ! HAVE_TM_GMTOFF /* Yield the difference between *A and *B, measured in seconds, ignoring leap seconds. */ @@ -1006,6 +1361,36 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) } } } +# if !USE_C_LOCALE + /* The output of the strftime %p and %r directives contains + an AM/PM indicator even for locales where it is not + suitable, such as French. Remove this indicator. */ + else if (format_char == L_('p')) + { + bool found_ampm = (len > 1); + if (found_ampm && should_remove_ampm ()) + { + ubuf[1] = '\0'; + len = 1; + } + } + else if (format_char == L_('r')) + { + char last_char = ubuf[len - 1]; + bool found_ampm = !(last_char >= '0' && last_char <= '9'); + if (found_ampm && should_remove_ampm ()) + { + char *space; + for (space = ubuf + len - 1; *space != ' '; space--) + ; + if (space > ubuf) + { + *space = '\0'; + len = space - ubuf; + } + } + } +# endif # endif cpy (len - 1, ubuf + 1); } diff --git a/modules/nstrftime b/modules/nstrftime index 57d3bbbf5d..77a8594c4c 100644 --- a/modules/nstrftime +++ b/modules/nstrftime @@ -15,6 +15,7 @@ errno extensions intprops libc-config +localename stdbool stdckdint time_rz diff --git a/tests/test-nstrftime.h b/tests/test-nstrftime.h index 5c54266043..b8776bacdb 100644 --- a/tests/test-nstrftime.h +++ b/tests/test-nstrftime.h @@ -524,9 +524,8 @@ locales_test (language_t language) break; case french: ASSERT (STREQ (buf, "06:40:03 ") /* glibc */ - || STREQ (buf, "06:40:03") /* Cygwin */ - || STREQ (buf, "06:40:03 AM") /* NetBSD */ - || STREQ (buf, " 6:40:03 AM") /* Solaris */); + || STREQ (buf, "06:40:03") /* NetBSD, Cygwin */ + || STREQ (buf, " 6:40:03") /* Solaris */); break; }