bug-gnulib@gnu.org mirror (unofficial)
 help / color / mirror / Atom feed
* 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).