Commit 52b56eb2543 for php.net

commit 52b56eb2543c9acb9a6ef5169b5fcda098acb833
Author: Weilin Du <weilindu@php.net>
Date:   Sat Jul 4 01:32:47 2026 +0800

    ext/Intl: Fix IntlDateFormatter offset type validation (#22533)

    IntlDateFormatter::parse()/datefmt_parse() and
    IntlDateFormatter::localtime()/datefmt_localtime() now raise a TypeError
    when the offset argument is not of type int instead of silently converting
    the value.

diff --git a/NEWS b/NEWS
index caddbd71749..216f3b2b52b 100644
--- a/NEWS
+++ b/NEWS
@@ -132,6 +132,9 @@ PHP                                                                        NEWS
     locale_get_display_keyword_value() respectively. (Weilin Du)
   . Fix incorrect argument positions for invalid start/end arguments in
     transliterator_transliterate(). (Weilin Du)
+  . IntlDateFormatter::parse()/datefmt_parse() and
+    IntlDateFormatter::localtime()/datefmt_localtime() now raise TypeError
+    when the offset argument is not of type int. (Weilin Du)
   . Fixed IntlTimeZone::getDisplayName() to synchronize object error state
     for invalid display types. (Weilin Du)
   . Fixed Locale::lookup() and locale_lookup() to return NULL instead of the
diff --git a/UPGRADING b/UPGRADING
index eb6d182c54a..a42d5ec355a 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -56,6 +56,10 @@ PHP 8.6 UPGRADE NOTES
   . ResourceBundle::get() and resourcebundle_get() now report fallback-disabled
     resource lookups with "without fallback to <locale>" instead of the
     malformed "without fallback from to <locale>".
+  . IntlDateFormatter::parse()/datefmt_parse() and
+    IntlDateFormatter::localtime()/datefmt_localtime() now raise a TypeError
+    when the offset argument is not of type int instead of silently converting
+    the value.

 - PCNTL:
   . pcntl_alarm() now raises a ValueError if the seconds argument is
diff --git a/ext/intl/dateformat/dateformat_parse.cpp b/ext/intl/dateformat/dateformat_parse.cpp
index d818627439e..0537b42feec 100644
--- a/ext/intl/dateformat/dateformat_parse.cpp
+++ b/ext/intl/dateformat/dateformat_parse.cpp
@@ -147,9 +147,12 @@ U_CFUNC PHP_FUNCTION(datefmt_parse)
 	DATE_FORMAT_METHOD_FETCH_OBJECT;

 	if (z_parse_pos) {
-		zval *z_parse_pos_tmp = z_parse_pos;
-		ZVAL_DEREF(z_parse_pos_tmp);
-		const zend_long long_parse_pos = zval_get_long(z_parse_pos_tmp);
+		bool failed;
+		const zend_long long_parse_pos = zval_try_get_long(z_parse_pos, &failed);
+		if (failed) {
+			zend_argument_type_error(hasThis() ? 2 : 3, "must be of type int, %s given", zend_zval_value_name(z_parse_pos));
+			RETURN_THROWS();
+		}
 		if (ZEND_LONG_INT_OVFL(long_parse_pos)) {
 			intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR);
 			intl_error_set_custom_msg(NULL, "String index is out of valid range.");
@@ -229,9 +232,12 @@ U_CFUNC PHP_FUNCTION(datefmt_localtime)
 	DATE_FORMAT_METHOD_FETCH_OBJECT;

 	if (z_parse_pos) {
-		zval *z_parse_pos_tmp = z_parse_pos;
-		ZVAL_DEREF(z_parse_pos_tmp);
-		const zend_long long_parse_pos = zval_get_long(z_parse_pos_tmp);
+		bool failed;
+		const zend_long long_parse_pos = zval_try_get_long(z_parse_pos, &failed);
+		if (failed) {
+			zend_argument_type_error(hasThis() ? 2 : 3, "must be of type int, %s given", zend_zval_value_name(z_parse_pos));
+			RETURN_THROWS();
+		}
 		if (ZEND_LONG_INT_OVFL(long_parse_pos)) {
 			intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR);
 			intl_error_set_custom_msg(NULL, "String index is out of valid range.");
diff --git a/ext/intl/tests/datefmt_parse_localtime_offset_type_error.phpt b/ext/intl/tests/datefmt_parse_localtime_offset_type_error.phpt
new file mode 100644
index 00000000000..5c3d03cd4f6
--- /dev/null
+++ b/ext/intl/tests/datefmt_parse_localtime_offset_type_error.phpt
@@ -0,0 +1,44 @@
+--TEST--
+datefmt_parse() and datefmt_localtime() validate offset type
+--EXTENSIONS--
+intl
+--FILE--
+<?php
+
+$fmt = new IntlDateFormatter('en_GB');
+$fmt->setPattern('VV');
+
+$offset = 'offset';
+try {
+    $fmt->parse('America/Los_Angeles', $offset);
+} catch (TypeError $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+$offset = 'offset';
+try {
+    datefmt_parse($fmt, 'America/Los_Angeles', $offset);
+} catch (TypeError $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+$offset = 'offset';
+try {
+    $fmt->localtime('America/Los_Angeles', $offset);
+} catch (TypeError $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+$offset = 'offset';
+try {
+    datefmt_localtime($fmt, 'America/Los_Angeles', $offset);
+} catch (TypeError $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+IntlDateFormatter::parse(): Argument #2 ($offset) must be of type int, string given
+datefmt_parse(): Argument #3 ($offset) must be of type int, string given
+IntlDateFormatter::localtime(): Argument #2 ($offset) must be of type int, string given
+datefmt_localtime(): Argument #3 ($offset) must be of type int, string given