diff --git a/ext/intl/CREDITS b/ext/intl/CREDITS index 0e7fd55dc12e..1903b6d2396d 100644 --- a/ext/intl/CREDITS +++ b/ext/intl/CREDITS @@ -1,2 +1,2 @@ Internationalization -Ed Batutis, Vladimir Iordanov, Dmitry Lakhtyuk, Stanislav Malyshev, Vadim Savchuk, Kirti Velankar +Ed Batutis, Vladimir Iordanov, Dmitry Lakhtyuk, Stanislav Malyshev, Vadim Savchuk, Kirti Velankar, Weilin Du diff --git a/ext/intl/config.m4 b/ext/intl/config.m4 index 62ea18c4470d..9ea8069c6755 100644 --- a/ext/intl/config.m4 +++ b/ext/intl/config.m4 @@ -73,6 +73,7 @@ if test "$PHP_INTL" != "no"; then breakiterator/codepointiterator_internal.cpp \ breakiterator/codepointiterator_methods.cpp \ listformatter/listformatter_class.cpp \ + reldateformatter/reldateformatter_class.cpp \ transliterator/transliterator_class.cpp \ transliterator/transliterator_methods.cpp \ idn/idn.cpp \ @@ -123,6 +124,7 @@ if test "$PHP_INTL" != "no"; then $ext_builddir/idn $ext_builddir/locale $ext_builddir/listformatter + $ext_builddir/reldateformatter $ext_builddir/msgformat $ext_builddir/normalizer $ext_builddir/rangeformatter diff --git a/ext/intl/config.w32 b/ext/intl/config.w32 index fb30953697b6..2372fe453959 100644 --- a/ext/intl/config.w32 +++ b/ext/intl/config.w32 @@ -41,6 +41,9 @@ if (PHP_INTL != "no") { ADD_SOURCES(configure_module_dirname + "/listformatter", "\ listformatter_class.cpp \ ", "intl"); + ADD_SOURCES(configure_module_dirname + "/reldateformatter", "\ + reldateformatter_class.cpp \ + ", "intl"); ADD_SOURCES(configure_module_dirname + "/locale", "\ locale.cpp \ locale_class.cpp \ diff --git a/ext/intl/php_intl.c b/ext/intl/php_intl.c index 7bc599728e68..c84a7ac3c1cf 100644 --- a/ext/intl/php_intl.c +++ b/ext/intl/php_intl.c @@ -42,6 +42,7 @@ #include "locale/locale_class.h" #include "listformatter/listformatter_class.h" +#include "reldateformatter/reldateformatter_class.h" #include "rangeformatter/rangeformatter_class.h" #include "dateformat/dateformat.h" @@ -190,6 +191,9 @@ PHP_MINIT_FUNCTION( intl ) /* Register 'ListFormatter' PHP class */ listformatter_register_class( ); + /* Register 'IntlRelativeDateTimeFormatter' PHP class */ + reldateformatter_register_class( ); + #if U_ICU_VERSION_MAJOR_NUM >= 63 /* Register 'NumberRangeFormatter' PHP class */ rangeformatter_register_class( ); diff --git a/ext/intl/reldateformatter/reldateformatter.stub.php b/ext/intl/reldateformatter/reldateformatter.stub.php new file mode 100644 index 000000000000..1232fe3ee254 --- /dev/null +++ b/ext/intl/reldateformatter/reldateformatter.stub.php @@ -0,0 +1,96 @@ +. | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Weilin Du | + +----------------------------------------------------------------------+ + */ + +extern "C" { +#include "php.h" +#include "zend_exceptions.h" +} + +#include "../formatter/formatter_class.h" + +#include +#include +#include +#include +#include + +#include + +extern "C" { +#include "../php_intl.h" +#include "../intl_data.h" +#include "../intl_error.h" +} + +#include "../intl_convertcpp.h" +#include "reldateformatter_class.h" +#include "reldateformatter_arginfo.h" + +using icu::Locale; +using icu::UnicodeString; + +zend_class_entry *IntlRelativeDateTimeFormatter_ce_ptr; +static zend_object_handlers reldateformatter_handlers; + +static void reldateformatter_free_object(zend_object *object) +{ + IntlRelativeDateTimeFormatter_object *obj = php_intl_reldateformatter_fetch_object(object); + + if (RELDATEFORMATTER_OBJECT(obj) != nullptr) { + ureldatefmt_close(RELDATEFORMATTER_OBJECT(obj)); + RELDATEFORMATTER_OBJECT(obj) = nullptr; + } + intl_error_reset(RELDATEFORMATTER_ERROR_P(obj)); + + zend_object_std_dtor(&obj->zo); +} + +static zend_object *reldateformatter_create_object(zend_class_entry *class_type) +{ + IntlRelativeDateTimeFormatter_object *obj = reinterpret_cast( + zend_object_alloc(sizeof(IntlRelativeDateTimeFormatter_object), class_type)); + + intl_error_init(RELDATEFORMATTER_ERROR_P(obj)); + RELDATEFORMATTER_OBJECT(obj) = nullptr; + + zend_object_std_init(&obj->zo, class_type); + object_properties_init(&obj->zo, class_type); + obj->zo.handlers = &reldateformatter_handlers; + + return &obj->zo; +} + +static bool reldateformatter_valid_style(zend_long style) +{ + return style == UDAT_STYLE_LONG || style == UDAT_STYLE_SHORT || style == UDAT_STYLE_NARROW; +} + +static bool reldateformatter_valid_capitalization_context(zend_long context) +{ + return context == UDISPCTX_CAPITALIZATION_NONE + || context == UDISPCTX_CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE + || context == UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE + || context == UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU + || context == UDISPCTX_CAPITALIZATION_FOR_STANDALONE; +} + +static bool reldateformatter_valid_unit(zend_long unit) +{ + return unit >= UDAT_REL_UNIT_YEAR && unit <= UDAT_REL_UNIT_SATURDAY; +} + +static void reldateformatter_throw_constructor_failure( + IntlRelativeDateTimeFormatter_object *obj, + UErrorCode status, + const char *message +) +{ + const bool old_use_exceptions = INTL_G(use_exceptions); + const zend_long old_error_level = INTL_G(error_level); + INTL_G(use_exceptions) = true; + INTL_G(error_level) = 0; + + intl_errors_set(RELDATEFORMATTER_ERROR_P(obj), status, message); + + INTL_G(use_exceptions) = old_use_exceptions; + INTL_G(error_level) = old_error_level; +} + +PHP_METHOD(IntlRelativeDateTimeFormatter, __construct) +{ + IntlRelativeDateTimeFormatter_object *obj = Z_INTL_RELDATEFORMATTER_P(ZEND_THIS); + char *locale = nullptr; + size_t locale_len = 0; + zend_long style = UDAT_STYLE_LONG; + zend_long capitalization_context = UDISPCTX_CAPITALIZATION_NONE; + zval *number_formatter = nullptr; + + ZEND_PARSE_PARAMETERS_START(0, 4) + Z_PARAM_OPTIONAL + Z_PARAM_STRING_OR_NULL(locale, locale_len) + Z_PARAM_LONG(style) + Z_PARAM_LONG(capitalization_context) + Z_PARAM_OBJECT_OF_CLASS_OR_NULL(number_formatter, NumberFormatter_ce_ptr) + ZEND_PARSE_PARAMETERS_END(); + + intl_errors_reset(RELDATEFORMATTER_ERROR_P(obj)); + + if (RELDATEFORMATTER_OBJECT(obj) != nullptr) { + zend_throw_error(NULL, "IntlRelativeDateTimeFormatter object is already constructed"); + RETURN_THROWS(); + } + + if (UNEXPECTED(locale_len > INTL_MAX_LOCALE_LEN)) { + zend_argument_value_error(1, "must be less than or equal to %d characters", INTL_MAX_LOCALE_LEN); + RETURN_THROWS(); + } + + if (locale == nullptr || locale_len == 0) { + locale = const_cast(intl_locale_get_default()); + } + + if (Locale(locale).getISO3Language()[0] == '\0') { + zend_argument_value_error(1, "\"%s\" is invalid", locale); + RETURN_THROWS(); + } + + if (!reldateformatter_valid_style(style)) { + zend_argument_value_error(2, + "must be one of IntlRelativeDateTimeFormatter::STYLE_LONG, " + "IntlRelativeDateTimeFormatter::STYLE_SHORT, or " + "IntlRelativeDateTimeFormatter::STYLE_NARROW"); + RETURN_THROWS(); + } + + if (!reldateformatter_valid_capitalization_context(capitalization_context)) { + zend_argument_value_error(3, + "must be one of the IntlRelativeDateTimeFormatter::CAPITALIZATION_* constants"); + RETURN_THROWS(); + } + + UErrorCode status = U_ZERO_ERROR; + UNumberFormat *number_formatter_clone = nullptr; + if (number_formatter != nullptr) { + NumberFormatter_object *number_formatter_obj = Z_INTL_NUMBERFORMATTER_P(number_formatter); + if (FORMATTER_OBJECT(number_formatter_obj) == nullptr) { + zend_throw_error(NULL, "Found unconstructed NumberFormatter"); + RETURN_THROWS(); + } + + number_formatter_clone = unum_clone( + reinterpret_cast(FORMATTER_OBJECT(number_formatter_obj)), + &status); + if (U_FAILURE(status)) { + reldateformatter_throw_constructor_failure(obj, status, "Failed to clone NumberFormatter"); + RETURN_THROWS(); + } + } + + RELDATEFORMATTER_OBJECT(obj) = ureldatefmt_open( + locale, + number_formatter_clone, + static_cast(style), + static_cast(capitalization_context), + &status); + + if (U_FAILURE(status) || RELDATEFORMATTER_OBJECT(obj) == nullptr) { + if (U_SUCCESS(status)) { + status = U_MEMORY_ALLOCATION_ERROR; + } + reldateformatter_throw_constructor_failure( + obj, status, "Failed to create IntlRelativeDateTimeFormatter"); + RETURN_THROWS(); + } +} + +template +static zend_string *reldateformatter_format_result( + IntlRelativeDateTimeFormatter_object *obj, + Formatter &&formatter, + const char *failure_message +) +{ + UErrorCode status = U_ZERO_ERROR; + int32_t result_len = formatter(nullptr, 0, &status); + + if (status == U_BUFFER_OVERFLOW_ERROR) { + status = U_ZERO_ERROR; + } else if (U_FAILURE(status)) { + intl_errors_set(RELDATEFORMATTER_ERROR_P(obj), status, failure_message); + return nullptr; + } + + if (result_len == 0) { + return ZSTR_EMPTY_ALLOC(); + } + + std::unique_ptr result(new UChar[static_cast(result_len) + 1]); + const int32_t actual_len = formatter(result.get(), result_len + 1, &status); + if (U_FAILURE(status)) { + intl_errors_set(RELDATEFORMATTER_ERROR_P(obj), status, failure_message); + return nullptr; + } + + UnicodeString unicode_result(result.get(), actual_len); + zend_string *utf8_result = intl_charFromString(unicode_result, &status); + if (utf8_result == nullptr) { + intl_errors_set(RELDATEFORMATTER_ERROR_P(obj), status, "Failed to convert result to UTF-8"); + } + + return utf8_result; +} + +static void reldateformatter_format(INTERNAL_FUNCTION_PARAMETERS, bool numeric) +{ + zval *offset; + zend_long unit; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_NUMBER(offset) + Z_PARAM_LONG(unit) + ZEND_PARSE_PARAMETERS_END(); + + IntlRelativeDateTimeFormatter_object *obj = Z_INTL_RELDATEFORMATTER_P(ZEND_THIS); + intl_errors_reset(RELDATEFORMATTER_ERROR_P(obj)); + + if (RELDATEFORMATTER_OBJECT(obj) == nullptr) { + zend_throw_error(NULL, "Found unconstructed IntlRelativeDateTimeFormatter"); + RETURN_THROWS(); + } + + if (!reldateformatter_valid_unit(unit)) { + zend_argument_value_error(2, + "must be one of the IntlRelativeDateTimeFormatter::UNIT_* constants"); + RETURN_THROWS(); + } + + const double numeric_offset = zval_get_double(offset); + zend_string *result; + if (numeric) { + result = reldateformatter_format_result(obj, + [obj, numeric_offset, unit](UChar *buffer, int32_t capacity, UErrorCode *status) { + return ureldatefmt_formatNumeric( + RELDATEFORMATTER_OBJECT(obj), numeric_offset, + static_cast(unit), buffer, capacity, status); + }, + "Failed to format relative date/time numerically"); + } else { + result = reldateformatter_format_result(obj, + [obj, numeric_offset, unit](UChar *buffer, int32_t capacity, UErrorCode *status) { + return ureldatefmt_format( + RELDATEFORMATTER_OBJECT(obj), numeric_offset, + static_cast(unit), buffer, capacity, status); + }, + "Failed to format relative date/time"); + } + + if (result == nullptr) { + RETURN_FALSE; + } + RETURN_STR(result); +} + +PHP_METHOD(IntlRelativeDateTimeFormatter, format) +{ + reldateformatter_format(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); +} + +PHP_METHOD(IntlRelativeDateTimeFormatter, formatNumeric) +{ + reldateformatter_format(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); +} + +PHP_METHOD(IntlRelativeDateTimeFormatter, combineDateAndTime) +{ + zend_string *relative_date; + zend_string *time; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(relative_date) + Z_PARAM_STR(time) + ZEND_PARSE_PARAMETERS_END(); + + IntlRelativeDateTimeFormatter_object *obj = Z_INTL_RELDATEFORMATTER_P(ZEND_THIS); + intl_errors_reset(RELDATEFORMATTER_ERROR_P(obj)); + + if (RELDATEFORMATTER_OBJECT(obj) == nullptr) { + zend_throw_error(NULL, "Found unconstructed IntlRelativeDateTimeFormatter"); + RETURN_THROWS(); + } + + UErrorCode status = U_ZERO_ERROR; + UnicodeString relative_date_utf16; + UnicodeString time_utf16; + intl_stringFromChar( + relative_date_utf16, ZSTR_VAL(relative_date), ZSTR_LEN(relative_date), &status); + if (U_FAILURE(status)) { + intl_errors_set(RELDATEFORMATTER_ERROR_P(obj), status, + "Failed to convert relative date to UTF-16"); + RETURN_FALSE; + } + + intl_stringFromChar(time_utf16, ZSTR_VAL(time), ZSTR_LEN(time), &status); + if (U_FAILURE(status)) { + intl_errors_set(RELDATEFORMATTER_ERROR_P(obj), status, + "Failed to convert time to UTF-16"); + RETURN_FALSE; + } + + zend_string *result = reldateformatter_format_result(obj, + [obj, &relative_date_utf16, &time_utf16](UChar *buffer, int32_t capacity, UErrorCode *format_status) { + return ureldatefmt_combineDateAndTime( + RELDATEFORMATTER_OBJECT(obj), + relative_date_utf16.getBuffer(), relative_date_utf16.length(), + time_utf16.getBuffer(), time_utf16.length(), + buffer, capacity, format_status); + }, + "Failed to combine relative date and time"); + + if (result == nullptr) { + RETURN_FALSE; + } + RETURN_STR(result); +} + +PHP_METHOD(IntlRelativeDateTimeFormatter, getErrorCode) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + IntlRelativeDateTimeFormatter_object *obj = Z_INTL_RELDATEFORMATTER_P(ZEND_THIS); + RETURN_LONG(intl_error_get_code(RELDATEFORMATTER_ERROR_P(obj))); +} + +PHP_METHOD(IntlRelativeDateTimeFormatter, getErrorMessage) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + IntlRelativeDateTimeFormatter_object *obj = Z_INTL_RELDATEFORMATTER_P(ZEND_THIS); + RETURN_STR(intl_error_get_message(RELDATEFORMATTER_ERROR_P(obj))); +} + +void reldateformatter_register_class(void) +{ + IntlRelativeDateTimeFormatter_ce_ptr = register_class_IntlRelativeDateTimeFormatter(); + IntlRelativeDateTimeFormatter_ce_ptr->create_object = reldateformatter_create_object; + IntlRelativeDateTimeFormatter_ce_ptr->default_object_handlers = &reldateformatter_handlers; + + memcpy(&reldateformatter_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + reldateformatter_handlers.offset = offsetof(IntlRelativeDateTimeFormatter_object, zo); + reldateformatter_handlers.free_obj = reldateformatter_free_object; + reldateformatter_handlers.clone_obj = nullptr; +} diff --git a/ext/intl/reldateformatter/reldateformatter_class.h b/ext/intl/reldateformatter/reldateformatter_class.h new file mode 100644 index 000000000000..ca112180d68f --- /dev/null +++ b/ext/intl/reldateformatter/reldateformatter_class.h @@ -0,0 +1,49 @@ +/* + +----------------------------------------------------------------------+ + | Copyright © The PHP Group and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Weilin Du | + +----------------------------------------------------------------------+ + */ + +#ifndef RELDATEFORMATTER_CLASS_H +#define RELDATEFORMATTER_CLASS_H + +#include +#include + +#include "../intl_error.h" + +typedef struct { + intl_error error; + URelativeDateTimeFormatter *formatter; + zend_object zo; +} IntlRelativeDateTimeFormatter_object; + +#define php_intl_reldateformatter_fetch_object(obj) \ + ZEND_CONTAINER_OF(obj, IntlRelativeDateTimeFormatter_object, zo) +#define Z_INTL_RELDATEFORMATTER_P(zv) \ + php_intl_reldateformatter_fetch_object(Z_OBJ_P(zv)) + +#define RELDATEFORMATTER_ERROR(obj) ((obj)->error) +#define RELDATEFORMATTER_ERROR_P(obj) (&RELDATEFORMATTER_ERROR(obj)) +#define RELDATEFORMATTER_OBJECT(obj) ((obj)->formatter) + +#ifdef __cplusplus +extern "C" { +#endif + +void reldateformatter_register_class(void); +extern zend_class_entry *IntlRelativeDateTimeFormatter_ce_ptr; + +#ifdef __cplusplus +} +#endif + +#endif /* RELDATEFORMATTER_CLASS_H */ diff --git a/ext/intl/tests/reldateformatter/reldateformatter_api.phpt b/ext/intl/tests/reldateformatter/reldateformatter_api.phpt new file mode 100644 index 000000000000..559f3be27f39 --- /dev/null +++ b/ext/intl/tests/reldateformatter/reldateformatter_api.phpt @@ -0,0 +1,79 @@ +--TEST-- +IntlRelativeDateTimeFormatter API surface +--EXTENSIONS-- +intl +--FILE-- +isFinal()); +var_dump($reflection->isInternal()); +var_dump($reflection->isInstantiable()); +var_dump(count($reflection->getConstants())); + +foreach ([ + 'STYLE_LONG', + 'STYLE_SHORT', + 'STYLE_NARROW', + 'CAPITALIZATION_NONE', + 'CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE', + 'CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE', + 'CAPITALIZATION_FOR_UI_LIST_OR_MENU', + 'CAPITALIZATION_FOR_STANDALONE', + 'UNIT_YEAR', + 'UNIT_QUARTER', + 'UNIT_MONTH', + 'UNIT_WEEK', + 'UNIT_DAY', + 'UNIT_HOUR', + 'UNIT_MINUTE', + 'UNIT_SECOND', + 'UNIT_SUNDAY', + 'UNIT_MONDAY', + 'UNIT_TUESDAY', + 'UNIT_WEDNESDAY', + 'UNIT_THURSDAY', + 'UNIT_FRIDAY', + 'UNIT_SATURDAY', +] as $constant) { + var_dump($reflection->getReflectionConstant($constant)->getType()->getName()); +} + +$constructor = $reflection->getConstructor(); +foreach ($constructor->getParameters() as $parameter) { + echo $parameter->getName(), ': ', $parameter->getType(), "\n"; +} + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +int(23) +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +string(3) "int" +locale: ?string +style: int +capitalizationContext: int +numberFormatter: ?NumberFormatter diff --git a/ext/intl/tests/reldateformatter/reldateformatter_basic.phpt b/ext/intl/tests/reldateformatter/reldateformatter_basic.phpt new file mode 100644 index 000000000000..e640ea7852c7 --- /dev/null +++ b/ext/intl/tests/reldateformatter/reldateformatter_basic.phpt @@ -0,0 +1,40 @@ +--TEST-- +IntlRelativeDateTimeFormatter basic formatting +--EXTENSIONS-- +intl +--FILE-- +format(-1, IntlRelativeDateTimeFormatter::UNIT_DAY), "\n"; +echo $formatter->format(0, IntlRelativeDateTimeFormatter::UNIT_DAY), "\n"; +echo $formatter->format(1, IntlRelativeDateTimeFormatter::UNIT_DAY), "\n"; +echo $formatter->format(-1, IntlRelativeDateTimeFormatter::UNIT_WEEK), "\n"; +echo $formatter->format(2, IntlRelativeDateTimeFormatter::UNIT_WEEK), "\n"; +echo $formatter->format(-1, IntlRelativeDateTimeFormatter::UNIT_SUNDAY), "\n"; + +echo $formatter->formatNumeric(-1, IntlRelativeDateTimeFormatter::UNIT_DAY), "\n"; +echo $formatter->formatNumeric(1, IntlRelativeDateTimeFormatter::UNIT_DAY), "\n"; +echo $formatter->formatNumeric(1.5, IntlRelativeDateTimeFormatter::UNIT_HOUR), "\n"; + +$relativeDate = $formatter->format(-1, IntlRelativeDateTimeFormatter::UNIT_DAY); +echo $formatter->combineDateAndTime($relativeDate, '3:45 PM'), "\n"; + +var_dump($formatter->getErrorCode()); +var_dump($formatter->getErrorMessage()); + +?> +--EXPECTF-- +yesterday +today +tomorrow +last week +in 2 weeks +last Sunday +1 day ago +in 1 day +in 1.5 hours +yesterday%s3:45 PM +int(0) +string(12) "U_ZERO_ERROR" diff --git a/ext/intl/tests/reldateformatter/reldateformatter_error_state.phpt b/ext/intl/tests/reldateformatter/reldateformatter_error_state.phpt new file mode 100644 index 000000000000..725f57d6512f --- /dev/null +++ b/ext/intl/tests/reldateformatter/reldateformatter_error_state.phpt @@ -0,0 +1,35 @@ +--TEST-- +IntlRelativeDateTimeFormatter synchronizes and resets ICU error state +--EXTENSIONS-- +intl +--FILE-- +combineDateAndTime("\x80", '3:45 PM')); +var_dump($formatter->getErrorCode() === U_INVALID_CHAR_FOUND); +var_dump(intl_get_error_code() === $formatter->getErrorCode()); +var_dump(str_contains($formatter->getErrorMessage(), 'Failed to convert relative date to UTF-16')); + +var_dump($formatter->format(1, IntlRelativeDateTimeFormatter::UNIT_DAY)); +var_dump($formatter->getErrorCode()); +var_dump(intl_get_error_code()); + +ini_set('intl.use_exceptions', '1'); +try { + $formatter->combineDateAndTime('today', "\x80"); +} catch (IntlException $e) { + echo $e::class, ': ', str_contains($e->getMessage(), 'Failed to convert time to UTF-16') ? 'conversion failed' : 'unexpected', "\n"; +} + +?> +--EXPECT-- +bool(false) +bool(true) +bool(true) +bool(true) +string(8) "tomorrow" +int(0) +int(0) +IntlException: conversion failed diff --git a/ext/intl/tests/reldateformatter/reldateformatter_errors.phpt b/ext/intl/tests/reldateformatter/reldateformatter_errors.phpt new file mode 100644 index 000000000000..84b3873a22ad --- /dev/null +++ b/ext/intl/tests/reldateformatter/reldateformatter_errors.phpt @@ -0,0 +1,50 @@ +--TEST-- +IntlRelativeDateTimeFormatter argument validation and object state errors +--EXTENSIONS-- +intl +--FILE-- +getMessage(), "\n"; + } +} + +showThrowable(fn() => new IntlRelativeDateTimeFormatter('f')); +showThrowable(fn() => new IntlRelativeDateTimeFormatter(str_repeat('a', 157))); +showThrowable(fn() => new IntlRelativeDateTimeFormatter('en_US', -1)); +showThrowable(fn() => new IntlRelativeDateTimeFormatter( + 'en_US', + IntlRelativeDateTimeFormatter::STYLE_LONG, + -1, +)); + +$formatter = new IntlRelativeDateTimeFormatter('en_US'); +showThrowable(fn() => $formatter->format(1, -1)); +showThrowable(fn() => $formatter->formatNumeric(1, 15)); +showThrowable(fn() => $formatter->__construct()); +showThrowable(fn() => clone $formatter); + +$unconstructedNumberFormatter = (new ReflectionClass(NumberFormatter::class)) + ->newInstanceWithoutConstructor(); +showThrowable(fn() => new IntlRelativeDateTimeFormatter( + 'en_US', + IntlRelativeDateTimeFormatter::STYLE_LONG, + IntlRelativeDateTimeFormatter::CAPITALIZATION_NONE, + $unconstructedNumberFormatter, +)); + +?> +--EXPECT-- +ValueError: IntlRelativeDateTimeFormatter::__construct(): Argument #1 ($locale) "f" is invalid +ValueError: IntlRelativeDateTimeFormatter::__construct(): Argument #1 ($locale) must be less than or equal to 156 characters +ValueError: IntlRelativeDateTimeFormatter::__construct(): Argument #2 ($style) must be one of IntlRelativeDateTimeFormatter::STYLE_LONG, IntlRelativeDateTimeFormatter::STYLE_SHORT, or IntlRelativeDateTimeFormatter::STYLE_NARROW +ValueError: IntlRelativeDateTimeFormatter::__construct(): Argument #3 ($capitalizationContext) must be one of the IntlRelativeDateTimeFormatter::CAPITALIZATION_* constants +ValueError: IntlRelativeDateTimeFormatter::format(): Argument #2 ($unit) must be one of the IntlRelativeDateTimeFormatter::UNIT_* constants +ValueError: IntlRelativeDateTimeFormatter::formatNumeric(): Argument #2 ($unit) must be one of the IntlRelativeDateTimeFormatter::UNIT_* constants +Error: IntlRelativeDateTimeFormatter object is already constructed +Error: Trying to clone an uncloneable object of class IntlRelativeDateTimeFormatter +Error: Found unconstructed NumberFormatter diff --git a/ext/intl/tests/reldateformatter/reldateformatter_locales_and_options.phpt b/ext/intl/tests/reldateformatter/reldateformatter_locales_and_options.phpt new file mode 100644 index 000000000000..874384b7640e --- /dev/null +++ b/ext/intl/tests/reldateformatter/reldateformatter_locales_and_options.phpt @@ -0,0 +1,45 @@ +--TEST-- +IntlRelativeDateTimeFormatter locales, styles, and capitalization context +--EXTENSIONS-- +intl +--FILE-- +formatNumeric(-1, IntlRelativeDateTimeFormatter::UNIT_DAY), "\n"; +echo $french->formatNumeric(1.5, IntlRelativeDateTimeFormatter::UNIT_HOUR), "\n"; + +$chinese = new IntlRelativeDateTimeFormatter('zh_CN'); +echo $chinese->formatNumeric(3, IntlRelativeDateTimeFormatter::UNIT_MONTH), "\n"; + +$beginning = new IntlRelativeDateTimeFormatter( + 'en_US', + IntlRelativeDateTimeFormatter::STYLE_LONG, + IntlRelativeDateTimeFormatter::CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE, +); +echo $beginning->format(-1, IntlRelativeDateTimeFormatter::UNIT_DAY), "\n"; + +$short = new IntlRelativeDateTimeFormatter( + 'en_US', + IntlRelativeDateTimeFormatter::STYLE_SHORT, +); +$narrow = new IntlRelativeDateTimeFormatter( + 'en_US', + IntlRelativeDateTimeFormatter::STYLE_NARROW, +); +echo $short->formatNumeric(3, IntlRelativeDateTimeFormatter::UNIT_DAY), "\n"; +echo $narrow->formatNumeric(3, IntlRelativeDateTimeFormatter::UNIT_DAY), "\n"; + +Locale::setDefault('en_US'); +$defaultLocale = new IntlRelativeDateTimeFormatter(); +echo $defaultLocale->format(1, IntlRelativeDateTimeFormatter::UNIT_DAY), "\n"; + +?> +--EXPECT-- +il y a 1 jour +dans 1,5 heure +3个月后 +Yesterday +in 3 days +in 3d +tomorrow diff --git a/ext/intl/tests/reldateformatter/reldateformatter_numberformatter.phpt b/ext/intl/tests/reldateformatter/reldateformatter_numberformatter.phpt new file mode 100644 index 000000000000..b427d900ebcf --- /dev/null +++ b/ext/intl/tests/reldateformatter/reldateformatter_numberformatter.phpt @@ -0,0 +1,27 @@ +--TEST-- +IntlRelativeDateTimeFormatter clones a custom NumberFormatter +--EXTENSIONS-- +intl +--FILE-- +setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, 1); +$formatter = new IntlRelativeDateTimeFormatter( + 'en_US', + IntlRelativeDateTimeFormatter::STYLE_LONG, + IntlRelativeDateTimeFormatter::CAPITALIZATION_NONE, + $numberFormatter, +); + +/* Mutating and destroying the source formatter must not affect the clone. */ +$numberFormatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, 0); +unset($numberFormatter); + +echo $formatter->formatNumeric(2, IntlRelativeDateTimeFormatter::UNIT_DAY), "\n"; +echo $formatter->format(2, IntlRelativeDateTimeFormatter::UNIT_WEEK), "\n"; + +?> +--EXPECT-- +in 2.0 days +in 2.0 weeks