* nstrtime: Fix %p, %P, %r directives' results on NetBSD, Solaris
@ 2024-02-08 14:35 Bruno Haible
0 siblings, 0 replies; only message in thread
From: Bruno Haible @ 2024-02-08 14:35 UTC (permalink / raw)
To: bug-gnulib
On NetBSD 9.3 and Solaris 11.4, there are two more problems, e.g. in the
French fr_FR locale:
- The unit test test-nstrftime-2.sh fails at line 513,
because the %p directive produced "AM" or "PM" (not adequate in France).
- The %r directive produces a string ending with " AM" or " PM" (equally
not adequate in France).
This patch fixes and documents the issues.
2024-02-08 Bruno Haible <bruno@clisp.org>
nstrtime: Fix %p, %P, %r directives' results on NetBSD, Solaris.
* lib/strftime.c: Include <locale.h>, 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/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 @@
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 <locale.h>
#endif
+#if (defined __NetBSD__ || defined __sun) && !USE_C_LOCALE
+# include <locale.h>
+# include "localename.h"
+#endif
+
#include "attribute.h"
#include <intprops.h>
@@ -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;
}
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2024-02-08 14:36 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-02-08 14:35 nstrtime: Fix %p, %P, %r directives' results on NetBSD, Solaris Bruno Haible
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).