Thanks, I tried that out on Fedora 20 (pretending that its nl_langinfo didn't exist) and had some trouble building and testing it, indicating trouble for other POSIXish platforms. I fixed the problems I found. A few things:

* nl_langinfo (FRAC_DIGITS) returns a pointer to a character, not a string, and similarly for the other values representing tiny integers. There's no need for null-termination.

* struct tm might not have the traditional Unix v7 layout.

* Some code can be shared between rpl_nl_langinfo and nl_langinfo by hoisting it into a shared static function ctype_codeset.

* Often it's better to use char[][] constants instead of char *[] constants when strings don't vary in size much, as this avoids unnecessary relocations in shared libraries.

* I didn't follow why tmm had some members updated but not others. Does Microsoft strftime care about otherwise-unused struct tm members? If so, perhaps we should initialize all the members to a valid and consistent value; if not, let's not bother with anything but the minimal.

Revised proposed patch attached.
From 4630fdeb658f96fce13ed2d4d9d89e9955aaa273 Mon Sep 17 00:00:00 2001
From: Eli Zaretskii <e...@gnu.org>
Date: Sat, 5 Jul 2014 14:42:47 -0700
Subject: [PATCH] nl_langinfo: CODESET on MS-Windows and more items from
 localeconv

* lib/langinfo.in.h (DECIMAL_POINT, THOUSANDS_SEP, GROUPING)
(CURRENCY_SYMBOL, INT_CURR_SYMBOL, MON_DECIMAL_POINT)
(MON_THOUSANDS_SEP, MON_GROUPING, POSITIVE_SIGN, NEGATIVE_SIGN)
(FRAC_DIGITS, INT_FRAC_DIGITS, P_CS_PRECEDES, N_CS_PRECEDES)
(P_SEP_BY_SPACE, N_SEP_BY_SPACE, P_SIGN_POSN, N_SIGN_POSN): Define.
* lib/nl_langinfo.c: Include <locale.h> and <string.h> early.
Include <time.h> if !REPLACE_NL_LANGINFO.
(ctype_codeset): New function, taken from rpl_nl_langinfo.
(rpl_nl_langinfo): Use it.
(nl_langinfo) [!REPLACE_NL_LANGINFO]: Likewise.
Compute the values of RADIXCHAR, THOUSEP, GROUPING, CRNCYSTR,
INT_CURR_SYMBOL, MON_DECIMAL_POINT, MON_THOUSANDS_SEP,
MON_GROUPING, POSITIVE_SIGN, NEGATIVE_SIGN, FRAC_DIGITS,
INT_FRAC_DIGITS, P_CS_PRECEDES, N_CS_PRECEDES, P_SEP_BY_SPACE,
N_SEP_BY_SPACE, P_SIGN_POSN, and N_SIGN_POSN from the
corresponding values returned by 'localeconv'.  Compute the values
of AM_STR, PM_STR, DAY_n, ABDAY_n, MON_n, and ABMON_n by calling
'strftime' with a suitable struct tm value.
---
 ChangeLog         |  23 ++++++
 lib/langinfo.in.h |  18 +++++
 lib/nl_langinfo.c | 215 +++++++++++++++++++++++++++++++++---------------------
 3 files changed, 172 insertions(+), 84 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index eccf03e..0e800a1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,26 @@
+2014-07-05  Eli Zaretskii  <e...@gnu.org>
+           Paul Eggert  <egg...@cs.ucla.edu>
+
+       nl_langinfo: CODESET on MS-Windows and more items from localeconv
+       * lib/langinfo.in.h (DECIMAL_POINT, THOUSANDS_SEP, GROUPING)
+       (CURRENCY_SYMBOL, INT_CURR_SYMBOL, MON_DECIMAL_POINT)
+       (MON_THOUSANDS_SEP, MON_GROUPING, POSITIVE_SIGN, NEGATIVE_SIGN)
+       (FRAC_DIGITS, INT_FRAC_DIGITS, P_CS_PRECEDES, N_CS_PRECEDES)
+       (P_SEP_BY_SPACE, N_SEP_BY_SPACE, P_SIGN_POSN, N_SIGN_POSN): Define.
+       * lib/nl_langinfo.c: Include <locale.h> and <string.h> early.
+       Include <time.h> if !REPLACE_NL_LANGINFO.
+       (ctype_codeset): New function, taken from rpl_nl_langinfo.
+       (rpl_nl_langinfo): Use it.
+       (nl_langinfo) [!REPLACE_NL_LANGINFO]: Likewise.
+       Compute the values of RADIXCHAR, THOUSEP, GROUPING, CRNCYSTR,
+       INT_CURR_SYMBOL, MON_DECIMAL_POINT, MON_THOUSANDS_SEP,
+       MON_GROUPING, POSITIVE_SIGN, NEGATIVE_SIGN, FRAC_DIGITS,
+       INT_FRAC_DIGITS, P_CS_PRECEDES, N_CS_PRECEDES, P_SEP_BY_SPACE,
+       N_SEP_BY_SPACE, P_SIGN_POSN, and N_SIGN_POSN from the
+       corresponding values returned by 'localeconv'.  Compute the values
+       of AM_STR, PM_STR, DAY_n, ABDAY_n, MON_n, and ABMON_n by calling
+       'strftime' with a suitable struct tm value.
+
 2014-07-05  Paul Eggert  <egg...@cs.ucla.edu>
 
        Bruno Haible has stepped down as maintainer.
diff --git a/lib/langinfo.in.h b/lib/langinfo.in.h
index 17f4b9b..d118d5d 100644
--- a/lib/langinfo.in.h
+++ b/lib/langinfo.in.h
@@ -49,7 +49,10 @@ typedef int nl_item;
 # define CODESET     10000
 /* nl_langinfo items of the LC_NUMERIC category */
 # define RADIXCHAR   10001
+# define DECIMAL_POINT RADIXCHAR
 # define THOUSEP     10002
+# define THOUSANDS_SEP THOUSEP
+# define GROUPING    10114
 /* nl_langinfo items of the LC_TIME category */
 # define D_T_FMT     10003
 # define D_FMT       10004
@@ -102,6 +105,21 @@ typedef int nl_item;
 # define ALT_DIGITS  10051
 /* nl_langinfo items of the LC_MONETARY category */
 # define CRNCYSTR    10052
+# define CURRENCY_SYMBOL   CRNCYSTR
+# define INT_CURR_SYMBOL   10100
+# define MON_DECIMAL_POINT 10101
+# define MON_THOUSANDS_SEP 10102
+# define MON_GROUPING      10103
+# define POSITIVE_SIGN     10104
+# define NEGATIVE_SIGN     10105
+# define FRAC_DIGITS       10106
+# define INT_FRAC_DIGITS   10107
+# define P_CS_PRECEDES     10108
+# define N_CS_PRECEDES     10109
+# define P_SEP_BY_SPACE    10110
+# define N_SEP_BY_SPACE    10111
+# define P_SIGN_POSN       10112
+# define N_SIGN_POSN       10113
 /* nl_langinfo items of the LC_MESSAGES category */
 # define YESEXPR     10053
 # define NOEXPR      10054
diff --git a/lib/nl_langinfo.c b/lib/nl_langinfo.c
index 287abfd..e461186 100644
--- a/lib/nl_langinfo.c
+++ b/lib/nl_langinfo.c
@@ -20,13 +20,47 @@
 /* Specification.  */
 #include <langinfo.h>
 
+#include <locale.h>
+#include <string.h>
+
+/* Return the codeset of the current locale, if this is easily deducible.
+   Otherwise, return "".  */
+static char *
+ctype_codeset (void)
+{
+  static char buf[2 + 10 + 1];
+  size_t buflen = 0;
+  char const *locale = setlocale (LC_CTYPE, NULL);
+
+  if (locale && locale[0])
+    {
+      /* If the locale name contains an encoding after the dot, return it.  */
+      char *dot = strchr (locale, '.');
+
+      if (dot)
+        {
+          /* Look for the possible @... trailer and remove it, if any.  */
+          char const *modifier = strchr (++dot, '@');
+
+          if (! modifier)
+            return dot;
+          if (modifier - dot < sizeof buf)
+            {
+              buflen = modifier - dot;
+              memcpy (buf, dot, buflen);
+            }
+        }
+    }
+
+  buf[buflen] = '\0';
+  return buf;
+}
+
+
 #if REPLACE_NL_LANGINFO
 
 /* Override nl_langinfo with support for added nl_item values.  */
 
-# include <locale.h>
-# include <string.h>
-
 # undef nl_langinfo
 
 char *
@@ -36,36 +70,7 @@ rpl_nl_langinfo (nl_item item)
     {
 # if GNULIB_defined_CODESET
     case CODESET:
-      {
-        const char *locale;
-        static char buf[2 + 10 + 1];
-
-        locale = setlocale (LC_CTYPE, NULL);
-        if (locale != NULL && locale[0] != '\0')
-          {
-            /* If the locale name contains an encoding after the dot, return
-               it.  */
-            const char *dot = strchr (locale, '.');
-
-            if (dot != NULL)
-              {
-                const char *modifier;
-
-                dot++;
-                /* Look for the possible @... trailer and remove it, if any.  
*/
-                modifier = strchr (dot, '@');
-                if (modifier == NULL)
-                  return dot;
-                if (modifier - dot < sizeof (buf))
-                  {
-                    memcpy (buf, dot, modifier - dot);
-                    buf [modifier - dot] = '\0';
-                    return buf;
-                  }
-              }
-          }
-        return "";
-      }
+      return ctype_codeset ();
 # endif
 # if GNULIB_defined_T_FMT_AMPM
     case T_FMT_AMPM:
@@ -128,24 +133,31 @@ rpl_nl_langinfo (nl_item item)
 
 # endif
 
-# include <locale.h>
+# include <time.h>
 
 char *
 nl_langinfo (nl_item item)
 {
+  static char nlbuf[100];
+  struct tm tmm = { 0 };
+
   switch (item)
     {
     /* nl_langinfo items of the LC_CTYPE category */
     case CODESET:
-# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
       {
-        static char buf[2 + 10 + 1];
-
-        /* The Windows API has a function returning the locale's codepage as
-           a number.  */
-        sprintf (buf, "CP%u", GetACP ());
-        return buf;
+        char *codeset = ctype_codeset ();
+        if (*codeset)
+          return codeset;
       }
+# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+      /* The Windows API has a function returning the locale's
+         codepage as a number, but the value doesn't change according
+         to what the 'setlocale' call specified.  So use it as a
+         last resort, in case the string returned by 'setlocale'
+         doesn't specify the codepage.  */
+      sprintf (nlbuf, "CP%u", GetACP ());
+      return nlbuf;
 # elif defined __BEOS__
       return "UTF-8";
 # else
@@ -156,6 +168,8 @@ nl_langinfo (nl_item item)
       return localeconv () ->decimal_point;
     case THOUSEP:
       return localeconv () ->thousands_sep;
+    case GROUPING:
+      return localeconv () ->grouping;
     /* nl_langinfo items of the LC_TIME category.
        TODO: Really use the locale.  */
     case D_T_FMT:
@@ -170,93 +184,126 @@ nl_langinfo (nl_item item)
     case T_FMT_AMPM:
       return "%I:%M:%S %p";
     case AM_STR:
-      return "AM";
+      if (!strftime (nlbuf, sizeof nlbuf, "%p", &tmm))
+        return "AM";
+      return nlbuf;
     case PM_STR:
-      return "PM";
+      tmm.tm_hour = 12;
+      if (!strftime (nlbuf, sizeof nlbuf, "%p", &tmm))
+        return "PM";
+      return nlbuf;
     case DAY_1:
-      return "Sunday";
     case DAY_2:
-      return "Monday";
     case DAY_3:
-      return "Tuesday";
     case DAY_4:
-      return "Wednesday";
     case DAY_5:
-      return "Thursday";
     case DAY_6:
-      return "Friday";
     case DAY_7:
-      return "Saturday";
+      {
+        static char const days[][sizeof "Wednesday"] = {
+          "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
+          "Friday", "Saturday"
+        };
+        tmm.tm_wday = item - DAY_1;
+        if (!strftime (nlbuf, sizeof nlbuf, "%A", &tmm))
+          return (char *) days[item - DAY_1];
+        return nlbuf;
+      }
     case ABDAY_1:
-      return "Sun";
     case ABDAY_2:
-      return "Mon";
     case ABDAY_3:
-      return "Tue";
     case ABDAY_4:
-      return "Wed";
     case ABDAY_5:
-      return "Thu";
     case ABDAY_6:
-      return "Fri";
     case ABDAY_7:
-      return "Sat";
+      {
+        static char const abdays[][sizeof "Sun"] = {
+          "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+        };
+        tmm.tm_wday = item - ABDAY_1;
+        if (!strftime (nlbuf, sizeof nlbuf, "%a", &tmm))
+          return (char *) abdays[item - ABDAY_1];
+        return nlbuf;
+      }
     case MON_1:
-      return "January";
     case MON_2:
-      return "February";
     case MON_3:
-      return "March";
     case MON_4:
-      return "April";
     case MON_5:
-      return "May";
     case MON_6:
-      return "June";
     case MON_7:
-      return "July";
     case MON_8:
-      return "August";
     case MON_9:
-      return "September";
     case MON_10:
-      return "October";
     case MON_11:
-      return "November";
     case MON_12:
-      return "December";
+      {
+        static char const months[][sizeof "September"] = {
+          "January", "February", "March", "April", "May", "June", "July",
+          "September", "October", "November", "December"
+        };
+        tmm.tm_mon = item - MON_1;
+        if (!strftime (nlbuf, sizeof nlbuf, "%B", &tmm))
+          return (char *) months[item - MON_1];
+        return nlbuf;
+      }
     case ABMON_1:
-      return "Jan";
     case ABMON_2:
-      return "Feb";
     case ABMON_3:
-      return "Mar";
     case ABMON_4:
-      return "Apr";
     case ABMON_5:
-      return "May";
     case ABMON_6:
-      return "Jun";
     case ABMON_7:
-      return "Jul";
     case ABMON_8:
-      return "Aug";
     case ABMON_9:
-      return "Sep";
     case ABMON_10:
-      return "Oct";
     case ABMON_11:
-      return "Nov";
     case ABMON_12:
-      return "Dec";
+      {
+        static char const abmonths[][sizeof "Jan"] = {
+          "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
+          "Sep", "Oct", "Nov", "Dec"
+        };
+        tmm.tm_mon = item - ABMON_1;
+        if (!strftime (nlbuf, sizeof nlbuf, "%b", &tmm))
+          return (char *) abmonths[item - ABMON_1];
+        return nlbuf;
+      }
     case ERA:
       return "";
     case ALT_DIGITS:
       return "\0\0\0\0\0\0\0\0\0\0";
-    /* nl_langinfo items of the LC_MONETARY category
-       TODO: Really use the locale. */
+    /* nl_langinfo items of the LC_MONETARY category.  */
     case CRNCYSTR:
-      return "-";
+      return localeconv () ->currency_symbol;
+    case INT_CURR_SYMBOL:
+      return localeconv () ->int_curr_symbol;
+    case MON_DECIMAL_POINT:
+      return localeconv () ->mon_decimal_point;
+    case MON_THOUSANDS_SEP:
+      return localeconv () ->mon_thousands_sep;
+    case MON_GROUPING:
+      return localeconv () ->mon_grouping;
+    case POSITIVE_SIGN:
+      return localeconv () ->positive_sign;
+    case NEGATIVE_SIGN:
+      return localeconv () ->negative_sign;
+    case FRAC_DIGITS:
+      return & localeconv () ->frac_digits;
+    case INT_FRAC_DIGITS:
+      return & localeconv () ->int_frac_digits;
+    case P_CS_PRECEDES:
+      return & localeconv () ->p_cs_precedes;
+    case N_CS_PRECEDES:
+      return & localeconv () ->n_cs_precedes;
+    case P_SEP_BY_SPACE:
+      return & localeconv () ->p_sep_by_space;
+    case N_SEP_BY_SPACE:
+      return & localeconv () ->n_sep_by_space;
+    case P_SIGN_POSN:
+      return & localeconv () ->p_sign_posn;
+    case N_SIGN_POSN:
+      return & localeconv () ->n_sign_posn;
     /* nl_langinfo items of the LC_MESSAGES category
        TODO: Really use the locale. */
     case YESEXPR:
-- 
1.9.3

Reply via email to