Hi Paul,
I wrote:
> I'll work on a proposed patch in that direction now.
Here's a proposed patch. The corresponding patches to coreutils,
diffutils, emacs, guile, octave show how the return value convention
makes the code more consistent with common conventions.
How does this look?
The fputc related changes from your patch are not contained here;
you may want to add them afterwards.
>From 9de35084ad01622a4eef33e230c9c45219382762 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Fri, 31 Oct 2025 21:52:06 +0100
Subject: [PATCH] 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.
---
ChangeLog | 24 +++++++++++++++
NEWS | 7 +++++
lib/fprintftime.h | 3 +-
lib/strftime.c | 56 ++++++++++++++++++++++------------
lib/strftime.h | 17 ++++++-----
tests/test-nstrftime-DE.c | 2 +-
tests/test-nstrftime-ET.c | 2 +-
tests/test-nstrftime-IR.c | 2 +-
tests/test-nstrftime-TH.c | 2 +-
tests/test-nstrftime-w32utf8.c | 4 +--
tests/test-nstrftime.h | 27 ++++++++--------
11 files changed, 99 insertions(+), 47 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 7136e3cb57..ed88885d7d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,27 @@
+2025-10-31 Bruno Haible <[email protected]>
+
+ 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 <[email protected]>
Fix support for Mac OS X/PowerPC G5.
diff --git a/NEWS b/NEWS
index 55dfcd95b0..0f226108ca 100644
--- a/NEWS
+++ b/NEWS
@@ -78,6 +78,13 @@ User visible incompatible changes
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
diff --git a/lib/fprintftime.h b/lib/fprintftime.h
index a340b86117..df6eed66b3 100644
--- a/lib/fprintftime.h
+++ b/lib/fprintftime.h
@@ -32,7 +32,8 @@ extern "C" {
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);
diff --git a/lib/strftime.c b/lib/strftime.c
index e29cd32666..afb783778e 100644
--- a/lib/strftime.c
+++ b/lib/strftime.c
@@ -205,7 +205,8 @@ enum pad_style
#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
@@ -215,6 +216,18 @@ typedef ptrdiff_t sbyte_count_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 \
@@ -249,7 +262,7 @@ typedef ptrdiff_t sbyte_count_t;
if (_incr >= maxsize - i) \
{ \
errno = ERANGE; \
- return 0; \
+ return FAILURE; \
} \
if (p) \
{ \
@@ -908,14 +921,14 @@ static CHAR_T const c_month_names[][sizeof "September"] =
# 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)) \
@@ -1118,13 +1131,16 @@ get_tm_zone (timezone_t tz, char *ubuf, int ubufsize, int modifier,
}
/* 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)
@@ -1167,7 +1183,7 @@ libc_hidden_def (my_strftime)
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,
@@ -1239,7 +1255,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
# 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
@@ -1603,13 +1619,15 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
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,
@@ -1894,7 +1912,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
if (ckd_add (&i, i, padding) && FPRINTFTIME)
{
errno = ERANGE;
- return 0;
+ return FAILURE;
}
width -= padding;
}
@@ -2057,7 +2075,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
if (ltm.tm_yday < 0)
{
errno = EOVERFLOW;
- return 0;
+ return FAILURE;
}
/* Generate string value for T using time_t arithmetic;
@@ -2276,12 +2294,12 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize)
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)
{
diff --git a/lib/strftime.h b/lib/strftime.h
index a76c98c9c8..0ac129a499 100644
--- a/lib/strftime.h
+++ b/lib/strftime.h
@@ -72,22 +72,23 @@ extern "C" {
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
}
diff --git a/tests/test-nstrftime-DE.c b/tests/test-nstrftime-DE.c
index e53d469f20..a3c8bf61f7 100644
--- a/tests/test-nstrftime-DE.c
+++ b/tests/test-nstrftime-DE.c
@@ -68,7 +68,7 @@ main ()
#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__)
{
diff --git a/tests/test-nstrftime-ET.c b/tests/test-nstrftime-ET.c
index a4e0051c63..cd9988fc1a 100644
--- a/tests/test-nstrftime-ET.c
+++ b/tests/test-nstrftime-ET.c
@@ -71,7 +71,7 @@ main ()
#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__)
{
diff --git a/tests/test-nstrftime-IR.c b/tests/test-nstrftime-IR.c
index 5f0c518489..459dd73d04 100644
--- a/tests/test-nstrftime-IR.c
+++ b/tests/test-nstrftime-IR.c
@@ -71,7 +71,7 @@ main ()
#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__)
{
diff --git a/tests/test-nstrftime-TH.c b/tests/test-nstrftime-TH.c
index b65cab4ed3..9f87451d6e 100644
--- a/tests/test-nstrftime-TH.c
+++ b/tests/test-nstrftime-TH.c
@@ -65,7 +65,7 @@ main ()
#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__)
{
diff --git a/tests/test-nstrftime-w32utf8.c b/tests/test-nstrftime-w32utf8.c
index 433a983fd8..3218d5e588 100644
--- a/tests/test-nstrftime-w32utf8.c
+++ b/tests/test-nstrftime-w32utf8.c
@@ -47,7 +47,7 @@ main (int argc, char *argv[])
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);
@@ -59,7 +59,7 @@ main (int argc, char *argv[])
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);
diff --git a/tests/test-nstrftime.h b/tests/test-nstrftime.h
index d16255d906..387a6c3b0f 100644
--- a/tests/test-nstrftime.h
+++ b/tests/test-nstrftime.h
@@ -23,12 +23,12 @@
#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);
@@ -67,12 +67,12 @@ posixtm_test (void)
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);
@@ -206,7 +206,7 @@ tzalloc_test (void)
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)
{
@@ -233,7 +233,7 @@ tzalloc_test (void)
}
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);
@@ -280,8 +280,8 @@ quarter_test (void)
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);
@@ -310,12 +310,12 @@ errno_test (void)
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);
@@ -335,7 +335,7 @@ errno_test (void)
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)
{
@@ -369,7 +369,8 @@ errno_test (void)
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))
{
@@ -400,7 +401,7 @@ locales_test (language_t language)
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);
--
2.51.0
diff --git a/src/ls.c b/src/ls.c
index 447089422..5183a414e 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -4153,7 +4153,7 @@ print_current_files (void)
Note on glibc-2.7 at least, this speeds up the whole 'ls -lU'
process by around 17%, compared to letting strftime() handle the %b. */
-static size_t
+static ptrdiff_t
align_nstrftime (char *buf, size_t size, bool recent, struct tm const *tm,
timezone_t tz, int ns)
{
@@ -4186,9 +4186,9 @@ long_time_expected_width (void)
their implementations limit the offset to 167:59 and 24:00, resp. */
if (localtime_rz (localtz, &epoch, &tm))
{
- size_t len = align_nstrftime (buf, sizeof buf, false,
- &tm, localtz, 0);
- if (len != 0)
+ ptrdiff_t len = align_nstrftime (buf, sizeof buf, false,
+ &tm, localtz, 0);
+ if (len > 0)
width = mbsnwidth (buf, len, MBSWIDTH_FLAGS);
}
@@ -4293,7 +4293,7 @@ print_long_format (const struct fileinfo *f)
+ LONGEST_HUMAN_READABLE + 1 /* minor device number */
+ TIME_STAMP_LEN_MAXIMUM + 1 /* max length of time/date */
];
- size_t s;
+ ptrdiff_t s;
char *p;
struct timespec when_timespec;
struct tm when_local;
@@ -4451,6 +4451,8 @@ print_long_format (const struct fileinfo *f)
whole number of seconds. */
s = align_nstrftime (p, TIME_STAMP_LEN_MAXIMUM + 1, recent,
&when_local, localtz, when_timespec.tv_nsec);
+ if (s < 0)
+ s = 0;
}
if (s || !*p)
diff --git a/src/context.c b/src/context.c
index 2687ad8..156a191 100644
--- a/src/context.c
+++ b/src/context.c
@@ -72,7 +72,7 @@ print_context_label (char const *mark,
struct tm const *tm = localtime (&ts.tv_sec);
int nsec = ts.tv_nsec;
- if (tm && nstrftime (buf, sizeof buf, time_format, tm, localtz, nsec))
+ if (tm && nstrftime (buf, sizeof buf, time_format, tm, localtz, nsec) > 0)
fprintf (outfile, "%s %s\t%s", mark, name, buf);
else if (TYPE_SIGNED (time_t))
{
diff --git a/src/timefns.c b/src/timefns.c
index b4baeaaff82..13fed52b0e7 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -1346,10 +1346,9 @@ emacs_nmemftime (char *s, size_t maxsize, const char *format,
'\0' byte so we must invoke it separately for each such string. */
for (;;)
{
- errno = 0;
- size_t result = nstrftime (s, maxsize, format, tp, tz, ns);
- if (result == 0 && errno != 0)
- return result;
+ ptrdiff_t result = nstrftime (s, maxsize, format, tp, tz, ns);
+ if (result == -1)
+ return 0; /* errno is set here */
if (s)
s += result + 1;
diff --git a/libguile/stime.c b/libguile/stime.c
index 18af71350..b0430b0a6 100644
--- a/libguile/stime.c
+++ b/libguile/stime.c
@@ -660,6 +660,7 @@ SCM_DEFINE (scm_strftime, "strftime", 2, 0, 0,
char *fmt;
char *myfmt;
size_t len;
+ ptrdiff_t ret;
SCM result;
SCM_VALIDATE_STRING (1, format);
@@ -714,7 +715,8 @@ SCM_DEFINE (scm_strftime, "strftime", 2, 0, 0,
/* Use `nstrftime ()' from Gnulib, which supports all GNU extensions
supported by glibc. */
- while ((len = nstrftime (tbuf, size, myfmt, &t, 0, 0)) == 0)
+ while ((ret = nstrftime (tbuf, size, myfmt, &t, 0, 0)) == -1
+ && errno == ERANGE)
{
free (tbuf);
size *= 2;
--- liboctave/wrappers/strftime-wrapper.c.bak 2024-04-14 04:30:41.010379929 +0200
+++ liboctave/wrappers/strftime-wrapper.c 2025-10-31 21:43:43.033473012 +0100
@@ -41,5 +41,6 @@
octave_strftime_wrapper (char *buf, size_t len, const char *fmt,
const struct tm *t)
{
- return nstrftime (buf, len, fmt, t, NULL, 0);
+ ptrdiff_t ret = nstrftime (buf, len, fmt, t, NULL, 0);
+ return (ret >= 0 ? ret : 0);
}