Commit 3c15cf1e4f3 for php.net

commit 3c15cf1e4f3ec9919c0ccbc67afa74e76ccd161f
Author: Derick Rethans <github@derickrethans.nl>
Date:   Fri Jul 10 13:37:11 2026 +0100

    Add support for -1 as valid year for idate() output. (#22657)

    * Add support for -1 as valid year for idate() output.

    Closes GH-13273

    * Simplify error handling

    * Add note in UPGRADING.INTERNALS about API change

diff --git a/NEWS b/NEWS
index 919fda31c99..6d8d5df08ed 100644
--- a/NEWS
+++ b/NEWS
@@ -20,6 +20,7 @@ PHP                                                                        NEWS
     an error). (Derick)
   . Fixed Unix timestamps in February of the year 0 are misparsed with
     @-notation. (LukasGelbmann)
+  . Fixed bug GH-11368 (idate() doesn't work for the year -1). (Derick)

 - DBA:
   . Fixed OOB read on malformed length field in dba flatfile handler. (alhudz)
diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index 1c602548b1e..bdf874b382d 100644
--- a/UPGRADING.INTERNALS
+++ b/UPGRADING.INTERNALS
@@ -169,6 +169,10 @@ PHP 8.6 INTERNALS UPGRADE NOTES
 3. Module changes
 ========================

+- ext/date:
+  . php_idate() now returns the result state, and moves the return value into an
+    out parameter.
+
 - ext/mbstring:
   . Added GB18030-2022 to default encoding list for zh-CN.

diff --git a/ext/date/php_date.c b/ext/date/php_date.c
index 1dbcf51e72d..e14aef1e085 100644
--- a/ext/date/php_date.c
+++ b/ext/date/php_date.c
@@ -901,13 +901,13 @@ PHPAPI zend_string *php_format_date(const char *format, size_t format_len, time_
 /* }}} */

 /* {{{ php_idate */
-PHPAPI int php_idate(char format, time_t ts, bool localtime)
+PHPAPI bool php_idate(char format, time_t ts, bool localtime, int *result)
 {
 	timelib_time   *t;
 	timelib_tzinfo *tzi;
-	int retval = -1;
 	timelib_time_offset *offset = NULL;
 	timelib_sll isoweek, isoyear;
+	bool success = true;

 	t = timelib_time_ctor();

@@ -947,46 +947,49 @@ PHPAPI int php_idate(char format, time_t ts, bool localtime)

 	switch (format) {
 		/* day */
-		case 'd': case 'j': retval = (int) t->d; break;
+		case 'd': case 'j': *result = (int) t->d; break;

-		case 'N': retval = (int) timelib_iso_day_of_week(t->y, t->m, t->d); break;
-		case 'w': retval = (int) timelib_day_of_week(t->y, t->m, t->d); break;
-		case 'z': retval = (int) timelib_day_of_year(t->y, t->m, t->d); break;
+		case 'N': *result = (int) timelib_iso_day_of_week(t->y, t->m, t->d); break;
+		case 'w': *result = (int) timelib_day_of_week(t->y, t->m, t->d); break;
+		case 'z': *result = (int) timelib_day_of_year(t->y, t->m, t->d); break;

 		/* week */
-		case 'W': retval = (int) isoweek; break; /* iso weeknr */
+		case 'W': *result = (int) isoweek; break; /* iso weeknr */

 		/* month */
-		case 'm': case 'n': retval = (int) t->m; break;
-		case 't': retval = (int) timelib_days_in_month(t->y, t->m); break;
+		case 'm': case 'n': *result = (int) t->m; break;
+		case 't': *result = (int) timelib_days_in_month(t->y, t->m); break;

 		/* year */
-		case 'L': retval = (int) timelib_is_leap((int) t->y); break;
-		case 'y': retval = (int) (t->y % 100); break;
-		case 'Y': retval = (int) t->y; break;
-		case 'o': retval = (int) isoyear; break; /* iso year */
+		case 'L': *result = (int) timelib_is_leap((int) t->y); break;
+		case 'y': *result = (int) (t->y % 100); break;
+		case 'Y': *result = (int) t->y; break;
+		case 'o': *result = (int) isoyear; break; /* iso year */

 		/* Swatch Beat a.k.a. Internet Time */
 		case 'B':
-			retval = ((((long)t->sse)-(((long)t->sse) - ((((long)t->sse) % 86400) + 3600))) * 10);
-			if (retval < 0) {
-				retval += 864000;
+			*result = ((((long)t->sse)-(((long)t->sse) - ((((long)t->sse) % 86400) + 3600))) * 10);
+			if (*result < 0) {
+				*result += 864000;
 			}
 			/* Make sure to do this on a positive int to avoid rounding errors */
-			retval = (retval / 864) % 1000;
+			*result = (*result / 864) % 1000;
 			break;

 		/* time */
-		case 'g': case 'h': retval = (int) ((t->h % 12) ? (int) t->h % 12 : 12); break;
-		case 'H': case 'G': retval = (int) t->h; break;
-		case 'i': retval = (int) t->i; break;
-		case 's': retval = (int) t->s; break;
+		case 'g': case 'h': *result = (int) ((t->h % 12) ? (int) t->h % 12 : 12); break;
+		case 'H': case 'G': *result = (int) t->h; break;
+		case 'i': *result = (int) t->i; break;
+		case 's': *result = (int) t->s; break;

 		/* timezone */
-		case 'I': retval = (int) (!localtime ? offset->is_dst : 0); break;
-		case 'Z': retval = (int) (!localtime ? offset->offset : 0); break;
+		case 'I': *result = (int) (!localtime ? offset->is_dst : 0); break;
+		case 'Z': *result = (int) (!localtime ? offset->offset : 0); break;

-		case 'U': retval = (int) t->sse; break;
+		case 'U': *result = (int) t->sse; break;
+
+		default:
+			success = false;
 	}

 	if (!localtime) {
@@ -994,7 +997,7 @@ PHPAPI int php_idate(char format, time_t ts, bool localtime)
 	}
 	timelib_time_dtor(t);

-	return retval;
+	return success;
 }
 /* }}} */

@@ -1018,7 +1021,7 @@ PHP_FUNCTION(idate)
 	zend_string *format;
 	zend_long    ts;
 	bool    ts_is_null = 1;
-	int ret;
+	int ret = 0;

 	ZEND_PARSE_PARAMETERS_START(1, 2)
 		Z_PARAM_STR(format)
@@ -1035,8 +1038,8 @@ PHP_FUNCTION(idate)
 		ts = php_time();
 	}

-	ret = php_idate(ZSTR_VAL(format)[0], ts, 0);
-	if (ret == -1) {
+	bool ok = php_idate(ZSTR_VAL(format)[0], ts, 0, &ret);
+	if (!ok) {
 		php_error_docref(NULL, E_WARNING, "Unrecognized date format token");
 		RETURN_FALSE;
 	}
diff --git a/ext/date/php_date.h b/ext/date/php_date.h
index f26617c15ea..651cc28225f 100644
--- a/ext/date/php_date.h
+++ b/ext/date/php_date.h
@@ -124,7 +124,7 @@ PHPAPI time_t php_time(void);
 /* Backwards compatibility wrapper */
 PHPAPI zend_long php_parse_date(const char *string, zend_long *now);
 PHPAPI void php_mktime(INTERNAL_FUNCTION_PARAMETERS, bool gmt);
-PHPAPI int php_idate(char format, time_t ts, bool localtime);
+PHPAPI bool php_idate(char format, time_t ts, bool localtime, int *result);

 #define _php_strftime php_strftime