From 58366f8c22dde5d7287aeb8a9c9322526f81b4f0 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Fri, 25 Sep 2026 06:57:08 +0200 Subject: [PATCH 1/2] Report the supported year range in easter_date() errors Depending on the year, easter_date() reported the range of easter_days() or only one of its bounds. Determine the supported range once and report it in a single error message. --- ext/calendar/easter.c | 42 +++++------- ext/calendar/tests/easter_date_32bit.phpt | 2 +- ext/calendar/tests/easter_date_64bit.phpt | 66 +++++++++---------- .../easter_date_checks_upper_bound_32bit.phpt | 2 +- .../easter_date_checks_upper_bound_64bit.phpt | 2 +- ext/calendar/tests/gh16228.phpt | 2 +- ext/calendar/tests/gh23915.phpt | 27 ++++++++ 7 files changed, 82 insertions(+), 61 deletions(-) create mode 100644 ext/calendar/tests/gh23915.phpt diff --git a/ext/calendar/easter.c b/ext/calendar/easter.c index 72c52e0fcff7..4ca34e9558d3 100644 --- a/ext/calendar/easter.c +++ b/ext/calendar/easter.c @@ -33,9 +33,24 @@ static void _cal_easter(INTERNAL_FUNCTION_PARAMETERS, bool gm) struct tm te; zend_long year, golden, solar, lunar, pfm, dom, tmp, easter, result; zend_long method = CAL_EASTER_DEFAULT; - const zend_long max_year = (zend_long)(ZEND_LONG_MAX / 5) * 4; + zend_long min_year, max_year; bool year_is_null = 1; + if (gm) { + /* Timestamps only start after 1970 and are calculated by mktime() */ + min_year = 1970; +#ifdef ZEND_ENABLE_ZVAL_LONG64 + /* Timestamps only go up to the year 2.000.000.000 */ + max_year = 2000000000; +#else + max_year = 2037; +#endif + } else { + /* Keep the calculations below from overflowing */ + min_year = 1; + max_year = (ZEND_LONG_MAX / 5) * 4; + } + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l!l", &year, &year_is_null, &method) == FAILURE) { RETURN_THROWS(); @@ -54,31 +69,10 @@ static void _cal_easter(INTERNAL_FUNCTION_PARAMETERS, bool gm) } } - if (year <= 0 || year > max_year) { - zend_argument_value_error(1, "must be between 1 and " ZEND_LONG_FMT, max_year); - RETURN_THROWS(); - } - - #ifdef ZEND_ENABLE_ZVAL_LONG64 - /* Compiling for 64bit, allow years between 1970 and 2.000.000.000 */ - if (gm && year < 1970) { - /* timestamps only start after 1970 */ - zend_argument_value_error(1, "must be a year after 1970 (inclusive)"); - RETURN_THROWS(); - } - - if (gm && year > 2000000000) { - /* timestamps only go up to the year 2.000.000.000 */ - zend_argument_value_error(1, "must be a year before 2.000.000.000 (inclusive)"); - RETURN_THROWS(); - } - #else - /* Compiling for 32bit, allow years between 1970 and 2037 */ - if (gm && (year < 1970 || year > 2037)) { - zend_argument_value_error(1, "must be between 1970 and 2037 (inclusive)"); + if (year < min_year || year > max_year) { + zend_argument_value_error(1, "must be between " ZEND_LONG_FMT " and " ZEND_LONG_FMT, min_year, max_year); RETURN_THROWS(); } - #endif golden = (year % 19) + 1; /* the Golden number */ diff --git a/ext/calendar/tests/easter_date_32bit.phpt b/ext/calendar/tests/easter_date_32bit.phpt index 52f880edc113..5d025de7ac10 100644 --- a/ext/calendar/tests/easter_date_32bit.phpt +++ b/ext/calendar/tests/easter_date_32bit.phpt @@ -24,4 +24,4 @@ try { 2000-04-23 2001-04-15 2002-03-31 -easter_date(): Argument #1 ($year) must be between 1970 and 2037 (inclusive) +easter_date(): Argument #1 ($year) must be between 1970 and 2037 diff --git a/ext/calendar/tests/easter_date_64bit.phpt b/ext/calendar/tests/easter_date_64bit.phpt index 3939b7e4a4d4..1c2443d20c1a 100644 --- a/ext/calendar/tests/easter_date_64bit.phpt +++ b/ext/calendar/tests/easter_date_64bit.phpt @@ -1,33 +1,33 @@ ---TEST-- -Test easter_date() on 64bit systems ---SKIPIF-- - ---INI-- -date.timezone=UTC ---ENV-- -TZ=UTC ---EXTENSIONS-- -calendar ---FILE-- -getMessage()}\n"; -} -?> ---EXPECT-- -2000-04-23 -2001-04-15 -2002-03-31 -2045-04-09 -2046-03-25 -2047-04-14 -easter_date(): Argument #1 ($year) must be a year after 1970 (inclusive) +--TEST-- +Test easter_date() on 64bit systems +--SKIPIF-- + +--INI-- +date.timezone=UTC +--ENV-- +TZ=UTC +--EXTENSIONS-- +calendar +--FILE-- +getMessage()}\n"; +} +?> +--EXPECT-- +2000-04-23 +2001-04-15 +2002-03-31 +2045-04-09 +2046-03-25 +2047-04-14 +easter_date(): Argument #1 ($year) must be between 1970 and 2000000000 diff --git a/ext/calendar/tests/easter_date_checks_upper_bound_32bit.phpt b/ext/calendar/tests/easter_date_checks_upper_bound_32bit.phpt index 6a9cee8027fb..5f8a03fa62d4 100644 --- a/ext/calendar/tests/easter_date_checks_upper_bound_32bit.phpt +++ b/ext/calendar/tests/easter_date_checks_upper_bound_32bit.phpt @@ -18,4 +18,4 @@ try { } ?> --EXPECT-- -easter_date(): Argument #1 ($year) must be between 1970 and 2037 (inclusive) +easter_date(): Argument #1 ($year) must be between 1970 and 2037 diff --git a/ext/calendar/tests/easter_date_checks_upper_bound_64bit.phpt b/ext/calendar/tests/easter_date_checks_upper_bound_64bit.phpt index 2c7afbdfa2d9..4fcfc317a84d 100644 --- a/ext/calendar/tests/easter_date_checks_upper_bound_64bit.phpt +++ b/ext/calendar/tests/easter_date_checks_upper_bound_64bit.phpt @@ -18,4 +18,4 @@ try { } ?> --EXPECT-- -easter_date(): Argument #1 ($year) must be a year before 2.000.000.000 (inclusive) +easter_date(): Argument #1 ($year) must be between 1970 and 2000000000 diff --git a/ext/calendar/tests/gh16228.phpt b/ext/calendar/tests/gh16228.phpt index 9ce80688195b..61059f3d502e 100644 --- a/ext/calendar/tests/gh16228.phpt +++ b/ext/calendar/tests/gh16228.phpt @@ -23,4 +23,4 @@ try { --EXPECTF-- easter_days(): Argument #1 ($year) must be between 1 and %d easter_days(): Argument #1 ($year) must be between 1 and %d -easter_date(): Argument #1 ($year) must be between 1 and %d +easter_date(): Argument #1 ($year) must be between 1970 and %d diff --git a/ext/calendar/tests/gh23915.phpt b/ext/calendar/tests/gh23915.phpt new file mode 100644 index 000000000000..1444ab01e349 --- /dev/null +++ b/ext/calendar/tests/gh23915.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-23915 (easter_date() reports inconsistent year ranges in errors and returns -1 for years after 3000 on Windows) +--INI-- +date.timezone=UTC +--ENV-- +TZ=UTC +--EXTENSIONS-- +calendar +--FILE-- +getMessage(), "\n"; + } +} +?> +--EXPECTF-- +%i: ValueError: easter_date(): Argument #1 ($year) must be between 1970 and %d +0: ValueError: easter_date(): Argument #1 ($year) must be between 1970 and %d +1: ValueError: easter_date(): Argument #1 ($year) must be between 1970 and %d +1969: ValueError: easter_date(): Argument #1 ($year) must be between 1970 and %d +1970: 1970-03-29 +%d: ValueError: easter_date(): Argument #1 ($year) must be between 1970 and %d From 798cce750b36d9e6281e16483667ef74bea8864f Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Fri, 25 Sep 2026 06:58:14 +0200 Subject: [PATCH 2/2] Fix easter_date() beyond the year 3000 on Windows mktime() on Windows only goes up to the year 3000 and returns -1 beyond that, so easter_date() returned -1 instead of throwing for the years 3001 to 2000000000. Limit the year to 3000 on Windows. --- NEWS | 4 +++ ext/calendar/easter.c | 5 ++++ ext/calendar/tests/easter_date_64bit.phpt | 8 ++--- .../easter_date_checks_upper_bound_64bit.phpt | 5 +++- ext/calendar/tests/gh23915_windows.phpt | 30 +++++++++++++++++++ 5 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 ext/calendar/tests/gh23915_windows.phpt diff --git a/NEWS b/NEWS index 8d34aad3fe67..d621754d8329 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.27 +- Calendar: + . Fixed bug GH-23915 (easter_date() reports inconsistent year ranges and + returns -1 after the year 3000 on Windows). (Marc Bennewitz) + - CLI . Fix GH-22567 (Windows ZTS CLI SAPI should refresh its TSRMLS cache during request activation). (matyhtf) diff --git a/ext/calendar/easter.c b/ext/calendar/easter.c index 4ca34e9558d3..ef14da3d5b34 100644 --- a/ext/calendar/easter.c +++ b/ext/calendar/easter.c @@ -40,8 +40,13 @@ static void _cal_easter(INTERNAL_FUNCTION_PARAMETERS, bool gm) /* Timestamps only start after 1970 and are calculated by mktime() */ min_year = 1970; #ifdef ZEND_ENABLE_ZVAL_LONG64 +# ifdef PHP_WIN32 + /* mktime() on Windows only goes up to the year 3000 */ + max_year = 3000; +# else /* Timestamps only go up to the year 2.000.000.000 */ max_year = 2000000000; +# endif #else max_year = 2037; #endif diff --git a/ext/calendar/tests/easter_date_64bit.phpt b/ext/calendar/tests/easter_date_64bit.phpt index 1c2443d20c1a..a99dcad8ef25 100644 --- a/ext/calendar/tests/easter_date_64bit.phpt +++ b/ext/calendar/tests/easter_date_64bit.phpt @@ -19,15 +19,15 @@ echo date("Y-m-d", easter_date(2046))."\n"; echo date("Y-m-d", easter_date(2047))."\n"; try { easter_date(1492); -} catch (ValueError $ex) { - echo "{$ex->getMessage()}\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; } ?> ---EXPECT-- +--EXPECTF-- 2000-04-23 2001-04-15 2002-03-31 2045-04-09 2046-03-25 2047-04-14 -easter_date(): Argument #1 ($year) must be between 1970 and 2000000000 +ValueError: easter_date(): Argument #1 ($year) must be between 1970 and %d diff --git a/ext/calendar/tests/easter_date_checks_upper_bound_64bit.phpt b/ext/calendar/tests/easter_date_checks_upper_bound_64bit.phpt index 4fcfc317a84d..fa8b68a80af1 100644 --- a/ext/calendar/tests/easter_date_checks_upper_bound_64bit.phpt +++ b/ext/calendar/tests/easter_date_checks_upper_bound_64bit.phpt @@ -1,7 +1,10 @@ --TEST-- Test easter_date() on 64bit systems checks the upper year limit --SKIPIF-- - + --INI-- date.timezone=UTC --ENV-- diff --git a/ext/calendar/tests/gh23915_windows.phpt b/ext/calendar/tests/gh23915_windows.phpt new file mode 100644 index 000000000000..214008f60041 --- /dev/null +++ b/ext/calendar/tests/gh23915_windows.phpt @@ -0,0 +1,30 @@ +--TEST-- +GH-23915 (easter_date() reports inconsistent year ranges in errors and returns -1 for years after 3000 on Windows) +--SKIPIF-- + +--INI-- +date.timezone=UTC +--ENV-- +TZ=UTC +--EXTENSIONS-- +calendar +--FILE-- +getMessage(), "\n"; + } +} +?> +--EXPECT-- +3000: 3000-04-13 +3001: ValueError: easter_date(): Argument #1 ($year) must be between 1970 and 3000 +293274701009: ValueError: easter_date(): Argument #1 ($year) must be between 1970 and 3000