From: Bruno Haible Date: Thu, 8 Feb 2024 00:09:08 +0000 (+0100) Subject: c-nstrftime: New module. X-Git-Tag: v1.0~452 X-Git-Url: https://gitweb.git.savannah.gnu.org/gitweb/?a=commitdiff_plain;h=2e665106444eb32b81e8e94df4b5d8e67723837f;p=gnulib.git 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 . Conditionally include . (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. --- diff --git a/ChangeLog b/ChangeLog index ecbe0ea7b2..0d9b82b1e9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,26 @@ +2024-02-07 Bruno Haible + + 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 . + Conditionally include . + (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 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 index 0000000000..cee7437e10 --- /dev/null +++ b/lib/c-nstrftime.c @@ -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 . */ + +#define USE_C_LOCALE 1 +#define my_strftime c_nstrftime +#include "strftime.c" diff --git a/lib/strftime.c b/lib/strftime.c index d81c7eddfa..f6714524bd 100644 --- a/lib/strftime.c +++ b/lib/strftime.c @@ -14,6 +14,14 @@ You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ +#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 @@ -31,7 +39,11 @@ # include "time-internal.h" #endif -#include +#if USE_C_LOCALE +# include "c-ctype.h" +#else +# include +#endif #include #include @@ -67,6 +79,10 @@ extern char *tzname[]; #include #include +#if USE_C_LOCALE && HAVE_STRFTIME_L +# include +#endif + #include "attribute.h" #include @@ -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 diff --git a/lib/strftime.h b/lib/strftime.h index 6e3dbf834c..8ce62cdb6d 100644 --- a/lib/strftime.h +++ b/lib/strftime.h @@ -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 index 0000000000..9c8268b557 --- /dev/null +++ b/m4/c-nstrftime.m4 @@ -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 ]]) +]) diff --git a/modules/c-nstrftime b/modules/c-nstrftime new file mode 100644 index 0000000000..8b2348c227 --- /dev/null +++ b/modules/c-nstrftime @@ -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