From b16f89ed23b481b1cada25e0ef0797054228d6ab Mon Sep 17 00:00:00 2001 From: lamentxu <1372449351@qq.com> Date: Wed, 6 May 2026 09:09:34 +0800 Subject: [PATCH 1/5] ext/intl: Allow numeric-castable objects as date/time values in IntlDateFormatter::formatObject() --- NEWS | 2 ++ UPGRADING | 5 +++++ ext/intl/common/common_date.cpp | 21 +++++++++++++++---- ...ateformat_formatObject_numeric_object.phpt | 20 ++++++++++++++++++ 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 ext/intl/tests/dateformat_formatObject_numeric_object.phpt diff --git a/NEWS b/NEWS index 4ace7d3dc462..38295c64df1c 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) + . IntlDateFormatter::formatObject() now accepts numeric-castable objects as + date/time values. (Weilin Du) - JSON: . Enriched JSON last error / exception message with error location. diff --git a/UPGRADING b/UPGRADING index d271eb47f7d1..8b7c0f5d6a93 100644 --- a/UPGRADING +++ b/UPGRADING @@ -200,6 +200,11 @@ PHP 8.6 UPGRADE NOTES . gmp_powm() modulo-by-zero now raises a DivisionByZeroError whose message includes the function name and argument index ($modulus). +- Intl: + . IntlDateFormatter::formatObject() now accepts objects that support + numeric casting as date/time values, in addition to IntlCalendar and + DateTimeInterface instances. + - mysqli: . The return structure of mysqli_get_charset() no longer contains the undocumented "comment" element. The value of "charsetnr" is diff --git a/ext/intl/common/common_date.cpp b/ext/intl/common/common_date.cpp index 0a7aa023b43c..cbc4d4df04cd 100644 --- a/ext/intl/common/common_date.cpp +++ b/ext/intl/common/common_date.cpp @@ -202,10 +202,23 @@ U_CFUNC double intl_zval_to_millis(zval *z, intl_error *err) } } } else { - /* TODO: try with cast(), get() to obtain a number */ - intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR, - "invalid object type for date/time " - "(only IntlCalendar and DateTimeInterface permitted)"); + zval casted; + ZVAL_UNDEF(&casted); + + if (Z_OBJ_HT_P(z)->cast_object(Z_OBJ_P(z), &casted, _IS_NUMBER) == SUCCESS) { + if (Z_TYPE(casted) == IS_LONG) { + rv = U_MILLIS_PER_SECOND * (double)Z_LVAL(casted); + } else if (Z_TYPE(casted) == IS_DOUBLE) { + rv = U_MILLIS_PER_SECOND * Z_DVAL(casted); + } else { + intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR, + "invalid object type for date/time"); + } + zval_ptr_dtor(&casted); + } else { + intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR, + "invalid object type for date/time "); + } } break; case IS_REFERENCE: diff --git a/ext/intl/tests/dateformat_formatObject_numeric_object.phpt b/ext/intl/tests/dateformat_formatObject_numeric_object.phpt new file mode 100644 index 000000000000..0e0c1869f7ec --- /dev/null +++ b/ext/intl/tests/dateformat_formatObject_numeric_object.phpt @@ -0,0 +1,20 @@ +--TEST-- +IntlDateFormatter::formatObject(): numeric-castable objects +--EXTENSIONS-- +intl +zend_test +--INI-- +intl.default_locale=en_US +date.timezone=UTC +--FILE-- + +--EXPECT-- +1970-01-01 00:00:00.000 +1970-01-01 00:00:00.500 From 96c2522c72bc73c22ac680a2ab56bc18c7359b52 Mon Sep 17 00:00:00 2001 From: lamentxu <1372449351@qq.com> Date: Wed, 6 May 2026 09:32:37 +0800 Subject: [PATCH 2/5] add exception catch, dtor pointers and fix CI --- NEWS | 2 +- UPGRADING | 5 ++--- ext/intl/common/common_date.cpp | 9 ++++++--- ext/intl/tests/dateformat_formatObject_error.phpt | 2 +- .../tests/dateformat_formatObject_numeric_object.phpt | 8 ++++---- ext/intl/tests/dateformat_format_error.phpt | 6 +++--- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/NEWS b/NEWS index 38295c64df1c..756fd78bb8ac 100644 --- a/NEWS +++ b/NEWS @@ -62,7 +62,7 @@ PHP NEWS . Fixed bug GH-20426 (Spoofchecker::setRestrictionLevel() error message suggests missing constants). (DanielEScherzer) . Added grapheme_strrev (Yuya Hamada) - . IntlDateFormatter::formatObject() now accepts numeric-castable objects as + . IntlDateFormatter::format() now accepts numeric-castable objects as date/time values. (Weilin Du) - JSON: diff --git a/UPGRADING b/UPGRADING index 8b7c0f5d6a93..297654e7235a 100644 --- a/UPGRADING +++ b/UPGRADING @@ -201,9 +201,8 @@ PHP 8.6 UPGRADE NOTES message includes the function name and argument index ($modulus). - Intl: - . IntlDateFormatter::formatObject() now accepts objects that support - numeric casting as date/time values, in addition to IntlCalendar and - DateTimeInterface instances. + . IntlDateFormatter::format() now accepts objects that support numeric + casting as date/time values. - mysqli: . The return structure of mysqli_get_charset() no longer contains diff --git a/ext/intl/common/common_date.cpp b/ext/intl/common/common_date.cpp index cbc4d4df04cd..24e1681d4b72 100644 --- a/ext/intl/common/common_date.cpp +++ b/ext/intl/common/common_date.cpp @@ -205,7 +205,8 @@ U_CFUNC double intl_zval_to_millis(zval *z, intl_error *err) zval casted; ZVAL_UNDEF(&casted); - if (Z_OBJ_HT_P(z)->cast_object(Z_OBJ_P(z), &casted, _IS_NUMBER) == SUCCESS) { + if (Z_OBJ_HT_P(z)->cast_object(Z_OBJ_P(z), &casted, _IS_NUMBER) == SUCCESS + && !EG(exception)) { if (Z_TYPE(casted) == IS_LONG) { rv = U_MILLIS_PER_SECOND * (double)Z_LVAL(casted); } else if (Z_TYPE(casted) == IS_DOUBLE) { @@ -214,10 +215,12 @@ U_CFUNC double intl_zval_to_millis(zval *z, intl_error *err) intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR, "invalid object type for date/time"); } - zval_ptr_dtor(&casted); } else { intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR, - "invalid object type for date/time "); + "invalid object type for date/time"); + } + if (!Z_ISUNDEF(casted)) { + zval_ptr_dtor(&casted); } } break; diff --git a/ext/intl/tests/dateformat_formatObject_error.phpt b/ext/intl/tests/dateformat_formatObject_error.phpt index b38a379b9f5a..4569c1ac1d90 100644 --- a/ext/intl/tests/dateformat_formatObject_error.phpt +++ b/ext/intl/tests/dateformat_formatObject_error.phpt @@ -40,7 +40,7 @@ var_dump(IntlDateFormatter::formatObject($cal, "")); var_dump(intl_get_error_message()); ?> ---EXPECT-- +--EXPECTF-- DateObjectError: Object of type B (inheriting DateTime) has not been correctly initialized by calling parent::__construct() in its constructor bool(false) string(130) "IntlDateFormatter::formatObject(): the passed object must be an instance of either IntlCalendar or DateTimeInterface: U_ZERO_ERROR" diff --git a/ext/intl/tests/dateformat_formatObject_numeric_object.phpt b/ext/intl/tests/dateformat_formatObject_numeric_object.phpt index 0e0c1869f7ec..564daba71e8d 100644 --- a/ext/intl/tests/dateformat_formatObject_numeric_object.phpt +++ b/ext/intl/tests/dateformat_formatObject_numeric_object.phpt @@ -1,5 +1,5 @@ --TEST-- -IntlDateFormatter::formatObject(): numeric-castable objects +IntlDateFormatter->format(): numeric-castable objects --EXTENSIONS-- intl zend_test @@ -9,10 +9,10 @@ date.timezone=UTC --FILE-- format(new NumericCastableNoOperations(0)), "\n"; +echo datefmt_format($fmt, new NumericCastableNoOperations(0.5)), "\n"; ?> --EXPECT-- diff --git a/ext/intl/tests/dateformat_format_error.phpt b/ext/intl/tests/dateformat_format_error.phpt index 4358271efd08..234e96280ea0 100644 --- a/ext/intl/tests/dateformat_format_error.phpt +++ b/ext/intl/tests/dateformat_format_error.phpt @@ -19,8 +19,8 @@ var_dump($v); var_dump(intl_get_error_message()); ?> ---EXPECT-- +--EXPECTF-- bool(false) -string(140) "IntlDateFormatter::format(): invalid object type for date/time (only IntlCalendar and DateTimeInterface permitted): U_ILLEGAL_ARGUMENT_ERROR" +string(%d) "IntlDateFormatter::format(): invalid object type for date/time: U_ILLEGAL_ARGUMENT_ERROR" bool(false) -string(129) "datefmt_format(): invalid object type for date/time (only IntlCalendar and DateTimeInterface permitted): U_ILLEGAL_ARGUMENT_ERROR" +string(%d) "datefmt_format(): invalid object type for date/time: U_ILLEGAL_ARGUMENT_ERROR" From 563684acd7c0347fce9f72a47b70b6ddb8e926dd Mon Sep 17 00:00:00 2001 From: lamentxu <1372449351@qq.com> Date: Wed, 6 May 2026 09:39:09 +0800 Subject: [PATCH 3/5] better error message --- ext/intl/common/common_date.cpp | 6 ++++-- ext/intl/tests/dateformat_formatObject_error.phpt | 2 +- ext/intl/tests/dateformat_format_error.phpt | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ext/intl/common/common_date.cpp b/ext/intl/common/common_date.cpp index 24e1681d4b72..87e7ac666a43 100644 --- a/ext/intl/common/common_date.cpp +++ b/ext/intl/common/common_date.cpp @@ -213,11 +213,13 @@ U_CFUNC double intl_zval_to_millis(zval *z, intl_error *err) rv = U_MILLIS_PER_SECOND * Z_DVAL(casted); } else { intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR, - "invalid object type for date/time"); + "invalid object type for date/time " + "(IntlCalendar, DateTimeInterface, or numeric-castable object permitted)"); } } else { intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR, - "invalid object type for date/time"); + "invalid object type for date/time " + "(IntlCalendar, DateTimeInterface, or numeric-castable object permitted)"); } if (!Z_ISUNDEF(casted)) { zval_ptr_dtor(&casted); diff --git a/ext/intl/tests/dateformat_formatObject_error.phpt b/ext/intl/tests/dateformat_formatObject_error.phpt index 4569c1ac1d90..b38a379b9f5a 100644 --- a/ext/intl/tests/dateformat_formatObject_error.phpt +++ b/ext/intl/tests/dateformat_formatObject_error.phpt @@ -40,7 +40,7 @@ var_dump(IntlDateFormatter::formatObject($cal, "")); var_dump(intl_get_error_message()); ?> ---EXPECTF-- +--EXPECT-- DateObjectError: Object of type B (inheriting DateTime) has not been correctly initialized by calling parent::__construct() in its constructor bool(false) string(130) "IntlDateFormatter::formatObject(): the passed object must be an instance of either IntlCalendar or DateTimeInterface: U_ZERO_ERROR" diff --git a/ext/intl/tests/dateformat_format_error.phpt b/ext/intl/tests/dateformat_format_error.phpt index 234e96280ea0..a282841c16ff 100644 --- a/ext/intl/tests/dateformat_format_error.phpt +++ b/ext/intl/tests/dateformat_format_error.phpt @@ -21,6 +21,6 @@ var_dump(intl_get_error_message()); ?> --EXPECTF-- bool(false) -string(%d) "IntlDateFormatter::format(): invalid object type for date/time: U_ILLEGAL_ARGUMENT_ERROR" +string(%d) "IntlDateFormatter::format(): invalid object type for date/time (IntlCalendar, DateTimeInterface, or numeric-castable object permitted): U_ILLEGAL_ARGUMENT_ERROR" bool(false) -string(%d) "datefmt_format(): invalid object type for date/time: U_ILLEGAL_ARGUMENT_ERROR" +string(%d) "datefmt_format(): invalid object type for date/time (IntlCalendar, DateTimeInterface, or numeric-castable object permitted): U_ILLEGAL_ARGUMENT_ERROR" From fdb0a96904fc5cb63536b2a3a9e9d30bc2d705b1 Mon Sep 17 00:00:00 2001 From: lamentxu <1372449351@qq.com> Date: Wed, 6 May 2026 13:15:32 +0800 Subject: [PATCH 4/5] use EG to guard --- ext/intl/common/common_date.cpp | 8 ++++---- .../tests/dateformat_formatObject_numeric_object.phpt | 4 ++++ ext/intl/tests/dateformat_format_error.phpt | 6 +++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ext/intl/common/common_date.cpp b/ext/intl/common/common_date.cpp index 87e7ac666a43..36f0cd18a9c3 100644 --- a/ext/intl/common/common_date.cpp +++ b/ext/intl/common/common_date.cpp @@ -205,9 +205,9 @@ U_CFUNC double intl_zval_to_millis(zval *z, intl_error *err) zval casted; ZVAL_UNDEF(&casted); - if (Z_OBJ_HT_P(z)->cast_object(Z_OBJ_P(z), &casted, _IS_NUMBER) == SUCCESS - && !EG(exception)) { - if (Z_TYPE(casted) == IS_LONG) { + if (Z_OBJ_HT_P(z)->cast_object(Z_OBJ_P(z), &casted, _IS_NUMBER) == SUCCESS) { + if (EG(exception)) { + } else if (Z_TYPE(casted) == IS_LONG) { rv = U_MILLIS_PER_SECOND * (double)Z_LVAL(casted); } else if (Z_TYPE(casted) == IS_DOUBLE) { rv = U_MILLIS_PER_SECOND * Z_DVAL(casted); @@ -216,7 +216,7 @@ U_CFUNC double intl_zval_to_millis(zval *z, intl_error *err) "invalid object type for date/time " "(IntlCalendar, DateTimeInterface, or numeric-castable object permitted)"); } - } else { + } else if (!EG(exception)) { intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR, "invalid object type for date/time " "(IntlCalendar, DateTimeInterface, or numeric-castable object permitted)"); diff --git a/ext/intl/tests/dateformat_formatObject_numeric_object.phpt b/ext/intl/tests/dateformat_formatObject_numeric_object.phpt index 564daba71e8d..578faed93e04 100644 --- a/ext/intl/tests/dateformat_formatObject_numeric_object.phpt +++ b/ext/intl/tests/dateformat_formatObject_numeric_object.phpt @@ -13,8 +13,12 @@ $fmt = new IntlDateFormatter('en_US', IntlDateFormatter::NONE, IntlDateFormatter echo $fmt->format(new NumericCastableNoOperations(0)), "\n"; echo datefmt_format($fmt, new NumericCastableNoOperations(0.5)), "\n"; +var_dump($fmt->format(new stdClass())); +var_dump(intl_get_error_message()); ?> --EXPECT-- 1970-01-01 00:00:00.000 1970-01-01 00:00:00.500 +bool(false) +string(146) "IntlDateFormatter::format(): invalid object type for date/time (IntlCalendar, DateTimeInterface, or numeric-castable object permitted): U_ILLEGAL_ARGUMENT_ERROR" diff --git a/ext/intl/tests/dateformat_format_error.phpt b/ext/intl/tests/dateformat_format_error.phpt index a282841c16ff..e28d3041cbb5 100644 --- a/ext/intl/tests/dateformat_format_error.phpt +++ b/ext/intl/tests/dateformat_format_error.phpt @@ -19,8 +19,8 @@ var_dump($v); var_dump(intl_get_error_message()); ?> ---EXPECTF-- +--EXPECT-- bool(false) -string(%d) "IntlDateFormatter::format(): invalid object type for date/time (IntlCalendar, DateTimeInterface, or numeric-castable object permitted): U_ILLEGAL_ARGUMENT_ERROR" +string(146) "IntlDateFormatter::format(): invalid object type for date/time (IntlCalendar, DateTimeInterface, or numeric-castable object permitted): U_ILLEGAL_ARGUMENT_ERROR" bool(false) -string(%d) "datefmt_format(): invalid object type for date/time (IntlCalendar, DateTimeInterface, or numeric-castable object permitted): U_ILLEGAL_ARGUMENT_ERROR" +string(135) "datefmt_format(): invalid object type for date/time (IntlCalendar, DateTimeInterface, or numeric-castable object permitted): U_ILLEGAL_ARGUMENT_ERROR" From 390793b995576e8e179be01ed51c868dabc4f6f9 Mon Sep 17 00:00:00 2001 From: lamentxu <1372449351@qq.com> Date: Wed, 6 May 2026 13:51:39 +0800 Subject: [PATCH 5/5] fix CI --- ext/intl/tests/dateformat_formatObject_numeric_object.phpt | 2 +- ext/intl/tests/dateformat_format_error.phpt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/intl/tests/dateformat_formatObject_numeric_object.phpt b/ext/intl/tests/dateformat_formatObject_numeric_object.phpt index 578faed93e04..b2a9a02f6bc8 100644 --- a/ext/intl/tests/dateformat_formatObject_numeric_object.phpt +++ b/ext/intl/tests/dateformat_formatObject_numeric_object.phpt @@ -21,4 +21,4 @@ var_dump(intl_get_error_message()); 1970-01-01 00:00:00.000 1970-01-01 00:00:00.500 bool(false) -string(146) "IntlDateFormatter::format(): invalid object type for date/time (IntlCalendar, DateTimeInterface, or numeric-castable object permitted): U_ILLEGAL_ARGUMENT_ERROR" +string(160) "IntlDateFormatter::format(): invalid object type for date/time (IntlCalendar, DateTimeInterface, or numeric-castable object permitted): U_ILLEGAL_ARGUMENT_ERROR" diff --git a/ext/intl/tests/dateformat_format_error.phpt b/ext/intl/tests/dateformat_format_error.phpt index e28d3041cbb5..f83f8250143e 100644 --- a/ext/intl/tests/dateformat_format_error.phpt +++ b/ext/intl/tests/dateformat_format_error.phpt @@ -21,6 +21,6 @@ var_dump(intl_get_error_message()); ?> --EXPECT-- bool(false) -string(146) "IntlDateFormatter::format(): invalid object type for date/time (IntlCalendar, DateTimeInterface, or numeric-castable object permitted): U_ILLEGAL_ARGUMENT_ERROR" +string(160) "IntlDateFormatter::format(): invalid object type for date/time (IntlCalendar, DateTimeInterface, or numeric-castable object permitted): U_ILLEGAL_ARGUMENT_ERROR" bool(false) -string(135) "datefmt_format(): invalid object type for date/time (IntlCalendar, DateTimeInterface, or numeric-castable object permitted): U_ILLEGAL_ARGUMENT_ERROR" +string(149) "datefmt_format(): invalid object type for date/time (IntlCalendar, DateTimeInterface, or numeric-castable object permitted): U_ILLEGAL_ARGUMENT_ERROR"