Commit 0d7a0a4703b for php.net
commit 0d7a0a4703ba6fd35d5ae9317a8c24b1bcc6a70d
Author: Weilin Du <108666168+LamentXU123@users.noreply.github.com>
Date: Sat May 9 23:55:26 2026 +0800
ext/intl: Throw TypeError for non-stringable timezone objects (#21990)
diff --git a/NEWS b/NEWS
index 4ace7d3dc46..4f8fe29d53e 100644
--- a/NEWS
+++ b/NEWS
@@ -62,6 +62,8 @@ PHP NEWS
. Fixed bug GH-20426 (Spoofchecker::setRestrictionLevel() error message
suggests missing constants). (DanielEScherzer)
. Added grapheme_strrev (Yuya Hamada)
+ . Passing a non-stringable object as a time zone to Intl time zone
+ argument handling now raises TypeError instead of Error. (Weilin Du)
- JSON:
. Enriched JSON last error / exception message with error location.
diff --git a/UPGRADING b/UPGRADING
index d271eb47f7d..f4853af485f 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -34,6 +34,10 @@ PHP 8.6 UPGRADE NOTES
array arguments types/values and raise a TypeError/ValueError
accordingly.
+- Intl:
+ . Passing a non-stringable object as a time zone to Intl APIs that accept
+ time zone objects or strings now raises a TypeError instead of an Error.
+
- PCNTL:
. pcntl_alarm() now raises a ValueError if the seconds argument is
lower than zero or greater than platform's UINT_MAX.
diff --git a/ext/intl/calendar/calendar_methods.cpp b/ext/intl/calendar/calendar_methods.cpp
index 4828c641730..08e44680693 100644
--- a/ext/intl/calendar/calendar_methods.cpp
+++ b/ext/intl/calendar/calendar_methods.cpp
@@ -85,7 +85,7 @@ U_CFUNC PHP_FUNCTION(intlcal_create_instance)
Z_PARAM_STRING_OR_NULL(locale_str, locale_len)
ZEND_PARSE_PARAMETERS_END();
- TimeZone *timeZone = timezone_process_timezone_argument(timezone_object, timezone_string, nullptr);
+ TimeZone *timeZone = timezone_process_timezone_argument(timezone_object, timezone_string, nullptr, 1);
if (timeZone == nullptr) {
RETURN_NULL();
}
@@ -316,7 +316,7 @@ U_CFUNC PHP_FUNCTION(intlcal_set_time_zone)
}
TimeZone *timeZone = timezone_process_timezone_argument(
- timezone_object, timezone_string, CALENDAR_ERROR_P(co));
+ timezone_object, timezone_string, CALENDAR_ERROR_P(co), 2);
if (timeZone == nullptr) {
RETURN_FALSE;
}
@@ -345,7 +345,7 @@ U_CFUNC PHP_METHOD(IntlCalendar, setTimeZone)
}
TimeZone *timeZone = timezone_process_timezone_argument(
- timezone_object, timezone_string, CALENDAR_ERROR_P(co));
+ timezone_object, timezone_string, CALENDAR_ERROR_P(co), 1);
if (timeZone == nullptr) {
RETURN_FALSE;
}
diff --git a/ext/intl/calendar/gregoriancalendar_methods.cpp b/ext/intl/calendar/gregoriancalendar_methods.cpp
index b94548b54a6..f79c6bf14c8 100644
--- a/ext/intl/calendar/gregoriancalendar_methods.cpp
+++ b/ext/intl/calendar/gregoriancalendar_methods.cpp
@@ -143,7 +143,7 @@ static void _php_intlgregcal_constructor_body(INTERNAL_FUNCTION_PARAMETERS, bool
if (variant <= 2) {
// From timezone and locale (0 to 2 arguments)
- TimeZone *tz = timezone_process_timezone_argument(timezone_object, timezone_string, nullptr);
+ TimeZone *tz = timezone_process_timezone_argument(timezone_object, timezone_string, nullptr, 1);
if (tz == nullptr) {
// TODO: Exception should always occur already?
if (!EG(exception)) {
diff --git a/ext/intl/dateformat/dateformat_attrcpp.cpp b/ext/intl/dateformat/dateformat_attrcpp.cpp
index 1391823ff0f..1c8fb88a3d5 100644
--- a/ext/intl/dateformat/dateformat_attrcpp.cpp
+++ b/ext/intl/dateformat/dateformat_attrcpp.cpp
@@ -94,7 +94,7 @@ U_CFUNC PHP_FUNCTION(datefmt_set_timezone)
DATE_FORMAT_METHOD_FETCH_OBJECT;
TimeZone *timezone = timezone_process_timezone_argument(
- timezone_object, timezone_string, INTL_DATA_ERROR_P(dfo));
+ timezone_object, timezone_string, INTL_DATA_ERROR_P(dfo), 2);
if (timezone == nullptr) {
RETURN_FALSE;
}
@@ -119,7 +119,7 @@ U_CFUNC PHP_METHOD(IntlDateFormatter, setTimeZone)
DATE_FORMAT_METHOD_FETCH_OBJECT;
TimeZone *timezone = timezone_process_timezone_argument(
- timezone_object, timezone_string, INTL_DATA_ERROR_P(dfo));
+ timezone_object, timezone_string, INTL_DATA_ERROR_P(dfo), 1);
if (timezone == nullptr) {
RETURN_FALSE;
}
diff --git a/ext/intl/dateformat/dateformat_create.cpp b/ext/intl/dateformat/dateformat_create.cpp
index 4b055fc88eb..574e00099d4 100644
--- a/ext/intl/dateformat/dateformat_create.cpp
+++ b/ext/intl/dateformat/dateformat_create.cpp
@@ -131,7 +131,7 @@ static zend_result datefmt_ctor(INTERNAL_FUNCTION_PARAMETERS)
if (explicit_tz || calendar_owned ) {
//we have an explicit time zone or a non-object calendar
- timezone = timezone_process_timezone_argument(timezone_object, timezone_string, INTL_DATA_ERROR_P(dfo));
+ timezone = timezone_process_timezone_argument(timezone_object, timezone_string, INTL_DATA_ERROR_P(dfo), 4);
if (timezone == nullptr) {
goto error;
}
diff --git a/ext/intl/msgformat/msgformat_helpers.cpp b/ext/intl/msgformat/msgformat_helpers.cpp
index 399db1e8c0b..f504ee50abc 100644
--- a/ext/intl/msgformat/msgformat_helpers.cpp
+++ b/ext/intl/msgformat/msgformat_helpers.cpp
@@ -341,7 +341,7 @@ static void umsg_set_timezone(MessageFormatter_object *mfo,
}
if (used_tz == NULL) {
- used_tz = timezone_process_timezone_argument(nullptr, nullptr, &err);
+ used_tz = timezone_process_timezone_argument(nullptr, nullptr, &err, 1);
if (used_tz == NULL) {
continue;
}
diff --git a/ext/intl/tests/timezone_argument_type_errors.phpt b/ext/intl/tests/timezone_argument_type_errors.phpt
new file mode 100644
index 00000000000..02e1c58492c
--- /dev/null
+++ b/ext/intl/tests/timezone_argument_type_errors.phpt
@@ -0,0 +1,42 @@
+--TEST--
+Intl timezone argument APIs reject non-stringable objects with TypeError
+--EXTENSIONS--
+intl
+--FILE--
+<?php
+
+function dump_exception(callable $cb): void {
+ try {
+ $cb();
+ } catch (Throwable $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+ }
+}
+
+$std = new stdClass();
+$calendar = IntlCalendar::createInstance();
+$formatter = new IntlDateFormatter(null, IntlDateFormatter::NONE, IntlDateFormatter::NONE);
+
+dump_exception(fn() => intlcal_create_instance($std));
+dump_exception(fn() => IntlCalendar::createInstance($std));
+dump_exception(fn() => intlcal_set_time_zone($calendar, $std));
+dump_exception(fn() => $calendar->setTimeZone($std));
+dump_exception(fn() => new IntlGregorianCalendar($std));
+dump_exception(fn() => datefmt_create(null, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $std));
+dump_exception(fn() => IntlDateFormatter::create(null, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $std));
+dump_exception(fn() => new IntlDateFormatter(null, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $std));
+dump_exception(fn() => datefmt_set_timezone($formatter, $std));
+dump_exception(fn() => $formatter->setTimeZone($std));
+
+?>
+--EXPECT--
+TypeError: intlcal_create_instance(): Argument #1 ($timezone) Object of class stdClass could not be converted to string
+TypeError: IntlCalendar::createInstance(): Argument #1 ($timezone) Object of class stdClass could not be converted to string
+TypeError: intlcal_set_time_zone(): Argument #2 ($timezone) Object of class stdClass could not be converted to string
+TypeError: IntlCalendar::setTimeZone(): Argument #1 ($timezone) Object of class stdClass could not be converted to string
+TypeError: IntlGregorianCalendar::__construct(): Argument #1 ($timezoneOrYear) Object of class stdClass could not be converted to string
+TypeError: datefmt_create(): Argument #4 ($timezone) Object of class stdClass could not be converted to string
+TypeError: IntlDateFormatter::create(): Argument #4 ($timezone) Object of class stdClass could not be converted to string
+TypeError: IntlDateFormatter::__construct(): Argument #4 ($timezone) Object of class stdClass could not be converted to string
+TypeError: datefmt_set_timezone(): Argument #2 ($timezone) Object of class stdClass could not be converted to string
+TypeError: IntlDateFormatter::setTimeZone(): Argument #1 ($timezone) Object of class stdClass could not be converted to string
diff --git a/ext/intl/timezone/timezone_class.cpp b/ext/intl/timezone/timezone_class.cpp
index 8aad4fb2b6b..0d29a47c186 100644
--- a/ext/intl/timezone/timezone_class.cpp
+++ b/ext/intl/timezone/timezone_class.cpp
@@ -121,7 +121,7 @@ static void timezone_throw_exception_with_call_location(const char *msg, const c
/* {{{ timezone_process_timezone_argument
* TimeZone argument processor. outside_error may be nullptr (for static functions/constructors) */
U_CFUNC TimeZone *timezone_process_timezone_argument(
- zend_object *timezone_object, zend_string *timezone_string, intl_error *outside_error)
+ zend_object *timezone_object, zend_string *timezone_string, intl_error *outside_error, uint32_t arg_num)
{
std::unique_ptr<TimeZone> timeZone;
bool free_string = false;
@@ -160,8 +160,9 @@ U_CFUNC TimeZone *timezone_process_timezone_argument(
free_string = true;
} else {
if (!EG(exception)) {
- // TODO Proper type error
- zend_throw_error(nullptr, "Object of class %s could not be converted to string", ZSTR_VAL(timezone_object->ce->name));
+ zend_argument_type_error(arg_num,
+ "Object of class %s could not be converted to string",
+ ZSTR_VAL(timezone_object->ce->name));
}
return nullptr;
}
diff --git a/ext/intl/timezone/timezone_class.h b/ext/intl/timezone/timezone_class.h
index b383db558fc..70b3b5637f2 100644
--- a/ext/intl/timezone/timezone_class.h
+++ b/ext/intl/timezone/timezone_class.h
@@ -67,7 +67,7 @@ static inline TimeZone_object *php_intl_timezone_fetch_object(zend_object *obj)
}
zval *timezone_convert_to_datetimezone(const TimeZone *timeZone, intl_error *outside_error, zval *ret);
-TimeZone *timezone_process_timezone_argument(zend_object *timezone_object, zend_string *timezone_string, intl_error *error);
+TimeZone *timezone_process_timezone_argument(zend_object *timezone_object, zend_string *timezone_string, intl_error *error, uint32_t arg_num);
void timezone_object_construct(const TimeZone *zone, zval *object, int owned);