+2025-10-31 Bruno Haible <bruno@clisp.org>
+
+ nstrftime, fprintftime: Return -1, not 0, in case of failure.
+ * lib/strftime.h (nstrftime, c_nstrftime): Change return type to
+ ptrdiff_t.
+ * lib/strftime.c (retval_t): New type.
+ (FAILURE): New macro.
+ (width_add): Return FAILURE instead of 0.
+ (my_strftime): Change return type to retval_t.
+ (__strftime_internal): Likewise. Change some local variables to
+ retval_t. Handle failure of recursive __strftime_internal invocation.
+ Return FAILURE instead of 0.
+ * lib/fprintftime.h (fprintftime): Update specification.
+ * tests/test-nstrftime.h (FUNC_CHECKED): Change return type to
+ ptrdiff_t.
+ (posixtm_test, tzalloc_test, quarter_test, errno_test, locales_test):
+ Update.
+ * tests/test-nstrftime-DE.c (main): Update.
+ * tests/test-nstrftime-ET.c (main): Likewise.
+ * tests/test-nstrftime-IR.c (main): Likewise.
+ * tests/test-nstrftime-TH.c (main): Likewise.
+ * tests/test-nstrftime-w32utf8.c (main): Likewise.
+ * NEWS: Mention the changes.
+
2025-10-31 Bruno Haible <bruno@clisp.org>
Fix support for Mac OS X/PowerPC G5.
Date Modules Changes
+2025-10-31 nstrftime The return type changed from size_t to ptrdiff_t.
+ c-nstrftime The return value in case of failure changed from 0
+ to -1.
+
+2025-10-31 fprintftime The return value in case of failure changed from 0
+ to -1.
+
2025-10-30 fprintftime The return value is changed from size_t to off64_t.
2025-08-05 git-merge-changelog This module is removed. Use the package from
nstrftime format string, FMT) the time data, *TM, and the ZONE
and NANOSECONDS values.
- Return the number of bytes written to the stream (always >= 0). */
+ Return the number of bytes written to the stream.
+ Upon failure, return -1 with errno set. */
off64_t fprintftime (FILE *fp, char const *fmt, struct tm const *tm,
timezone_t zone, int nanoseconds);
#if FPRINTFTIME
# define STREAM_OR_CHAR_T FILE
# define STRFTIME_ARG(x) /* empty */
-typedef off64_t byte_count_t, sbyte_count_t;
+typedef off64_t byte_count_t;
+typedef off64_t sbyte_count_t;
# define SBYTE_COUNT_MAX 0x7fffffffffffffff
#else
# define STREAM_OR_CHAR_T CHAR_T
# define SBYTE_COUNT_MAX PTRDIFF_MAX
#endif
+/* The functions strftime[_l], wcsftime[_l] defined by glibc have a return type
+ 'size_t', for compatibility with POSIX, and return 0 upon failure.
+ The functions defined by Gnulib have a signed return type, and return -1
+ upon failure. */
+#ifdef _LIBC
+typedef size_t retval_t;
+# define FAILURE 0
+#else
+typedef sbyte_count_t retval_t;
+# define FAILURE -1
+#endif
+
#if FPRINTFTIME
# define memset_byte(P, Len, Byte) \
do \
if (_incr >= maxsize - i) \
{ \
errno = ERANGE; \
- return 0; \
+ return FAILURE; \
} \
if (p) \
{ \
# define ns 0
#endif
-static byte_count_t __strftime_internal (STREAM_OR_CHAR_T *,
- STRFTIME_ARG (size_t)
- const CHAR_T *, const struct tm *,
- CAL_ARGS (const struct calendar *,
- struct calendar_date *)
- bool, enum pad_style,
- sbyte_count_t, bool *
- extra_args_spec LOCALE_PARAM);
+static retval_t __strftime_internal (STREAM_OR_CHAR_T *,
+ STRFTIME_ARG (size_t)
+ const CHAR_T *, const struct tm *,
+ CAL_ARGS (const struct calendar *,
+ struct calendar_date *)
+ bool, enum pad_style,
+ sbyte_count_t, bool *
+ extra_args_spec LOCALE_PARAM);
#if !defined _LIBC \
&& (!(HAVE_ONLY_C_LOCALE || (USE_C_LOCALE && !HAVE_STRFTIME_L)) \
}
/* Write information from TP into S according to the format
- string FORMAT. Return the humber of bytes written.
+ string FORMAT. Return the number of bytes written.
+ Upon failure:
+ - return 0 for the functions defined by glibc,
+ - return -1 for the functions defined by Gnulib.
If !FPRINTFTIME, write no more than MAXSIZE bytes (including the
terminating '\0'), and if S is NULL do not write into S.
To determine how many characters would be written, use NULL for S
and (size_t) -1 for MAXSIZE. */
-byte_count_t
+retval_t
my_strftime (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
const CHAR_T *format,
const struct tm *tp extra_args_spec LOCALE_PARAM)
UPCASE indicates that the result should be converted to upper case.
YR_SPEC and WIDTH specify the padding and width for the year.
*TZSET_CALLED indicates whether tzset has been called here. */
-static byte_count_t
+static retval_t
__strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
const CHAR_T *format,
const struct tm *tp,
# define ampm (L_("AMPM") + 2 * (tp->tm_hour > 11))
# define ap_len 2
#endif
- byte_count_t i = 0;
+ retval_t i = 0;
STREAM_OR_CHAR_T *p = s;
const CHAR_T *f;
#if DO_MULTIBYTE && !defined COMPILE_WIDE
subwidth = -1;
subformat_width:
{
- byte_count_t len =
+ retval_t len =
__strftime_internal (NULL, STRFTIME_ARG ((size_t) -1)
subfmt, tp,
CAL_ARGS (cal, caldate)
to_uppcase, pad, subwidth,
tzset_called
extra_args LOCALE_ARG);
+ if (FAILURE < 0 && len < 0)
+ return FAILURE; /* errno is set here */
add (len, __strftime_internal (p,
STRFTIME_ARG (maxsize - i)
subfmt, tp,
if (ckd_add (&i, i, padding) && FPRINTFTIME)
{
errno = ERANGE;
- return 0;
+ return FAILURE;
}
width -= padding;
}
if (ltm.tm_yday < 0)
{
errno = EOVERFLOW;
- return 0;
+ return FAILURE;
}
/* Generate string value for T using time_t arithmetic;
mbstate_t st = {0};
size_t len = __mbsrtowcs_l (p, &z, maxsize - i, &st, loc);
if (len == (size_t) -1)
- return 0;
+ return FAILURE;
size_t incr = len < w ? w : len;
if (incr >= maxsize - i)
{
errno = ERANGE;
- return 0;
+ return FAILURE;
}
if (p)
{
Store the result, as a string with a trailing NUL character, at the
beginning of the array __S[0..__MAXSIZE-1] and return the length of
that string, not counting the trailing NUL, and without changing errno.
- If unsuccessful, possibly change the array __S, set errno, and return 0;
+ If unsuccessful, possibly change the array __S, set errno, and return -1;
errno == ERANGE means the string didn't fit.
This function is like strftime, but with two more arguments:
* __TZ instead of the local timezone information,
- * __NS as the number of nanoseconds in the %N directive.
+ * __NS as the number of nanoseconds in the %N directive,
+ and with a failure return value of -1 instead of 0.
*/
-size_t nstrftime (char *restrict __s, size_t __maxsize,
- char const *__format,
- struct tm const *__tp, timezone_t __tz, int __ns);
+ptrdiff_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);
+ptrdiff_t c_nstrftime (char *restrict __s, size_t __maxsize,
+ char const *__format,
+ struct tm const *__tp, timezone_t __tz, int __ns);
#ifdef __cplusplus
}
#else
char buf[100];
- size_t ret;
+ ptrdiff_t ret;
/* Native Windows does not support dates before 1970-01-01. */
# if !(defined _WIN32 && !defined __CYGWIN__)
{
#else
char buf[100];
- size_t ret;
+ ptrdiff_t ret;
/* Native Windows does not support dates before 1970-01-01. */
# if !(defined _WIN32 && !defined __CYGWIN__)
{
#else
char buf[100];
- size_t ret;
+ ptrdiff_t ret;
/* Native Windows does not support dates before 1970-01-01. */
# if !(defined _WIN32 && !defined __CYGWIN__)
{
#else
char buf[100];
- size_t ret;
+ ptrdiff_t ret;
/* Native Windows does not support dates before 1970-01-01. */
# if !(defined _WIN32 && !defined __CYGWIN__)
{
if (strcmp (argv[1], "1") == 0)
{
/* Test a non-ASCII French month name. */
- size_t n = nstrftime (buf, sizeof (buf), "%B", tm, NULL, ns);
+ ptrdiff_t n = nstrftime (buf, sizeof (buf), "%B", tm, NULL, ns);
ASSERT (n > 0);
printf ("buf = |%s|\n", buf);
fflush (stdout);
if (strcmp (argv[1], "2") == 0)
{
/* Test a non-ASCII Japanese weekday name. */
- size_t n = nstrftime (buf, sizeof (buf), "%A", tm, NULL, ns);
+ ptrdiff_t n = nstrftime (buf, sizeof (buf), "%A", tm, NULL, ns);
ASSERT (n > 0);
printf ("buf = |%s|\n", buf);
fflush (stdout);
#define TZ_ANGLE_BRACKETS_SHOULD_WORK (200112 <= _POSIX_VERSION)
/* A wrapper around FUNC that checks the return value. */
-static size_t
+static ptrdiff_t
FUNC_CHECKED (char *restrict s, size_t maxsize,
char const *format,
struct tm const *tp, timezone_t tz, int ns)
{
- size_t ret = FUNC (s, maxsize, format, tp, tz, ns);
+ ptrdiff_t ret = FUNC (s, maxsize, format, tp, tz, ns);
if (ret > 0)
{
ASSERT (ret < maxsize);
char buf[1000];
time_t t = T[i].in;
struct tm *tm = gmtime (&t);
- size_t n;
+ ptrdiff_t n;
ASSERT (tm);
n = FUNC_CHECKED (buf, sizeof buf, T[i].fmt, tm, NULL, T[i].in_ns);
- if (n == 0)
+ if (n == -1)
{
fail = 1;
printf ("%s failed with format %s\n", FUNC_NAME, T[i].fmt);
static char const format[] = "%Y-%m-%d %H:%M:%S %z (%Z)";
char buf[1000];
struct tm tm;
- size_t n;
+ ptrdiff_t n;
if (!tz && tza->setting)
{
}
n = FUNC_CHECKED (buf, sizeof buf, format, &tm, tz, 0);
- if (n == 0)
+ if (n == -1)
{
fail = 1;
printf ("%s: %ld: %s failed\n", setting, lt, FUNC_NAME);
struct tm qtm = { .tm_mon = mon - 1 };
char fmt[3] = {'%','q','\0'};
- size_t r = FUNC_CHECKED (out, sizeof (out), fmt, &qtm, NULL, 0);
- if (r == 0)
+ ptrdiff_t r = FUNC_CHECKED (out, sizeof (out), fmt, &qtm, NULL, 0);
+ if (r == -1)
{
printf ("%s(\"%%q\") failed\n", FUNC_NAME);
fflush (stdout);
int fail = 0;
struct tm tm = { .tm_year = 2020 - 1900, .tm_mday = 1 };
char buf[INT_BUFSIZE_BOUND (time_t)];
- size_t n;
+ ptrdiff_t n;
int bigyear = LLONG_MAX - 1900 < INT_MAX ? LLONG_MAX - 1900 : INT_MAX;
errno = 0;
n = FUNC_CHECKED (buf, 0, "%m", &tm, NULL, 0);
- if (! (n == 0 && errno == ERANGE))
+ if (! (n == -1 && errno == ERANGE))
{
fail = 1;
printf ("%s failed to set errno = ERANGE\n", FUNC_NAME);
tm.tm_year = bigyear;
errno = 0;
n = FUNC_CHECKED (buf, sizeof buf, "%s", &tm, NULL, 0);
- if (n == 0)
+ if (n == -1)
{
if (errno != EOVERFLOW)
{
else
{
char buf1[sizeof buf];
- size_t n1 = FUNC_CHECKED (buf1, sizeof buf1, "%s", tmp, NULL, 0);
+ ptrdiff_t n1 =
+ FUNC_CHECKED (buf1, sizeof buf1, "%s", tmp, NULL, 0);
buf1[n1] = '\0';
if (! STREQ (buf, buf1))
{
struct tm *tm = gmtime (&t);
int ns = 123456789;
char buf[100];
- size_t n;
+ ptrdiff_t n;
n = FUNC_CHECKED (buf, sizeof buf, "%+4Y-%m-%d %H:%M:%S.%N", tm, NULL, ns);
ASSERT (n > 0);