Commit 3ed80a154d5 for php.net

commit 3ed80a154d589e3ea1b02f43fa1899ea9c46d70f
Author: David CARLIER <devnexen@gmail.com>
Date:   Sat May 16 14:01:47 2026 +0100

    ext/intl: Migrate formatter, listformatter, and rangeformatter from I… (#21378)

    - formatter: Replace intl_convert_utf8_to_utf16/uloc_getISO3Language with
      intl_stringFromChar/icu::Locale, use UnicodeString for pattern handling
    - listformatter: Replace ulistfmt_open/ulistfmt_openForType/ulistfmt_close/
      ulistfmt_format with ListFormatter C++ equivalents, use UnicodeString array
      and intl_stringFromChar/intl_charFromString for string conversions
    - rangeformatter: Replace uloc_getISO3Language with icu::Locale::getISO3Language
    - Replace UNumberFormat*/unum_* with icu::NumberFormat C++ equivalents.
    - Attribute get/set still use reinterpret_cast<UNumberFormat*> for now.

diff --git a/ext/intl/formatter/formatter_attr.cpp b/ext/intl/formatter/formatter_attr.cpp
index 01108351a5d..d21873ecdab 100644
--- a/ext/intl/formatter/formatter_attr.cpp
+++ b/ext/intl/formatter/formatter_attr.cpp
@@ -16,13 +16,16 @@
 #include <config.h>
 #endif

+#include <unicode/unum.h>
+#include "../intl_convertcpp.h"
+#include "formatter_class.h"
+
 extern "C" {
 #include "php_intl.h"
 #include "intl_convert.h"
 }
-#include "formatter_class.h"

-#include <unicode/ustring.h>
+#define FORMATTER_UNUM(nfo) reinterpret_cast<UNumberFormat*>(FORMATTER_OBJECT(nfo))

 /* {{{ Get formatter attribute value. */
 U_CFUNC PHP_FUNCTION( numfmt_get_attribute )
@@ -62,7 +65,7 @@ U_CFUNC PHP_FUNCTION( numfmt_get_attribute )
 		case UNUM_MIN_SIGNIFICANT_DIGITS:
 		case UNUM_MAX_SIGNIFICANT_DIGITS:
 		case UNUM_LENIENT_PARSE:
-			value = unum_getAttribute(FORMATTER_OBJECT(nfo), attribute);
+			value = unum_getAttribute(FORMATTER_UNUM(nfo), attribute);
 			if(value == -1) {
 				INTL_DATA_ERROR_CODE(nfo) = U_UNSUPPORTED_ERROR;
 			} else {
@@ -71,7 +74,7 @@ U_CFUNC PHP_FUNCTION( numfmt_get_attribute )
 			break;
 		case UNUM_ROUNDING_INCREMENT:
 		{
-			double value_double = unum_getDoubleAttribute(FORMATTER_OBJECT(nfo), attribute);
+			double value_double = unum_getDoubleAttribute(FORMATTER_UNUM(nfo), attribute);
 			if(value_double == -1) {
 				INTL_DATA_ERROR_CODE(nfo) = U_UNSUPPORTED_ERROR;
 			} else {
@@ -110,12 +113,12 @@ U_CFUNC PHP_FUNCTION( numfmt_get_text_attribute )

 	UNumberFormatTextAttribute attribute = static_cast<UNumberFormatTextAttribute>(lattribute);

-	length = unum_getTextAttribute( FORMATTER_OBJECT(nfo), attribute, value, value_buf_size, &INTL_DATA_ERROR_CODE(nfo) );
+	length = unum_getTextAttribute( FORMATTER_UNUM(nfo), attribute, value, value_buf_size, &INTL_DATA_ERROR_CODE(nfo) );
 	if(INTL_DATA_ERROR_CODE(nfo) == U_BUFFER_OVERFLOW_ERROR && length >= value_buf_size) {
 		++length; /* to avoid U_STRING_NOT_TERMINATED_WARNING */
 		INTL_DATA_ERROR_CODE(nfo) = U_ZERO_ERROR;
 		value = eumalloc(length);
-		length = unum_getTextAttribute( FORMATTER_OBJECT(nfo), attribute, value, length, &INTL_DATA_ERROR_CODE(nfo) );
+		length = unum_getTextAttribute( FORMATTER_UNUM(nfo), attribute, value, length, &INTL_DATA_ERROR_CODE(nfo) );
 		if(U_FAILURE(INTL_DATA_ERROR_CODE(nfo))) {
 			efree(value);
 			value = value_buf;
@@ -166,10 +169,10 @@ U_CFUNC PHP_FUNCTION( numfmt_set_attribute )
 		case UNUM_MIN_SIGNIFICANT_DIGITS:
 		case UNUM_MAX_SIGNIFICANT_DIGITS:
 		case UNUM_LENIENT_PARSE:
-			unum_setAttribute(FORMATTER_OBJECT(nfo), attribute, zval_get_long(value));
+			unum_setAttribute(FORMATTER_UNUM(nfo), attribute, zval_get_long(value));
 			break;
 		case UNUM_ROUNDING_INCREMENT:
-			unum_setDoubleAttribute(FORMATTER_OBJECT(nfo), attribute, zval_get_double(value));
+			unum_setDoubleAttribute(FORMATTER_UNUM(nfo), attribute, zval_get_double(value));
 			break;
 		default:
 			INTL_DATA_ERROR_CODE(nfo) = U_UNSUPPORTED_ERROR;
@@ -185,8 +188,6 @@ U_CFUNC PHP_FUNCTION( numfmt_set_attribute )
 /* {{{ Get formatter attribute value. */
 U_CFUNC PHP_FUNCTION( numfmt_set_text_attribute )
 {
-	int32_t slength = 0;
-	UChar *svalue = NULL;
 	zend_long attribute;
 	char *value;
 	size_t len;
@@ -203,14 +204,12 @@ U_CFUNC PHP_FUNCTION( numfmt_set_text_attribute )
 	FORMATTER_METHOD_FETCH_OBJECT;

 	/* Convert given attribute value to UTF-16. */
-	intl_convert_utf8_to_utf16(&svalue, &slength, value, len, &INTL_DATA_ERROR_CODE(nfo));
+	UnicodeString svalue;
+	intl_stringFromChar(svalue, value, len, &INTL_DATA_ERROR_CODE(nfo));
 	INTL_METHOD_CHECK_STATUS( nfo, "Error converting attribute value to UTF-16" );

 	/* Actually set new attribute value. */
-	unum_setTextAttribute(FORMATTER_OBJECT(nfo), static_cast<UNumberFormatTextAttribute>(attribute), svalue, slength, &INTL_DATA_ERROR_CODE(nfo));
-	if (svalue) {
-		efree(svalue);
-	}
+	unum_setTextAttribute(FORMATTER_UNUM(nfo), static_cast<UNumberFormatTextAttribute>(attribute), svalue.getBuffer(), svalue.length(), &INTL_DATA_ERROR_CODE(nfo));
 	INTL_METHOD_CHECK_STATUS( nfo, "Error setting text attribute" );

 	RETURN_TRUE;
@@ -243,12 +242,12 @@ U_CFUNC PHP_FUNCTION( numfmt_get_symbol )
 	/* Fetch the object. */
 	FORMATTER_METHOD_FETCH_OBJECT;

-	length = unum_getSymbol(FORMATTER_OBJECT(nfo), symbol, value_buf, length, &INTL_DATA_ERROR_CODE(nfo));
+	length = unum_getSymbol(FORMATTER_UNUM(nfo), symbol, value_buf, length, &INTL_DATA_ERROR_CODE(nfo));
 	if(INTL_DATA_ERROR_CODE(nfo) == U_BUFFER_OVERFLOW_ERROR && length >= USIZE( value_buf )) {
 		++length; /* to avoid U_STRING_NOT_TERMINATED_WARNING */
 		INTL_DATA_ERROR_CODE(nfo) = U_ZERO_ERROR;
 		value = eumalloc(length);
-		length = unum_getSymbol(FORMATTER_OBJECT(nfo), symbol, value, length, &INTL_DATA_ERROR_CODE(nfo));
+		length = unum_getSymbol(FORMATTER_UNUM(nfo), symbol, value, length, &INTL_DATA_ERROR_CODE(nfo));
 		if(U_FAILURE(INTL_DATA_ERROR_CODE(nfo))) {
 			efree(value);
 			value = value_buf;
@@ -266,8 +265,6 @@ U_CFUNC PHP_FUNCTION( numfmt_set_symbol )
 	zend_long  lsymbol;
 	char*      value     = NULL;
 	size_t     value_len = 0;
-	UChar*     svalue  = 0;
-	int32_t    slength = 0;
 	FORMATTER_METHOD_INIT_VARS;

 	/* Parse parameters. */
@@ -288,14 +285,12 @@ U_CFUNC PHP_FUNCTION( numfmt_set_symbol )
 	FORMATTER_METHOD_FETCH_OBJECT;

 	/* Convert given symbol to UTF-16. */
-	intl_convert_utf8_to_utf16(&svalue, &slength, value, value_len, &INTL_DATA_ERROR_CODE(nfo));
+	UnicodeString svalue;
+	intl_stringFromChar(svalue, value, value_len, &INTL_DATA_ERROR_CODE(nfo));
 	INTL_METHOD_CHECK_STATUS( nfo, "Error converting symbol value to UTF-16" );

 	/* Actually set the symbol. */
-	unum_setSymbol(FORMATTER_OBJECT(nfo), symbol, svalue, slength, &INTL_DATA_ERROR_CODE(nfo));
-	if (svalue) {
-		efree(svalue);
-	}
+	unum_setSymbol(FORMATTER_UNUM(nfo), symbol, svalue.getBuffer(), svalue.length(), &INTL_DATA_ERROR_CODE(nfo));
 	INTL_METHOD_CHECK_STATUS( nfo, "Error setting symbol value" );

 	RETURN_TRUE;
@@ -320,12 +315,12 @@ U_CFUNC PHP_FUNCTION( numfmt_get_pattern )
 	/* Fetch the object. */
 	FORMATTER_METHOD_FETCH_OBJECT;

-	length = unum_toPattern(FORMATTER_OBJECT(nfo), 0, value, length, &INTL_DATA_ERROR_CODE(nfo));
+	length = unum_toPattern(FORMATTER_UNUM(nfo), 0, value, length, &INTL_DATA_ERROR_CODE(nfo));
 	if(INTL_DATA_ERROR_CODE(nfo) == U_BUFFER_OVERFLOW_ERROR && length >= USIZE( value_buf )) {
 		++length; /* to avoid U_STRING_NOT_TERMINATED_WARNING */
 		INTL_DATA_ERROR_CODE(nfo) = U_ZERO_ERROR;
 		value = eumalloc(length);
-		length = unum_toPattern( FORMATTER_OBJECT(nfo), 0, value, length, &INTL_DATA_ERROR_CODE(nfo) );
+		length = unum_toPattern( FORMATTER_UNUM(nfo), 0, value, length, &INTL_DATA_ERROR_CODE(nfo) );
 		if(U_FAILURE(INTL_DATA_ERROR_CODE(nfo))) {
 			efree(value);
 			value = value_buf;
@@ -342,8 +337,6 @@ U_CFUNC PHP_FUNCTION( numfmt_set_pattern )
 {
 	char*       value = NULL;
 	size_t      value_len = 0;
-	int32_t     slength = 0;
-	UChar*	    svalue  = NULL;
 	UParseError spattern_error = {0};
 	FORMATTER_METHOD_INIT_VARS;

@@ -357,13 +350,11 @@ U_CFUNC PHP_FUNCTION( numfmt_set_pattern )
 	FORMATTER_METHOD_FETCH_OBJECT;

 	/* Convert given pattern to UTF-16. */
-	intl_convert_utf8_to_utf16(&svalue, &slength, value, value_len, &INTL_DATA_ERROR_CODE(nfo));
+	UnicodeString svalue;
+	intl_stringFromChar(svalue, value, value_len, &INTL_DATA_ERROR_CODE(nfo));
 	INTL_METHOD_CHECK_STATUS( nfo, "Error converting pattern to UTF-16" );

-	unum_applyPattern(FORMATTER_OBJECT(nfo), 0, svalue, slength, &spattern_error, &INTL_DATA_ERROR_CODE(nfo));
-	if (svalue) {
-		efree(svalue);
-	}
+	unum_applyPattern(FORMATTER_UNUM(nfo), 0, svalue.getBuffer(), svalue.length(), &spattern_error, &INTL_DATA_ERROR_CODE(nfo));
 	if (U_FAILURE(INTL_DATA_ERROR_CODE(nfo))) {
 		char *msg;
 		spprintf(&msg, 0, "Error setting pattern value at line %d, offset %d", spattern_error.line, spattern_error.offset);
@@ -393,7 +384,7 @@ U_CFUNC PHP_FUNCTION( numfmt_get_locale )
 	/* Fetch the object. */
 	FORMATTER_METHOD_FETCH_OBJECT;

-	loc = unum_getLocaleByType(FORMATTER_OBJECT(nfo), static_cast<ULocDataLocaleType>(type), &INTL_DATA_ERROR_CODE(nfo));
+	loc = unum_getLocaleByType(FORMATTER_UNUM(nfo), static_cast<ULocDataLocaleType>(type), &INTL_DATA_ERROR_CODE(nfo));
 	INTL_METHOD_CHECK_STATUS( nfo, "Error getting locale" );
 	RETURN_STRING(loc);
 }
diff --git a/ext/intl/formatter/formatter_class.cpp b/ext/intl/formatter/formatter_class.cpp
index ce367096d85..7391a1cd9f6 100644
--- a/ext/intl/formatter/formatter_class.cpp
+++ b/ext/intl/formatter/formatter_class.cpp
@@ -12,8 +12,6 @@
    +----------------------------------------------------------------------+
  */

-#include <unicode/unum.h>
-
 #include "formatter_class.h"
 extern "C" {
 #include "php_intl.h"
@@ -72,10 +70,9 @@ U_CFUNC zend_object *NumberFormatter_object_clone(zend_object *object)
 	zend_objects_clone_members(&new_nfo->zo, &nfo->zo);

 	/* clone formatter object. It may fail, the destruction code must handle this case */
-	if (FORMATTER_OBJECT(nfo) != NULL) {
-		UErrorCode error = U_ZERO_ERROR;
-		FORMATTER_OBJECT(new_nfo) = unum_clone(FORMATTER_OBJECT(nfo), &error);
-		if (U_FAILURE(error)) {
+	if (FORMATTER_OBJECT(nfo) != nullptr) {
+		FORMATTER_OBJECT(new_nfo) = FORMATTER_OBJECT(nfo)->clone();
+		if (FORMATTER_OBJECT(new_nfo) == nullptr) {
 			zend_throw_error(NULL, "Failed to clone NumberFormatter");
 		}
 	} else {
diff --git a/ext/intl/formatter/formatter_class.h b/ext/intl/formatter/formatter_class.h
index 465c9edaceb..47207b843bf 100644
--- a/ext/intl/formatter/formatter_class.h
+++ b/ext/intl/formatter/formatter_class.h
@@ -15,7 +15,7 @@
 #ifndef FORMATTER_CLASS_H
 #define FORMATTER_CLASS_H

-#include <php.h>
+#include "formatter_data.h"

 #ifdef __cplusplus
 extern "C" {
@@ -23,7 +23,6 @@ extern "C" {
 #include "intl_common.h"
 #include "intl_error.h"
 #include "intl_data.h"
-#include "formatter_data.h"
 #ifdef __cplusplus
 }
 #endif
diff --git a/ext/intl/formatter/formatter_data.cpp b/ext/intl/formatter/formatter_data.cpp
index 593d746f100..506972e7208 100644
--- a/ext/intl/formatter/formatter_data.cpp
+++ b/ext/intl/formatter/formatter_data.cpp
@@ -26,7 +26,7 @@ void formatter_data_init( formatter_data* nf_data )
 	if( !nf_data )
 		return;

-	nf_data->unum                = NULL;
+	nf_data->unum                = nullptr;
 	intl_error_reset( &nf_data->error );
 }
 /* }}} */
@@ -39,10 +39,8 @@ void formatter_data_free( formatter_data* nf_data )
 	if( !nf_data )
 		return;

-	if( nf_data->unum )
-		unum_close( nf_data->unum );
-
-	nf_data->unum = NULL;
+	delete nf_data->unum;
+	nf_data->unum = nullptr;
 	intl_error_reset( &nf_data->error );
 }
 /* }}} */
diff --git a/ext/intl/formatter/formatter_data.h b/ext/intl/formatter/formatter_data.h
index 183682bee6a..1208970471b 100644
--- a/ext/intl/formatter/formatter_data.h
+++ b/ext/intl/formatter/formatter_data.h
@@ -15,9 +15,21 @@
 #ifndef FORMATTER_DATA_H
 #define FORMATTER_DATA_H

+#ifdef __cplusplus
+extern "C" {
+#endif
 #include <php.h>
+#ifdef __cplusplus
+}
+#endif
+
+#include <unicode/numfmt.h>

-#include <unicode/unum.h>
+#ifdef __cplusplus
+using icu::NumberFormat;
+#else
+typedef void NumberFormat;
+#endif

 #ifdef __cplusplus
 extern "C" {
@@ -32,7 +44,7 @@ typedef struct {
 	intl_error      error;

 	// formatter handling
-	UNumberFormat*  unum;
+	NumberFormat*    unum;
 } formatter_data;

 #ifdef __cplusplus
diff --git a/ext/intl/formatter/formatter_format.cpp b/ext/intl/formatter/formatter_format.cpp
index 6b2651e4ead..48edabc8ccd 100644
--- a/ext/intl/formatter/formatter_format.cpp
+++ b/ext/intl/formatter/formatter_format.cpp
@@ -16,24 +16,21 @@
 #include <config.h>
 #endif

+#include <unicode/curramt.h>
+#include <unicode/fmtable.h>
+#include "../intl_convertcpp.h"
+#include "formatter_class.h"
+#include "formatter_format.h"
+
 extern "C" {
 #include "php_intl.h"
-#include "intl_convert.h"
 }

-#include <unicode/ustring.h>
-
-#include "formatter_class.h"
-#include "formatter_format.h"
-
 /* {{{ Format a number. */
 U_CFUNC PHP_FUNCTION( numfmt_format )
 {
 	zval *number;
 	zend_long type = FORMAT_TYPE_DEFAULT;
-	UChar format_buf[32];
-	UChar* formatted = format_buf;
-	int32_t formatted_len = USIZE(format_buf);
 	FORMATTER_METHOD_INIT_VARS;

 	/* Parse parameters. */
@@ -59,50 +56,27 @@ U_CFUNC PHP_FUNCTION( numfmt_format )
 		}
 	}

+	icu::UnicodeString result;
+	icu::FieldPosition pos;
+
 	switch(type) {
 		case FORMAT_TYPE_INT32:
 			convert_to_long(number);
-			formatted_len = unum_format(FORMATTER_OBJECT(nfo), (int32_t)Z_LVAL_P(number),
-				formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo));
-			if (INTL_DATA_ERROR_CODE(nfo) == U_BUFFER_OVERFLOW_ERROR) {
-				intl_error_reset(INTL_DATA_ERROR_P(nfo));
-				formatted = eumalloc(formatted_len);
-				formatted_len = unum_format(FORMATTER_OBJECT(nfo), (int32_t)Z_LVAL_P(number),
-					formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo));
-				if (U_FAILURE( INTL_DATA_ERROR_CODE(nfo) ) ) {
-					efree(formatted);
-				}
-			}
+			FORMATTER_OBJECT(nfo)->format((int32_t)Z_LVAL_P(number), result, pos, INTL_DATA_ERROR_CODE(nfo));
 			INTL_METHOD_CHECK_STATUS( nfo, "Number formatting failed" );
 			break;

 		case FORMAT_TYPE_INT64:
 		{
 			int64_t value = (Z_TYPE_P(number) == IS_DOUBLE)?(int64_t)Z_DVAL_P(number):Z_LVAL_P(number);
-			formatted_len = unum_formatInt64(FORMATTER_OBJECT(nfo), value, formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo));
-			if (INTL_DATA_ERROR_CODE(nfo) == U_BUFFER_OVERFLOW_ERROR) {
-				intl_error_reset(INTL_DATA_ERROR_P(nfo));
-				formatted = eumalloc(formatted_len);
-				formatted_len = unum_formatInt64(FORMATTER_OBJECT(nfo), value, formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo));
-				if (U_FAILURE( INTL_DATA_ERROR_CODE(nfo) ) ) {
-					efree(formatted);
-				}
-			}
+			FORMATTER_OBJECT(nfo)->format(value, result, pos, INTL_DATA_ERROR_CODE(nfo));
 			INTL_METHOD_CHECK_STATUS( nfo, "Number formatting failed" );
 		}
 			break;

 		case FORMAT_TYPE_DOUBLE:
 			convert_to_double(number);
-			formatted_len = unum_formatDouble(FORMATTER_OBJECT(nfo), Z_DVAL_P(number), formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo));
-			if (INTL_DATA_ERROR_CODE(nfo) == U_BUFFER_OVERFLOW_ERROR) {
-				intl_error_reset(INTL_DATA_ERROR_P(nfo));
-				formatted = eumalloc(formatted_len);
-				unum_formatDouble(FORMATTER_OBJECT(nfo), Z_DVAL_P(number), formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo));
-				if (U_FAILURE( INTL_DATA_ERROR_CODE(nfo) ) ) {
-					efree(formatted);
-				}
-			}
+			FORMATTER_OBJECT(nfo)->format(Z_DVAL_P(number), result, pos, INTL_DATA_ERROR_CODE(nfo));
 			INTL_METHOD_CHECK_STATUS( nfo, "Number formatting failed" );
 			break;
 		case FORMAT_TYPE_CURRENCY:
@@ -120,7 +94,9 @@ U_CFUNC PHP_FUNCTION( numfmt_format )
 			RETURN_THROWS();
 	}

-	INTL_METHOD_RETVAL_UTF8( nfo, formatted, formatted_len, ( formatted != format_buf ) );
+	zend_string *u8str = intl_charFromString(result, &INTL_DATA_ERROR_CODE(nfo));
+	INTL_METHOD_CHECK_STATUS(nfo, "Error converting result to UTF-8");
+	RETVAL_STR(u8str);
 }
 /* }}} */

@@ -128,13 +104,8 @@ U_CFUNC PHP_FUNCTION( numfmt_format )
 U_CFUNC PHP_FUNCTION( numfmt_format_currency )
 {
 	double     number;
-	UChar      format_buf[32];
-	UChar*     formatted     = format_buf;
-	int32_t    formatted_len = USIZE(format_buf);
 	char*      currency      = NULL;
 	size_t     currency_len  = 0;
-	UChar*     scurrency     = NULL;
-	int32_t    scurrency_len = 0;
 	FORMATTER_METHOD_INIT_VARS;

 	/* Parse parameters. */
@@ -148,36 +119,31 @@ U_CFUNC PHP_FUNCTION( numfmt_format_currency )
 	FORMATTER_METHOD_FETCH_OBJECT;

 	/* Convert currency to UTF-16. */
-	intl_convert_utf8_to_utf16(&scurrency, &scurrency_len, currency, currency_len, &INTL_DATA_ERROR_CODE(nfo));
+	icu::UnicodeString ucurrency;
+	intl_stringFromChar(ucurrency, currency, currency_len, &INTL_DATA_ERROR_CODE(nfo));
 	INTL_METHOD_CHECK_STATUS( nfo, "Currency conversion to UTF-16 failed" );

-	/* Format the number using a fixed-length buffer. */
-	formatted_len = unum_formatDoubleCurrency(FORMATTER_OBJECT(nfo), number, scurrency, formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo));
-
-	/* If the buffer turned out to be too small
-	 * then allocate another buffer dynamically
-	 * and use it to format the number.
-	 */
-	if (INTL_DATA_ERROR_CODE(nfo) == U_BUFFER_OVERFLOW_ERROR) {
-		intl_error_reset(INTL_DATA_ERROR_P(nfo));
-		formatted = eumalloc(formatted_len);
-		unum_formatDoubleCurrency(FORMATTER_OBJECT(nfo), number, scurrency, formatted, formatted_len, NULL, &INTL_DATA_ERROR_CODE(nfo));
+	/* Format using CurrencyAmount. */
+	icu::CurrencyAmount *currAmt = new icu::CurrencyAmount(number, ucurrency.getTerminatedBuffer(), INTL_DATA_ERROR_CODE(nfo));
+	if (U_FAILURE(INTL_DATA_ERROR_CODE(nfo))) {
+		delete currAmt;
+		intl_errors_set_custom_msg(INTL_DATA_ERROR_P(nfo), "Number formatting failed");
+		RETURN_FALSE;
 	}
-
-	if( U_FAILURE( INTL_DATA_ERROR_CODE((nfo)) ) ) {
-		intl_error_set_code( NULL, INTL_DATA_ERROR_CODE((nfo)) );
-		intl_errors_set_custom_msg( INTL_DATA_ERROR_P(nfo), "Number formatting failed");
-		RETVAL_FALSE;
-		if (formatted != format_buf) {
-			efree(formatted);
-		}
-	} else {
-		INTL_METHOD_RETVAL_UTF8( nfo, formatted, formatted_len, ( formatted != format_buf ) );
+	icu::Formattable fmt;
+	fmt.adoptObject(currAmt);
+	icu::UnicodeString result;
+	icu::FieldPosition pos;
+	FORMATTER_OBJECT(nfo)->format(fmt, result, pos, INTL_DATA_ERROR_CODE(nfo));
+
+	if (U_FAILURE(INTL_DATA_ERROR_CODE(nfo))) {
+		intl_errors_set_custom_msg(INTL_DATA_ERROR_P(nfo), "Number formatting failed");
+		RETURN_FALSE;
 	}

-	if(scurrency) {
-		efree(scurrency);
-	}
+	zend_string *u8str = intl_charFromString(result, &INTL_DATA_ERROR_CODE(nfo));
+	INTL_METHOD_CHECK_STATUS(nfo, "Error converting result to UTF-8");
+	RETVAL_STR(u8str);
 }

 /* }}} */
diff --git a/ext/intl/formatter/formatter_main.cpp b/ext/intl/formatter/formatter_main.cpp
index 31c4cdcc485..79750714a5f 100644
--- a/ext/intl/formatter/formatter_main.cpp
+++ b/ext/intl/formatter/formatter_main.cpp
@@ -16,14 +16,17 @@
 #include <config.h>
 #endif

-#include <unicode/ustring.h>
-#include <unicode/uloc.h>
+#include <unicode/locid.h>
+#include <unicode/decimfmt.h>
+#include <unicode/dcfmtsym.h>
+#include <unicode/rbnf.h>
+#include <unicode/compactdecimalformat.h>
+#include "../intl_convertcpp.h"
+#include "formatter_class.h"

 extern "C" {
 #include "php_intl.h"
-#include "intl_convert.h"
 }
-#include "formatter_class.h"

 /* {{{ */
 static int numfmt_ctor(INTERNAL_FUNCTION_PARAMETERS)
@@ -32,8 +35,7 @@ static int numfmt_ctor(INTERNAL_FUNCTION_PARAMETERS)
 	char*       pattern = NULL;
 	size_t      locale_len = 0, pattern_len = 0;
 	zend_long   style;
-	UChar*      spattern     = NULL;
-	int32_t     spattern_len = 0;
+	UnicodeString upattern;
 	FORMATTER_METHOD_INIT_VARS;

 	ZEND_PARSE_PARAMETERS_START(2, 3)
@@ -53,7 +55,7 @@ static int numfmt_ctor(INTERNAL_FUNCTION_PARAMETERS)

 	/* Convert pattern (if specified) to UTF-16. */
 	if(pattern && pattern_len) {
-		intl_convert_utf8_to_utf16(&spattern, &spattern_len, pattern, pattern_len, &INTL_DATA_ERROR_CODE(nfo));
+		intl_stringFromChar(upattern, pattern, pattern_len, &INTL_DATA_ERROR_CODE(nfo));
 		INTL_CTOR_CHECK_STATUS(nfo, "error converting pattern to UTF-16");
 	}

@@ -61,7 +63,7 @@ static int numfmt_ctor(INTERNAL_FUNCTION_PARAMETERS)
 		locale = (char *)intl_locale_get_default();
 	}

-	if (strlen(uloc_getISO3Language(locale)) == 0) {
+	if (icu::Locale(locale).getISO3Language()[0] == '\0') {
 		zend_argument_value_error(1, "\"%s\" is invalid", locale);
 		return FAILURE;
 	}
@@ -70,12 +72,55 @@ static int numfmt_ctor(INTERNAL_FUNCTION_PARAMETERS)
 	const char* final_locale = canonicalized_locale ? canonicalized_locale : locale;

 	/* Create an ICU number formatter. */
-	FORMATTER_OBJECT(nfo) = unum_open(static_cast<UNumberFormatStyle>(style), spattern, spattern_len, final_locale, nullptr, &INTL_DATA_ERROR_CODE(nfo));
-
-	if (spattern) {
-		efree(spattern);
+	icu::Locale loc(final_locale);
+	switch (style) {
+		case UNUM_PATTERN_DECIMAL: {
+			icu::DecimalFormatSymbols *syms = new icu::DecimalFormatSymbols(loc, INTL_DATA_ERROR_CODE(nfo));
+			if (U_FAILURE(INTL_DATA_ERROR_CODE(nfo))) {
+				delete syms;
+				break;
+			}
+			FORMATTER_OBJECT(nfo) = new icu::DecimalFormat(upattern, syms, INTL_DATA_ERROR_CODE(nfo));
+			if (FORMATTER_OBJECT(nfo) == nullptr) {
+				delete syms;
+			}
+			break;
+		}
+		case UNUM_PATTERN_RULEBASED: {
+			UParseError parseError;
+			FORMATTER_OBJECT(nfo) = new icu::RuleBasedNumberFormat(upattern, loc, parseError, INTL_DATA_ERROR_CODE(nfo));
+			break;
+		}
+		case UNUM_SPELLOUT:
+			FORMATTER_OBJECT(nfo) = new icu::RuleBasedNumberFormat(icu::URBNF_SPELLOUT, loc, INTL_DATA_ERROR_CODE(nfo));
+			break;
+		case UNUM_ORDINAL:
+			FORMATTER_OBJECT(nfo) = new icu::RuleBasedNumberFormat(icu::URBNF_ORDINAL, loc, INTL_DATA_ERROR_CODE(nfo));
+			break;
+		case UNUM_DURATION:
+			FORMATTER_OBJECT(nfo) = new icu::RuleBasedNumberFormat(icu::URBNF_DURATION, loc, INTL_DATA_ERROR_CODE(nfo));
+			break;
+		case UNUM_NUMBERING_SYSTEM: {
+			UErrorCode localErr = U_ZERO_ERROR;
+			int32_t keywordLength = loc.getKeywordValue("numbers", nullptr, 0, localErr);
+			if (keywordLength > 0) {
+				FORMATTER_OBJECT(nfo) = NumberFormat::createInstance(loc, UNUM_DEFAULT, INTL_DATA_ERROR_CODE(nfo));
+			} else {
+				FORMATTER_OBJECT(nfo) = new icu::RuleBasedNumberFormat(icu::URBNF_NUMBERING_SYSTEM, loc, INTL_DATA_ERROR_CODE(nfo));
+			}
+			break;
+		}
+		case UNUM_DECIMAL_COMPACT_SHORT:
+			FORMATTER_OBJECT(nfo) = icu::CompactDecimalFormat::createInstance(loc, UNUM_SHORT, INTL_DATA_ERROR_CODE(nfo));
+			break;
+		case UNUM_DECIMAL_COMPACT_LONG:
+			FORMATTER_OBJECT(nfo) = icu::CompactDecimalFormat::createInstance(loc, UNUM_LONG, INTL_DATA_ERROR_CODE(nfo));
+			break;
+		default:
+			FORMATTER_OBJECT(nfo) = NumberFormat::createInstance(loc, static_cast<UNumberFormatStyle>(style), INTL_DATA_ERROR_CODE(nfo));
+			break;
 	}
-
+
 	if (canonicalized_locale) {
 		efree(canonicalized_locale);
 	}
diff --git a/ext/intl/formatter/formatter_parse.cpp b/ext/intl/formatter/formatter_parse.cpp
index b2dc2e8dcbd..7bbc461516b 100644
--- a/ext/intl/formatter/formatter_parse.cpp
+++ b/ext/intl/formatter/formatter_parse.cpp
@@ -16,16 +16,18 @@
 #include <config.h>
 #endif

+#include <unicode/fmtable.h>
+#include <unicode/curramt.h>
+#include "../intl_convertcpp.h"
+#include "formatter_class.h"
+#include "formatter_format.h"
+
 extern "C" {
 #include "php_intl.h"
-#include "intl_convert.h"
 }

-#include <unicode/ustring.h>
 #include <locale.h>
-
-#include "formatter_class.h"
-#include "formatter_format.h"
+#include <memory>

 #define ICU_LOCALE_BUG 1

@@ -33,14 +35,9 @@ extern "C" {
 U_CFUNC PHP_FUNCTION( numfmt_parse )
 {
 	zend_long type = FORMAT_TYPE_DOUBLE;
-	UChar* sstr = NULL;
-	int32_t sstr_len = 0;
 	char* str = NULL;
 	size_t str_len;
-	int32_t val32, position = 0;
-	int64_t val64;
-	double val_double;
-	int32_t* position_p = NULL;
+	int32_t position = 0;
 	zval *zposition = NULL;
 	char *oldlocale;
 	FORMATTER_METHOD_INIT_VARS;
@@ -54,14 +51,14 @@ U_CFUNC PHP_FUNCTION( numfmt_parse )

 	if (zposition) {
 		position = (int32_t) zval_get_long(zposition);
-		position_p = &position;
 	}

 	/* Fetch the object. */
 	FORMATTER_METHOD_FETCH_OBJECT;

 	/* Convert given string to UTF-16. */
-	intl_convert_utf8_to_utf16(&sstr, &sstr_len, str, str_len, &INTL_DATA_ERROR_CODE(nfo));
+	icu::UnicodeString ustr;
+	intl_stringFromChar(ustr, str, str_len, &INTL_DATA_ERROR_CODE(nfo));
 	INTL_METHOD_CHECK_STATUS( nfo, "String conversion to UTF-16 failed" );

 #if ICU_LOCALE_BUG && defined(LC_NUMERIC)
@@ -72,21 +69,38 @@ U_CFUNC PHP_FUNCTION( numfmt_parse )

 	switch(type) {
 		case FORMAT_TYPE_INT32:
-			val32 = unum_parse(FORMATTER_OBJECT(nfo), sstr, sstr_len, position_p, &INTL_DATA_ERROR_CODE(nfo));
-			RETVAL_LONG(val32);
-			break;
 		case FORMAT_TYPE_INT64:
-			val64 = unum_parseInt64(FORMATTER_OBJECT(nfo), sstr, sstr_len, position_p, &INTL_DATA_ERROR_CODE(nfo));
-			if(val64 > ZEND_LONG_MAX || val64 < ZEND_LONG_MIN) {
-				RETVAL_DOUBLE(val64);
+		case FORMAT_TYPE_DOUBLE:
+		{
+			icu::Formattable result;
+			icu::ParsePosition pp(position);
+			FORMATTER_OBJECT(nfo)->parse(ustr, result, pp);
+
+			if (pp.getErrorIndex() >= 0) {
+				INTL_DATA_ERROR_CODE(nfo) = U_PARSE_ERROR;
 			} else {
-				RETVAL_LONG((zend_long)val64);
+				position = pp.getIndex();
+				switch(type) {
+					case FORMAT_TYPE_INT32:
+						RETVAL_LONG(result.getLong(INTL_DATA_ERROR_CODE(nfo)));
+						break;
+					case FORMAT_TYPE_INT64:
+					{
+						int64_t val64 = result.getInt64(INTL_DATA_ERROR_CODE(nfo));
+						if(val64 > ZEND_LONG_MAX || val64 < ZEND_LONG_MIN) {
+							RETVAL_DOUBLE(val64);
+						} else {
+							RETVAL_LONG((zend_long)val64);
+						}
+						break;
+					}
+					case FORMAT_TYPE_DOUBLE:
+						RETVAL_DOUBLE(result.getDouble(INTL_DATA_ERROR_CODE(nfo)));
+						break;
+				}
 			}
 			break;
-		case FORMAT_TYPE_DOUBLE:
-			val_double = unum_parseDouble(FORMATTER_OBJECT(nfo), sstr, sstr_len, position_p, &INTL_DATA_ERROR_CODE(nfo));
-			RETVAL_DOUBLE(val_double);
-			break;
+		}
 		case FORMAT_TYPE_CURRENCY:
 			if (hasThis()) {
 				const char *space;
@@ -113,10 +127,6 @@ cleanup:
 	efree(oldlocale);
 #endif

-	if (sstr) {
-		efree(sstr);
-	}
-
 	INTL_METHOD_CHECK_STATUS( nfo, "Number parsing failed" );
 }
 /* }}} */
@@ -124,14 +134,8 @@ cleanup:
 /* {{{ Parse a number as currency. */
 U_CFUNC PHP_FUNCTION( numfmt_parse_currency )
 {
-	double number;
-	UChar currency[5] = {0};
-	UChar* sstr = NULL;
-	int32_t sstr_len = 0;
-	zend_string *u8str;
 	char *str;
 	size_t str_len;
-	int32_t* position_p = NULL;
 	int32_t position = 0;
 	zval *zcurrency, *zposition = NULL;
 	FORMATTER_METHOD_INIT_VARS;
@@ -147,27 +151,34 @@ U_CFUNC PHP_FUNCTION( numfmt_parse_currency )
 	FORMATTER_METHOD_FETCH_OBJECT;

 	/* Convert given string to UTF-16. */
-	intl_convert_utf8_to_utf16(&sstr, &sstr_len, str, str_len, &INTL_DATA_ERROR_CODE(nfo));
+	icu::UnicodeString ustr;
+	intl_stringFromChar(ustr, str, str_len, &INTL_DATA_ERROR_CODE(nfo));
 	INTL_METHOD_CHECK_STATUS( nfo, "String conversion to UTF-16 failed" );

 	if(zposition) {
 		position = (int32_t) zval_get_long(zposition);
-		position_p = &position;
 	}

-	number = unum_parseDoubleCurrency(FORMATTER_OBJECT(nfo), sstr, sstr_len, position_p, currency, &INTL_DATA_ERROR_CODE(nfo));
-	if(zposition) {
-		ZEND_TRY_ASSIGN_REF_LONG(zposition, position);
+	icu::ParsePosition pp(position);
+	std::unique_ptr<icu::CurrencyAmount> currAmt(FORMATTER_OBJECT(nfo)->parseCurrency(ustr, pp));
+
+	if (currAmt == nullptr || pp.getErrorIndex() >= 0) {
+		INTL_DATA_ERROR_CODE(nfo) = U_PARSE_ERROR;
+		INTL_METHOD_CHECK_STATUS( nfo, "Number parsing failed" );
 	}
-	if (sstr) {
-		efree(sstr);
+
+	if(zposition) {
+		ZEND_TRY_ASSIGN_REF_LONG(zposition, pp.getIndex());
 	}
-	INTL_METHOD_CHECK_STATUS( nfo, "Number parsing failed" );
+
+	double number = currAmt->getNumber().getDouble(INTL_DATA_ERROR_CODE(nfo));

 	/* Convert parsed currency to UTF-8 and pass it back to caller. */
-	u8str = intl_convert_utf16_to_utf8(currency, u_strlen(currency), &INTL_DATA_ERROR_CODE(nfo));
+	icu::UnicodeString ucurrency(currAmt->getISOCurrency());
+
+	zend_string *u8str = intl_charFromString(ucurrency, &INTL_DATA_ERROR_CODE(nfo));
 	INTL_METHOD_CHECK_STATUS( nfo, "Currency conversion to UTF-8 failed" );
-	ZEND_TRY_ASSIGN_REF_NEW_STR(zcurrency, u8str);
+	ZEND_TRY_ASSIGN_REF_STR(zcurrency, u8str);

 	RETVAL_DOUBLE( number );
 }
diff --git a/ext/intl/listformatter/listformatter_class.cpp b/ext/intl/listformatter/listformatter_class.cpp
index f39e86f6673..817b3d74369 100644
--- a/ext/intl/listformatter/listformatter_class.cpp
+++ b/ext/intl/listformatter/listformatter_class.cpp
@@ -14,22 +14,27 @@

 extern "C" {
 #include "php.h"
+}
+
+#include <unicode/listformatter.h>
+#include <unicode/locid.h>
+#include "../intl_convertcpp.h"
+
+extern "C" {
 #include "php_intl.h"
-#include "intl_convert.h"
 }
-#include <unicode/ulistformatter.h>
 #include "listformatter_class.h"
 #include "listformatter_arginfo.h"

+#include <memory>
+
 static zend_object_handlers listformatter_handlers;

 static void listformatter_free_obj(zend_object *object)
 {
     ListFormatter_object *obj = php_intl_listformatter_fetch_object(object);

-    if( obj->lf_data.ulistfmt )
-        ulistfmt_close( obj->lf_data.ulistfmt );
-
+    delete obj->lf_data.ulistfmt;
     obj->lf_data.ulistfmt = nullptr;
     intl_error_reset( &obj->lf_data.error );

@@ -78,7 +83,7 @@ PHP_METHOD(IntlListFormatter, __construct)
         RETURN_THROWS();
     }

-    if (strlen(uloc_getISO3Language(locale)) == 0) {
+    if (icu::Locale(locale).getISO3Language()[0] == '\0') {
         zend_argument_value_error(1, "\"%s\" is invalid", locale);
         RETURN_THROWS();
     }
@@ -89,13 +94,13 @@ PHP_METHOD(IntlListFormatter, __construct)
             zend_argument_value_error(2, "must be one of IntlListFormatter::TYPE_AND, IntlListFormatter::TYPE_OR, or IntlListFormatter::TYPE_UNITS");
             RETURN_THROWS();
         }
-
+
         if (width != ULISTFMT_WIDTH_WIDE && width != ULISTFMT_WIDTH_SHORT && width != ULISTFMT_WIDTH_NARROW) {
             zend_argument_value_error(3, "must be one of IntlListFormatter::WIDTH_WIDE, IntlListFormatter::WIDTH_SHORT, or IntlListFormatter::WIDTH_NARROW");
             RETURN_THROWS();
         }

-        LISTFORMATTER_OBJECT(obj) = ulistfmt_openForType(locale, static_cast<UListFormatterType>(type), static_cast<UListFormatterWidth>(width), &status);
+        LISTFORMATTER_OBJECT(obj) = ListFormatter::createInstance(icu::Locale(locale), static_cast<UListFormatterType>(type), static_cast<UListFormatterWidth>(width), status);
     #else
         if (type != INTL_LISTFORMATTER_FALLBACK_TYPE_AND) {
             zend_argument_value_error(2, "contains an unsupported type. ICU 66 and below only support IntlListFormatter::TYPE_AND");
@@ -107,7 +112,7 @@ PHP_METHOD(IntlListFormatter, __construct)
             RETURN_THROWS();
         }

-        LISTFORMATTER_OBJECT(obj) = ulistfmt_open(locale, &status);
+        LISTFORMATTER_OBJECT(obj) = ListFormatter::createInstance(icu::Locale(locale), status);
     #endif

     if (U_FAILURE(status)) {
@@ -131,84 +136,47 @@ PHP_METHOD(IntlListFormatter, format)
         RETURN_EMPTY_STRING();
     }

-    const UChar **items = (const UChar **)safe_emalloc(count, sizeof(const UChar *), 0);
-    int32_t *itemLengths = (int32_t *)safe_emalloc(count, sizeof(int32_t), 0);
+    std::unique_ptr<UnicodeString[]> items(new UnicodeString[count]);
     uint32_t i = 0;
     zval *val;

     ZEND_HASH_FOREACH_VAL(ht, val) {
         zend_string *str_val, *tmp_str;
-
-        str_val = zval_get_tmp_string(val, &tmp_str);
-
-        // Convert PHP string to UTF-16
-        UChar *ustr = nullptr;
-        int32_t ustr_len = 0;
-        UErrorCode status = U_ZERO_ERROR;
-
-        intl_convert_utf8_to_utf16(&ustr, &ustr_len, ZSTR_VAL(str_val), ZSTR_LEN(str_val), &status);
+        UErrorCode conv_status = U_ZERO_ERROR;
+
+        str_val = zval_try_get_tmp_string(val, &tmp_str);
+        if (UNEXPECTED(!str_val)) {
+            RETURN_THROWS();
+        }
+        intl_stringFromChar(items[i], ZSTR_VAL(str_val), ZSTR_LEN(str_val), &conv_status);
         zend_tmp_string_release(tmp_str);

-        if (U_FAILURE(status)) {
-            // We can't use goto cleanup because items and itemLengths are incompletely allocated
-            for (uint32_t j = 0; j < i; j++) {
-                efree((void *)items[j]);
-            }
-            efree(items);
-            efree(itemLengths);
-            intl_error_set(nullptr, status, "Failed to convert string to UTF-16");
+        if (U_FAILURE(conv_status)) {
+            intl_error_set(nullptr, conv_status, "Failed to convert string to UTF-16");
             RETURN_FALSE;
         }
-
-        items[i] = ustr;
-        itemLengths[i] = ustr_len;
+
         i++;
     } ZEND_HASH_FOREACH_END();

     UErrorCode status = U_ZERO_ERROR;
-    int32_t resultLength;
-    UChar *result = nullptr;
-    zend_string *ret = nullptr;
-
-    resultLength = ulistfmt_format(LISTFORMATTER_OBJECT(obj), items, itemLengths, count, nullptr, 0, &status);
-
-    if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) {
-        intl_error_set(nullptr, status, "Failed to format list");
-        RETVAL_FALSE;
-        goto cleanup;
-    }
+    UnicodeString result;

-    // Allocate buffer and try again
-    status = U_ZERO_ERROR;
-    result = (UChar *)safe_emalloc(resultLength + 1, sizeof(UChar), 0);
-    ulistfmt_format(LISTFORMATTER_OBJECT(obj), items, itemLengths, count, result, resultLength, &status);
+    LISTFORMATTER_OBJECT(obj)->format(items.get(), count, result, status);

     if (U_FAILURE(status)) {
-        if (result) {
-            efree(result);
-        }
         intl_error_set(nullptr, status, "Failed to format list");
-        RETVAL_FALSE;
-        goto cleanup;
+        RETURN_FALSE;
     }

-    // Convert result back to UTF-8
-    ret = intl_convert_utf16_to_utf8(result, resultLength, &status);
-    efree(result);
-
+    zend_string *ret = intl_charFromString(result, &status);
+
     if (!ret) {
         intl_error_set(nullptr, status, "Failed to convert result to UTF-8");
-        RETVAL_FALSE;
-    } else {
-        RETVAL_NEW_STR(ret);
+        RETURN_FALSE;
     }

-cleanup:
-    for (i = 0; i < count; i++) {
-        efree((void *)items[i]);
-    }
-    efree(items);
-    efree(itemLengths);
+    RETVAL_STR(ret);
 }

 PHP_METHOD(IntlListFormatter, getErrorCode)
@@ -236,7 +204,7 @@ void listformatter_register_class(void)
 {
     zend_class_entry *class_entry = register_class_IntlListFormatter();
     class_entry->create_object = listformatter_create_object;
-
+
     memcpy(&listformatter_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
     listformatter_handlers.offset = offsetof(ListFormatter_object, zo);
     listformatter_handlers.free_obj = listformatter_free_obj;
diff --git a/ext/intl/listformatter/listformatter_class.h b/ext/intl/listformatter/listformatter_class.h
index dc40b7a8be0..6f8676193df 100644
--- a/ext/intl/listformatter/listformatter_class.h
+++ b/ext/intl/listformatter/listformatter_class.h
@@ -21,14 +21,20 @@
 #include "intl_error.h"
 #include "intl_data.h"

-#include <unicode/ulistformatter.h>
+#include <unicode/listformatter.h>
+
+#ifdef __cplusplus
+using icu::ListFormatter;
+#else
+typedef void ListFormatter;
+#endif

 typedef struct {
     // error handling
     intl_error      error;

     // formatter handling
-    UListFormatter*  ulistfmt;
+    ListFormatter*  ulistfmt;
 } listformatter_data;

 typedef struct {
diff --git a/ext/intl/rangeformatter/rangeformatter_class.cpp b/ext/intl/rangeformatter/rangeformatter_class.cpp
index 5a19df08316..6921791e588 100644
--- a/ext/intl/rangeformatter/rangeformatter_class.cpp
+++ b/ext/intl/rangeformatter/rangeformatter_class.cpp
@@ -22,6 +22,7 @@ extern "C" {
 #include <unicode/numberrangeformatter.h>
 #include <unicode/numberformatter.h>
 #include <unicode/unistr.h>
+#include <unicode/locid.h>
 #include "../intl_convertcpp.h"

 extern "C" {
@@ -30,7 +31,6 @@ extern "C" {
     #include "../intl_data.h"
     #include "rangeformatter_arginfo.h"
     #include "rangeformatter_class.h"
-    #include "intl_convert.h"
 }

 using icu::number::NumberRangeFormatter;
@@ -91,7 +91,7 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton)
         RETURN_THROWS();
     }

-    if (strlen(uloc_getISO3Language(locale)) == 0) {
+    if (icu::Locale(locale).getISO3Language()[0] == '\0') {
         zend_argument_value_error(2, "\"%s\" is invalid", locale);
         RETURN_THROWS();
     }
diff --git a/ext/intl/tests/formatter_fail.phpt b/ext/intl/tests/formatter_fail.phpt
index 53f6b4ac7c7..678b87b8c1b 100644
--- a/ext/intl/tests/formatter_fail.phpt
+++ b/ext/intl/tests/formatter_fail.phpt
@@ -137,9 +137,9 @@ function crt($t, $l, $s) {
 'U_ZERO_ERROR'

 IntlException: NumberFormatter::__construct(): number formatter creation failed
-'NumberFormatter::__construct(): number formatter creation failed: U_UNSUPPORTED_ERROR'
-'NumberFormatter::create(): number formatter creation failed: U_UNSUPPORTED_ERROR'
-'numfmt_create(): number formatter creation failed: U_UNSUPPORTED_ERROR'
+'NumberFormatter::__construct(): number formatter creation failed: U_ILLEGAL_ARGUMENT_ERROR'
+'NumberFormatter::create(): number formatter creation failed: U_ILLEGAL_ARGUMENT_ERROR'
+'numfmt_create(): number formatter creation failed: U_ILLEGAL_ARGUMENT_ERROR'

 IntlException: NumberFormatter::__construct(): number formatter creation failed
 'NumberFormatter::__construct(): number formatter creation failed: U_MEMORY_ALLOCATION_ERROR'