[tz] [PROPOSED 2/2] strftime: conform better to POSIX+

Paul Eggert eggert at cs.ucla.edu
Sat Aug 8 19:29:23 UTC 2020


The latest POSIX draft specifies errno values for some strftime
errors.  Implement those, plus one other one: a reliable way
to determine whether 0 represents failure or buffer exhaustion
(I’ll propose this to POSIX).
* newstrftime.3 (RETURN VALUE): Document this.
* strftime.c (strftime): Set errno according to current draft of
POSIX.  Also, set errno to ERANGE on overflow, and preserve errno
if there is no error.
(_fmt): Return NULL if %s would be out of range.  Callers changed.
---
 newstrftime.3 | 34 +++++++++++++++++++++++++++-------
 strftime.c    | 24 +++++++++++++++++++++++-
 2 files changed, 50 insertions(+), 8 deletions(-)

diff --git a/newstrftime.3 b/newstrftime.3
index 63842c7..887aba5 100644
--- a/newstrftime.3
+++ b/newstrftime.3
@@ -82,13 +82,6 @@ and one other character.
 No more than
 .I maxsize
 bytes are placed into the array.
-If the total number of resulting bytes, including the terminating
-NUL character, is not more than
-.IR maxsize ,
-.B strftime
-returns the number of bytes placed into the array, not counting the
-terminating NUL.
-Otherwise, zero is returned and the array contents are unspecified.
 .PP
 Each conversion specification is replaced by the characters as
 follows which are then copied into the array.
@@ -259,6 +252,33 @@ is replaced by a single %.
 is replaced by the locale's date and time in
 .BR date (1)
 format.
+.SH "RETURN VALUE"
+If the conversion is successful,
+.B strftime
+returns the number of bytes placed into the array, not counting the
+terminating NUL;
+.B errno
+is unchanged if the returned value is zero.
+Otherwise,
+.B errno
+is set to indicate the error, zero is returned,
+and the array contents are unspecified.
+.SH ERRORS
+This function fails if:
+.TP
+[ERANGE]
+The total number of resulting bytes, including the terminating
+NUL character, is more than
+.IR maxsize .
+.PP
+This function may fail if:
+.TP
+[EOVERFLOW]
+The format includes an
+.c %s
+conversion and the number of seconds since the Epoch cannot be represented
+in a
+.c time_t .
 .SH SEE ALSO
 date(1),
 getenv(3),
diff --git a/strftime.c b/strftime.c
index 14cbc9a..4f871cd 100644
--- a/strftime.c
+++ b/strftime.c
@@ -130,10 +130,15 @@ size_t
 strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
 {
 	char *	p;
+	int saved_errno = errno;
 	enum warn warn = IN_NONE;
 
 	tzset();
 	p = _fmt(format, t, s, s + maxsize, &warn);
+	if (!p) {
+	  errno = EOVERFLOW;
+	  return 0;
+	}
 	if (DEPRECATE_TWO_DIGIT_YEARS
 	    && warn != IN_NONE && getenv(YEAR_2000_NAME)) {
 		fprintf(stderr, "\n");
@@ -146,9 +151,12 @@ strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
 		else	fprintf(stderr, "all locales");
 		fprintf(stderr, "\n");
 	}
-	if (p == s + maxsize)
+	if (p == s + maxsize) {
+		errno = ERANGE;
 		return 0;
+	}
 	*p = '\0';
+	errno = saved_errno;
 	return p - s;
 }
 
@@ -312,7 +320,21 @@ label:
 					time_t		mkt;
 
 					tm = *t;
+					tm.tm_yday = -1;
 					mkt = mktime(&tm);
+					if (mkt == (time_t) -1) {
+					  /* Fail unless this -1 represents
+					     a valid time.  */
+					  struct tm tm_1;
+					  if (!localtime_r(&mkt, &tm_1))
+					    return NULL;
+					  if (!(tm.tm_year == tm_1.tm_year
+						&& tm.tm_yday == tm_1.tm_yday
+						&& tm.tm_hour == tm_1.tm_hour
+						&& tm.tm_min == tm_1.tm_min
+						&& tm.tm_sec == tm_1.tm_sec))
+					    return NULL;
+					}
 					if (TYPE_SIGNED(time_t))
 						sprintf(buf, "%"PRIdMAX,
 							(intmax_t) mkt);
-- 
2.17.1



More information about the tz mailing list