+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.
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>
#include <stdlib.h>
#include <string.h>
+#if USE_C_LOCALE && HAVE_STRFTIME_L
+# include <locale.h>
+#endif
+
#include "attribute.h"
#include <intprops.h>
# 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 */
# 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
#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. */
}
+#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. */
# 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;
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
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
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
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
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
}
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
*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);
}
# endif
break;
}
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
#else
goto underlying_strftime;
#endif
!= 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
break;
}
}
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
#else
goto underlying_strftime;
#endif
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
== 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");
!= 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
pad = yr_spec;
goto subformat;
}
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
#else
goto underlying_strftime;
#endif
DO_NUMBER (2, (era->offset
+ delta * era->absolute_direction));
}
+#elif USE_C_LOCALE && !HAVE_STRFTIME_L
#else
goto underlying_strftime;
#endif