]> Savannah Git Hosting - gnulib.git/commitdiff
c-nstrftime: New module.
authorBruno Haible <bruno@clisp.org>
Thu, 8 Feb 2024 00:09:08 +0000 (01:09 +0100)
committerBruno Haible <bruno@clisp.org>
Thu, 8 Feb 2024 00:11:48 +0000 (01:11 +0100)
Suggested by Paul Eggert.

* lib/strftime.h (c_nstrftime): New declaration.
* lib/c-nstrftime.c: New file.
* lib/strftime.c (FPRINTFTIME): Move fallback definition before its
first use.
(USE_C_LOCALE): Add fallback definition.
If USE_C_LOCALE is set, include c-ctype.h instead of <ctype.h>.
Conditionally include <locale.h>.
(TOUPPER, TOLOWER): Define differently if USE_C_LOCALE is set.
(c_locale_cache, c_locale): New definitions, copied from lib/c-strtod.c.
(c_weekday_names, c_month_names): New variables, based on
glibc/time/strftime_l.c.
(__strftime_internal): Define a_wkday, aw_len, f_wkday, a_month, am_len,
f_month, ampm, ap_len differently for USE_C_LOCALE. Avoid a
'goto underlying_strftime' if USE_C_LOCALE && !HAVE_STRFTIME_L. Make the
strftime fallback declaration ISO C 23 compliant. If USE_C_LOCALE, use
strftime_l() with the C locale object instead of strftime().
* m4/c-nstrftime.m4: New file, based on m4/nstrftime.m4.
* modules/c-nstrftime: New file, based on modules/nstrftime.

ChangeLog
lib/c-nstrftime.c [new file with mode: 0644]
lib/strftime.c
lib/strftime.h
m4/c-nstrftime.m4 [new file with mode: 0644]
modules/c-nstrftime [new file with mode: 0644]

index ecbe0ea7b235ed159c4fa2f9ad30411eb2d2582c..0d9b82b1e9463c121a656f04bf92b1d68b2a6a9b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,26 @@
+2024-02-07  Bruno Haible  <bruno@clisp.org>
+
+       c-nstrftime: New module.
+       Suggested by Paul Eggert.
+       * lib/strftime.h (c_nstrftime): New declaration.
+       * lib/c-nstrftime.c: New file.
+       * lib/strftime.c (FPRINTFTIME): Move fallback definition before its
+       first use.
+       (USE_C_LOCALE): Add fallback definition.
+       If USE_C_LOCALE is set, include c-ctype.h instead of <ctype.h>.
+       Conditionally include <locale.h>.
+       (TOUPPER, TOLOWER): Define differently if USE_C_LOCALE is set.
+       (c_locale_cache, c_locale): New definitions, copied from lib/c-strtod.c.
+       (c_weekday_names, c_month_names): New variables, based on
+       glibc/time/strftime_l.c.
+       (__strftime_internal): Define a_wkday, aw_len, f_wkday, a_month, am_len,
+       f_month, ampm, ap_len differently for USE_C_LOCALE. Avoid a
+       'goto underlying_strftime' if USE_C_LOCALE && !HAVE_STRFTIME_L. If
+       USE_C_LOCALE, use strftime_l() with the C locale object instead of
+       strftime().
+       * m4/c-nstrftime.m4: New file, based on m4/nstrftime.m4.
+       * modules/c-nstrftime: New file, based on modules/nstrftime.
+
 2024-02-07  Bruno Haible  <bruno@clisp.org>
 
        nstrftime: Add tests of all directives, also in non-trivial locales.
diff --git a/lib/c-nstrftime.c b/lib/c-nstrftime.c
new file mode 100644 (file)
index 0000000..cee7437
--- /dev/null
@@ -0,0 +1,20 @@
+/* Generate time strings in the "C" locale.
+
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is free software: you can redistribute it and/or modify
+   it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation, either version 3 of the
+   License, or (at your option) any later version.
+
+   This file is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#define USE_C_LOCALE 1
+#define my_strftime c_nstrftime
+#include "strftime.c"
index d81c7eddfaf6d2ff5bd28417cd5429d489100a5d..f6714524bdcd5d29b0463add88a718a48e291c69 100644 (file)
    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
 
+#ifndef FPRINTFTIME
+# define FPRINTFTIME 0
+#endif
+
+#ifndef USE_C_LOCALE
+# define USE_C_LOCALE 0
+#endif
+
 #ifdef _LIBC
 # define USE_IN_EXTENDED_LOCALE_MODEL 1
 # define HAVE_STRUCT_ERA_ENTRY 1
 # include "time-internal.h"
 #endif
 
-#include <ctype.h>
+#if USE_C_LOCALE
+# include "c-ctype.h"
+#else
+# include <ctype.h>
+#endif
 #include <errno.h>
 #include <time.h>
 
@@ -67,6 +79,10 @@ extern char *tzname[];
 #include <stdlib.h>
 #include <string.h>
 
+#if USE_C_LOCALE && HAVE_STRFTIME_L
+# include <locale.h>
+#endif
+
 #include "attribute.h"
 #include <intprops.h>
 
@@ -127,10 +143,6 @@ extern char *tzname[];
 # define mktime(tp) __mktime64 (tp)
 #endif
 
-#ifndef FPRINTFTIME
-# define FPRINTFTIME 0
-#endif
-
 #if FPRINTFTIME
 # define STREAM_OR_CHAR_T FILE
 # define STRFTIME_ARG(x) /* empty */
@@ -270,8 +282,13 @@ extern char *tzname[];
 #  define TOUPPER(Ch, L) __toupper_l (Ch, L)
 #  define TOLOWER(Ch, L) __tolower_l (Ch, L)
 # else
-#  define TOUPPER(Ch, L) toupper (Ch)
-#  define TOLOWER(Ch, L) tolower (Ch)
+#  if USE_C_LOCALE
+#   define TOUPPER(Ch, L) c_toupper (Ch)
+#   define TOLOWER(Ch, L) c_tolower (Ch)
+#  else
+#   define TOUPPER(Ch, L) toupper (Ch)
+#   define TOLOWER(Ch, L) tolower (Ch)
+#  endif
 # endif
 #endif
 /* We don't use 'isdigit' here since the locale dependent
@@ -333,6 +350,26 @@ memcpy_uppcase (CHAR_T *dest, const CHAR_T *src, size_t len LOCALE_PARAM)
 #endif
 
 
+#if USE_C_LOCALE && HAVE_STRFTIME_L
+
+/* Cache for the C locale object.
+   Marked volatile so that different threads see the same value
+   (avoids locking).  */
+static volatile locale_t c_locale_cache;
+
+/* Return the C locale object, or (locale_t) 0 with errno set
+   if it cannot be created.  */
+static locale_t
+c_locale (void)
+{
+  if (!c_locale_cache)
+    c_locale_cache = newlocale (LC_ALL_MASK, "C", (locale_t) 0);
+  return c_locale_cache;
+}
+
+#endif
+
+
 #if ! HAVE_TM_GMTOFF
 /* Yield the difference between *A and *B,
    measured in seconds, ignoring leap seconds.  */
@@ -381,6 +418,21 @@ iso_week_days (int yday, int wday)
 }
 
 
+#if !defined _NL_CURRENT && (USE_C_LOCALE && !HAVE_STRFTIME_L)
+static CHAR_T const c_weekday_names[][sizeof "Wednesday"] =
+  {
+    L_("Sunday"), L_("Monday"), L_("Tuesday"), L_("Wednesday"),
+    L_("Thursday"), L_("Friday"), L_("Saturday")
+  };
+static CHAR_T const c_month_names[][sizeof "September"] =
+  {
+    L_("January"), L_("February"), L_("March"), L_("April"), L_("May"),
+    L_("June"), L_("July"), L_("August"), L_("September"), L_("October"),
+    L_("November"), L_("December")
+  };
+#endif
+
+
 /* When compiling this file, GNU applications can #define my_strftime
    to a symbol (typically nstrftime) to get an extended strftime with
    extra arguments TZ and NS.  */
@@ -478,6 +530,24 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
 # define am_len STRLEN (a_month)
 # define aam_len STRLEN (a_altmonth)
 # define ap_len STRLEN (ampm)
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
+/* The English abbreviated weekday names are just the first 3 characters of the
+   English full weekday names.  */
+# define a_wkday \
+  (tp->tm_wday < 0 || tp->tm_wday > 6 ? L_("?") : c_weekday_names[tp->tm_wday])
+# define aw_len 3
+# define f_wkday \
+  (tp->tm_wday < 0 || tp->tm_wday > 6 ? L_("?") : c_weekday_names[tp->tm_wday])
+/* The English abbreviated month names are just the first 3 characters of the
+   English full month names.  */
+# define a_month \
+  (tp->tm_mon < 0 || tp->tm_mon > 11 ? L_("?") : c_month_names[tp->tm_mon])
+# define am_len 3
+# define f_month \
+  (tp->tm_mon < 0 || tp->tm_mon > 11 ? L_("?") : c_month_names[tp->tm_mon])
+/* The English AM/PM strings happen to have the same length, namely 2.  */
+# define ampm (L_("AMPM") + 2 * (tp->tm_hour > 11))
+# define ap_len 2
 #endif
 #if HAVE_TZNAME
   char **tzname_vec = tzname;
@@ -765,7 +835,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
               to_uppcase = true;
               to_lowcase = false;
             }
-#ifdef _NL_CURRENT
+#if defined _NL_CURRENT || (USE_C_LOCALE && !HAVE_STRFTIME_L)
           cpy (aw_len, a_wkday);
           break;
 #else
@@ -780,7 +850,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
               to_uppcase = true;
               to_lowcase = false;
             }
-#ifdef _NL_CURRENT
+#if defined _NL_CURRENT || (USE_C_LOCALE && !HAVE_STRFTIME_L)
           cpy (STRLEN (f_wkday), f_wkday);
           break;
 #else
@@ -802,6 +872,9 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
           else
             cpy (am_len, a_month);
           break;
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
+          cpy (am_len, a_month);
+          break;
 #else
           goto underlying_strftime;
 #endif
@@ -820,6 +893,9 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
           else
             cpy (STRLEN (f_month), f_month);
           break;
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
+          cpy (STRLEN (f_month), f_month);
+          break;
 #else
           goto underlying_strftime;
 #endif
@@ -834,6 +910,8 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
                                                      NLW(ERA_D_T_FMT)))
                      != '\0')))
             subfmt = (const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(D_T_FMT));
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
+          subfmt = L_("%a %b %e %H:%M:%S %Y");
 #else
           goto underlying_strftime;
 #endif
@@ -854,7 +932,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
           }
           break;
 
-#if !(defined _NL_CURRENT && HAVE_STRUCT_ERA_ENTRY)
+#if !((defined _NL_CURRENT && HAVE_STRUCT_ERA_ENTRY) || (USE_C_LOCALE && !HAVE_STRFTIME_L))
         underlying_strftime:
           {
             /* The relevant information is available only via the
@@ -879,7 +957,15 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
               *u++ = modifier;
             *u++ = format_char;
             *u = '\0';
+
+# if USE_C_LOCALE /* implies HAVE_STRFTIME_L */
+            locale_t locale = c_locale ();
+            if (!locale)
+              return 0; /* errno is set here */
+            len = strftime_l (ubuf, sizeof ubuf, ufmt, tp, locale);
+# else
             len = strftime (ubuf, sizeof ubuf, ufmt, tp);
+# endif
             if (len != 0)
               cpy (len - 1, ubuf + 1);
           }
@@ -902,6 +988,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
 # endif
                   break;
                 }
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
 #else
               goto underlying_strftime;
 #endif
@@ -925,6 +1012,9 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
                      != L_('\0'))))
             subfmt = (const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(D_FMT));
           goto subformat;
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
+          subfmt = L_("%m/%d/%y");
+          goto subformat;
 #else
           goto underlying_strftime;
 #endif
@@ -1000,6 +1090,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
                       break;
                     }
                 }
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
 #else
               goto underlying_strftime;
 #endif
@@ -1147,7 +1238,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
               to_uppcase = false;
               to_lowcase = true;
             }
-#ifdef _NL_CURRENT
+#if defined _NL_CURRENT || (USE_C_LOCALE && !HAVE_STRFTIME_L)
           cpy (ap_len, ampm);
           break;
 #else
@@ -1168,6 +1259,9 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
               == L_('\0'))
             subfmt = L_("%I:%M:%S %p");
           goto subformat;
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
+          subfmt = L_("%I:%M:%S %p");
+          goto subformat;
 #elif (defined __APPLE__ && defined __MACH__) || defined __FreeBSD__
           /* macOS, FreeBSD strftime() may produce empty output for "%r".  */
           subfmt = L_("%I:%M:%S %p");
@@ -1225,6 +1319,9 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
                      != L_('\0'))))
             subfmt = (const CHAR_T *) _NL_CURRENT (LC_TIME, NLW(T_FMT));
           goto subformat;
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
+          subfmt = L_("%H:%M:%S");
+          goto subformat;
 #else
           goto underlying_strftime;
 #endif
@@ -1332,6 +1429,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
                     pad = yr_spec;
                   goto subformat;
                 }
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
 #else
               goto underlying_strftime;
 #endif
@@ -1355,6 +1453,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
                   DO_NUMBER (2, (era->offset
                                  + delta * era->absolute_direction));
                 }
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
 #else
               goto underlying_strftime;
 #endif
index 6e3dbf834c2ec73baa5622bb6ac552eecdeb1a10..8ce62cdb6d7d69d4e03b1684b220b5c9e0923c34 100644 (file)
@@ -78,6 +78,12 @@ size_t nstrftime (char *restrict __s, size_t __maxsize,
                   char const *__format,
                   struct tm const *__tp, timezone_t __tz, int __ns);
 
+/* Like nstrftime, except that it uses the "C" locale instead of the
+   current locale.  */
+size_t c_nstrftime (char *restrict __s, size_t __maxsize,
+                    char const *__format,
+                    struct tm const *__tp, timezone_t __tz, int __ns);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/m4/c-nstrftime.m4 b/m4/c-nstrftime.m4
new file mode 100644 (file)
index 0000000..9c8268b
--- /dev/null
@@ -0,0 +1,21 @@
+# c-nstrftime.m4 serial 1
+dnl Copyright (C) 1996-2024 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_C_GNU_STRFTIME],
+[
+  AC_REQUIRE([AC_C_RESTRICT])
+
+  # This defines (or not) HAVE_TZNAME and HAVE_STRUCT_TM_TM_ZONE.
+  AC_REQUIRE([AC_STRUCT_TIMEZONE])
+
+  AC_REQUIRE([gl_TM_GMTOFF])
+
+  dnl Test for strftime_l. It exists in
+  dnl glibc >= 2.3, musl libc,
+  dnl macOS >= 10.4, FreeBSD >= 9.0, NetBSD >= 8.0, OpenBSD >= 6.2, Minix >= 3.3,
+  dnl AIX >= 7.2, Solaris >= 11.4, Cygwin >= 2.6, Android API level >= 21.
+  gl_CHECK_FUNCS_ANDROID([strftime_l], [[#include <time.h>]])
+])
diff --git a/modules/c-nstrftime b/modules/c-nstrftime
new file mode 100644 (file)
index 0000000..8b2348c
--- /dev/null
@@ -0,0 +1,37 @@
+Description:
+c_nstrftime() function: convert date and time to string, with GNU extensions, in "C" locale.
+
+Files:
+lib/strftime.h
+lib/c-nstrftime.c
+lib/strftime.c
+m4/tm_gmtoff.m4
+m4/c-nstrftime.m4
+
+Depends-on:
+attribute
+c-ctype
+c99
+errno
+extensions
+intprops
+libc-config
+locale
+stdbool
+stdckdint
+time_rz
+
+configure.ac:
+gl_C_GNU_STRFTIME
+
+Makefile.am:
+lib_SOURCES += c-nstrftime.c
+
+Include:
+"strftime.h"
+
+License:
+LGPL
+
+Maintainer:
+Jim Meyering, glibc