Commit 10704f079f6 for php.net

commit 10704f079f6087a51e3b579faca00d833141d889
Author: “LamentXU123” <108666168+LamentXU123@users.noreply.github.com>
Date:   Fri May 15 16:18:51 2026 +0800

    ext/intl: Fix out-of-bounds argument positions in calendar date/time APIs.

    close GH-22044

diff --git a/NEWS b/NEWS
index 8dea2348ff3..e9392cadf7d 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,11 @@ PHP                                                                        NEWS
 |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
 ?? ??? ????, PHP 8.4.22

+- Intl:
+  . Fix incorrect argument positions in out-of-bounds errors for
+    IntlCalendar::set(), IntlCalendar::setDate(), IntlCalendar::setDateTime(),
+    and IntlGregorianCalendar date/time construction. (Weilin Du)
+
 - MySQLnd:
   . Fix persistent free of non-persistent connect_attr key (David Carlier).

diff --git a/UPGRADING b/UPGRADING
index a67fc1b1d88..466b445c2d1 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -82,6 +82,9 @@ PHP 8.4 UPGRADE NOTES
     . bind_textdomain_codeset, textdomain and d(*)gettext functions now throw an exception
       if the domain argument is empty.
   . Intl:
+    . IntlCalendar::set(), IntlCalendar::setDate(), IntlCalendar::setDateTime(),
+      and IntlGregorianCalendar date/time construction now report the correct
+      argument position for out-of-bounds integer values.
     . resourcebundle_get(), ResourceBundle::get(), and accessing offsets on a
       ResourceBundle object now throw:
       - TypeError for invalid offset types
diff --git a/ext/intl/calendar/calendar_methods.cpp b/ext/intl/calendar/calendar_methods.cpp
index 9b7a37c0df5..61408375c86 100644
--- a/ext/intl/calendar/calendar_methods.cpp
+++ b/ext/intl/calendar/calendar_methods.cpp
@@ -391,8 +391,8 @@ U_CFUNC PHP_FUNCTION(intlcal_set)
 	}

 	for (int i = 0; i < arg_num; i++) {
-		/* Arguments start at 1 */
-		ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(args[i], i + 1);
+		/* Count from intlcal_set($calendar, ...), so date/time arguments start at #2. */
+		ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(args[i], i + 2);
 	}

 	CALENDAR_METHOD_FETCH_OBJECT;
@@ -427,9 +427,10 @@ U_CFUNC PHP_METHOD(IntlCalendar, setDate)
 		RETURN_THROWS();
 	}

-	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(year, 1);
-	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(month, 2);
-	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(day, 3);
+	/* These method-only APIs parse the object first, so the API argument positions are offset by +1. */
+	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(year, 2);
+	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(month, 3);
+	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(day, 4);

 	CALENDAR_METHOD_FETCH_OBJECT;

@@ -450,18 +451,19 @@ U_CFUNC PHP_METHOD(IntlCalendar, setDateTime)
 		RETURN_THROWS();
 	}

-	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(year, 1);
-	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(month, 2);
-	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(day, 3);
-	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(hour, 4);
-	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(minute, 5);
+	/* These method-only APIs parse the object first, so the API argument positions are offset by +1. */
+	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(year, 2);
+	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(month, 3);
+	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(day, 4);
+	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(hour, 5);
+	ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(minute, 6);

 	CALENDAR_METHOD_FETCH_OBJECT;

 	if (second_is_null) {
 		co->ucal->set((int32_t) year, (int32_t) month, (int32_t) day, (int32_t) hour, (int32_t) minute);
 	} else {
-		ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(second, 6);
+		ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(second, 7);
 		co->ucal->set((int32_t) year, (int32_t) month, (int32_t) day, (int32_t) hour, (int32_t) minute, (int32_t) second);
 	}
 }
diff --git a/ext/intl/calendar/gregoriancalendar_methods.cpp b/ext/intl/calendar/gregoriancalendar_methods.cpp
index d6b8f0602dd..0b36e621ef7 100644
--- a/ext/intl/calendar/gregoriancalendar_methods.cpp
+++ b/ext/intl/calendar/gregoriancalendar_methods.cpp
@@ -178,7 +178,7 @@ static void _php_intlgregcal_constructor_body(
 	} else {
 		// From date/time (3, 5 or 6 arguments)
 		for (int i = 0; i < variant; i++) {
-			ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(largs[i], hasThis() ? (i-1) : i);
+			ZEND_VALUE_ERROR_OUT_OF_BOUND_VALUE(largs[i], i + 1);
 		}

 		if (variant == 3) {
diff --git a/ext/intl/tests/calendar_set_date_out_of_bounds.phpt b/ext/intl/tests/calendar_set_date_out_of_bounds.phpt
new file mode 100644
index 00000000000..db9d18275ae
--- /dev/null
+++ b/ext/intl/tests/calendar_set_date_out_of_bounds.phpt
@@ -0,0 +1,32 @@
+--TEST--
+IntlCalendar::setDate(): out-of-bounds arguments report correct positions
+--EXTENSIONS--
+intl
+--SKIPIF--
+<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
+--FILE--
+<?php
+$cal = IntlCalendar::createInstance();
+
+try {
+    $cal->setDate(99999999999, 1, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $cal->setDate(1, 99999999999, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $cal->setDate(1, 1, 99999999999);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+IntlCalendar::setDate(): Argument #1 ($year) must be between -2147483648 and 2147483647
+IntlCalendar::setDate(): Argument #2 ($month) must be between -2147483648 and 2147483647
+IntlCalendar::setDate(): Argument #3 ($dayOfMonth) must be between -2147483648 and 2147483647
diff --git a/ext/intl/tests/calendar_set_date_time_out_of_bounds.phpt b/ext/intl/tests/calendar_set_date_time_out_of_bounds.phpt
new file mode 100644
index 00000000000..798ab1ebb01
--- /dev/null
+++ b/ext/intl/tests/calendar_set_date_time_out_of_bounds.phpt
@@ -0,0 +1,53 @@
+--TEST--
+IntlCalendar::setDateTime(): out-of-bounds arguments report correct positions
+--EXTENSIONS--
+intl
+--SKIPIF--
+<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
+--FILE--
+<?php
+$cal = IntlCalendar::createInstance();
+
+try {
+    $cal->setDateTime(99999999999, 1, 1, 1, 1, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $cal->setDateTime(1, 99999999999, 1, 1, 1, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $cal->setDateTime(1, 1, 99999999999, 1, 1, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $cal->setDateTime(1, 1, 1, 99999999999, 1, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $cal->setDateTime(1, 1, 1, 1, 99999999999, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $cal->setDateTime(1, 1, 1, 1, 1, 99999999999);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+IntlCalendar::setDateTime(): Argument #1 ($year) must be between -2147483648 and 2147483647
+IntlCalendar::setDateTime(): Argument #2 ($month) must be between -2147483648 and 2147483647
+IntlCalendar::setDateTime(): Argument #3 ($dayOfMonth) must be between -2147483648 and 2147483647
+IntlCalendar::setDateTime(): Argument #4 ($hour) must be between -2147483648 and 2147483647
+IntlCalendar::setDateTime(): Argument #5 ($minute) must be between -2147483648 and 2147483647
+IntlCalendar::setDateTime(): Argument #6 ($second) must be between -2147483648 and 2147483647
diff --git a/ext/intl/tests/calendar_set_out_of_bounds.phpt b/ext/intl/tests/calendar_set_out_of_bounds.phpt
new file mode 100644
index 00000000000..1ca407d3f71
--- /dev/null
+++ b/ext/intl/tests/calendar_set_out_of_bounds.phpt
@@ -0,0 +1,55 @@
+--TEST--
+IntlCalendar::set(): out-of-bounds date/time arguments report correct positions
+--EXTENSIONS--
+intl
+--SKIPIF--
+<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
+--FILE--
+<?php
+$cal = IntlCalendar::createInstance();
+
+try {
+    $cal->set(99999999999, 1, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    intlcal_set($cal, 1, 99999999999, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $cal->set(1, 1, 1, 99999999999, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    $cal->set(1, 1, 1, 1, 99999999999, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    intlcal_set($cal, 1, 1, 1, 1, 1, 99999999999);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+?>
+--EXPECTF--
+Deprecated: Calling IntlCalendar::set() with more than 2 arguments is deprecated, use either IntlCalendar::setDate() or IntlCalendar::setDateTime() instead in %s on line %d
+IntlCalendar::set(): Argument #1 ($year) must be between -2147483648 and 2147483647
+
+Deprecated: Function intlcal_set() is deprecated since 8.4, use IntlCalendar::set(), IntlCalendar::setDate(), or IntlCalendar::setDateTime() instead in %s on line %d
+intlcal_set(): Argument #3 ($month) must be between -2147483648 and 2147483647
+
+Deprecated: Calling IntlCalendar::set() with more than 2 arguments is deprecated, use either IntlCalendar::setDate() or IntlCalendar::setDateTime() instead in %s on line %d
+IntlCalendar::set(): Argument #4 ($hour) must be between -2147483648 and 2147483647
+
+Deprecated: Calling IntlCalendar::set() with more than 2 arguments is deprecated, use either IntlCalendar::setDate() or IntlCalendar::setDateTime() instead in %s on line %d
+IntlCalendar::set(): Argument #5 ($minute) must be between -2147483648 and 2147483647
+
+Deprecated: Function intlcal_set() is deprecated since 8.4, use IntlCalendar::set(), IntlCalendar::setDate(), or IntlCalendar::setDateTime() instead in %s on line %d
+intlcal_set(): Argument #7 ($second) must be between -2147483648 and 2147483647
diff --git a/ext/intl/tests/gregoriancalendar___construct_out_of_bounds.phpt b/ext/intl/tests/gregoriancalendar___construct_out_of_bounds.phpt
new file mode 100644
index 00000000000..5b084b25d86
--- /dev/null
+++ b/ext/intl/tests/gregoriancalendar___construct_out_of_bounds.phpt
@@ -0,0 +1,53 @@
+--TEST--
+IntlGregorianCalendar::__construct(): out-of-bounds date/time arguments report correct positions
+--EXTENSIONS--
+intl
+--SKIPIF--
+<?php if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ?>
+--FILE--
+<?php
+try {
+    new IntlGregorianCalendar(99999999999, 1, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    intlgregcal_create_instance(1, 99999999999, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    new IntlGregorianCalendar(1, 1, 1, 99999999999, 1);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    new IntlGregorianCalendar(1, 1, 1, 1, 99999999999);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+
+try {
+    intlgregcal_create_instance(1, 1, 1, 1, 1, 99999999999);
+} catch (Throwable $e) {
+    echo $e->getMessage(), "\n";
+}
+?>
+--EXPECTF--
+Deprecated: Calling IntlGregorianCalendar::__construct() with more than 2 arguments is deprecated, use either IntlGregorianCalendar::createFromDate() or IntlGregorianCalendar::createFromDateTime() instead in %s on line %d
+IntlGregorianCalendar::__construct(): Argument #1 ($timezoneOrYear) must be between -2147483648 and 2147483647
+
+Deprecated: Function intlgregcal_create_instance() is deprecated since 8.4, use IntlGregorianCalendar::__construct(), IntlGregorianCalendar::createFromDate(), or IntlGregorianCalendar::createFromDateTime() instead in %s on line %d
+intlgregcal_create_instance(): Argument #2 ($localeOrMonth) must be between -2147483648 and 2147483647
+
+Deprecated: Calling IntlGregorianCalendar::__construct() with more than 2 arguments is deprecated, use either IntlGregorianCalendar::createFromDate() or IntlGregorianCalendar::createFromDateTime() instead in %s on line %d
+IntlGregorianCalendar::__construct(): Argument #4 ($hour) must be between -2147483648 and 2147483647
+
+Deprecated: Calling IntlGregorianCalendar::__construct() with more than 2 arguments is deprecated, use either IntlGregorianCalendar::createFromDate() or IntlGregorianCalendar::createFromDateTime() instead in %s on line %d
+IntlGregorianCalendar::__construct(): Argument #5 ($minute) must be between -2147483648 and 2147483647
+
+Deprecated: Function intlgregcal_create_instance() is deprecated since 8.4, use IntlGregorianCalendar::__construct(), IntlGregorianCalendar::createFromDate(), or IntlGregorianCalendar::createFromDateTime() instead in %s on line %d
+intlgregcal_create_instance(): Argument #6 ($second) must be between -2147483648 and 2147483647