Commit 0f20be38672 for php.net

commit 0f20be38672a08a3ebed8f4492f4a8e1872fd3f0
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Mon Aug 24 11:44:06 2026 -0400

    ext/intl: Preserve PHP-side state when cloning formatters

    IntlDateFormatter_object_clone() left date_type, time_type, calendar and
    requested_locale at their constructor defaults and MessageFormatter_object_
    clone() dropped orig_format/orig_format_len/tz_set, so a cloned formatter
    reported wrong types/calendar/pattern and lost the requested locale used by
    datefmt_set_calendar(). The clone handlers now copy these fields alongside
    the ICU handle; msgformat_data.arg_types is deliberately not copied since it
    is a lazily rebuilt cache derived from the cloned ICU formatter. Sibling
    audit: NumberFormatter, IntlCalendar, SpoofChecker and Transliterator carry
    no other PHP-side scalar state in their clone paths.

    Closes GH-23652

diff --git a/NEWS b/NEWS
index c367688672c..77689d91498 100644
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,11 @@ PHP                                                                        NEWS
     registrations are freed while still reachable from the cycle collector.
     (Ilia Alshanetsky)

+- Intl:
+  . Fixed cloning IntlDateFormatter and MessageFormatter losing PHP-side state
+    such as dateType, timeType, calendar and the message pattern.
+    (Ilia Alshanetsky)
+
 - MBString:
   . Fixed bug GH-23106 (mb_strpos() reads past the end of a haystack ending in
     a truncated UTF-8 sequence). (Lazizbek Ergashev)
diff --git a/ext/intl/dateformat/dateformat_class.c b/ext/intl/dateformat/dateformat_class.c
index 15bf5bf23ce..21bb16dcd46 100644
--- a/ext/intl/dateformat/dateformat_class.c
+++ b/ext/intl/dateformat/dateformat_class.c
@@ -71,6 +71,13 @@ zend_object *IntlDateFormatter_object_clone(zend_object *object)
 	/* clone standard parts */
 	zend_objects_clone_members(&new_dfo->zo, &dfo->zo);

+	new_dfo->date_type = dfo->date_type;
+	new_dfo->time_type = dfo->time_type;
+	new_dfo->calendar = dfo->calendar;
+	if (dfo->requested_locale != NULL) {
+		new_dfo->requested_locale = estrdup(dfo->requested_locale);
+	}
+
 	/* clone formatter object */
 	if (DATE_FORMAT_OBJECT(dfo) != NULL) {
 		UErrorCode error = U_ZERO_ERROR;
diff --git a/ext/intl/msgformat/msgformat_class.c b/ext/intl/msgformat/msgformat_class.c
index 4e0766a911b..0d5097e481d 100644
--- a/ext/intl/msgformat/msgformat_class.c
+++ b/ext/intl/msgformat/msgformat_class.c
@@ -63,6 +63,12 @@ zend_object *MessageFormatter_object_clone(zend_object *object)
 	/* clone standard parts */
 	zend_objects_clone_members(&new_mfo->zo, &mfo->zo);

+	if (mfo->mf_data.orig_format != NULL) {
+		new_mfo->mf_data.orig_format = estrndup(mfo->mf_data.orig_format, mfo->mf_data.orig_format_len);
+		new_mfo->mf_data.orig_format_len = mfo->mf_data.orig_format_len;
+	}
+	new_mfo->mf_data.tz_set = mfo->mf_data.tz_set;
+
 	/* clone formatter object */
 	if (MSG_FORMAT_OBJECT(mfo) != NULL) {
 		UErrorCode error = U_ZERO_ERROR;
diff --git a/ext/intl/tests/clone_preserves_php_fields.phpt b/ext/intl/tests/clone_preserves_php_fields.phpt
new file mode 100644
index 00000000000..1c57ce539ad
--- /dev/null
+++ b/ext/intl/tests/clone_preserves_php_fields.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Cloning IntlDateFormatter and MessageFormatter preserves PHP-side fields
+--EXTENSIONS--
+intl
+--FILE--
+<?php
+$d = new IntlDateFormatter('de_DE', IntlDateFormatter::LONG, IntlDateFormatter::MEDIUM, 'UTC', IntlDateFormatter::GREGORIAN);
+$c = clone $d;
+var_dump($c->getDateType());
+var_dump($c->getTimeType());
+var_dump($c->getCalendar());
+var_dump($c->format(strtotime('2024-01-02 03:04:05 UTC')));
+$c->setCalendar(IntlDateFormatter::GREGORIAN);
+var_dump($c->getPattern());
+
+$m = new MessageFormatter('en_US', '{0, number}');
+$mc = clone $m;
+var_dump($mc->getPattern());
+var_dump($mc->format([1.5]));
+
+date_default_timezone_set('UTC');
+$t = new MessageFormatter('en_US', '{0,time,short}');
+$dt = new DateTimeImmutable('2024-01-02 03:04:05', new DateTimeZone('UTC'));
+$t->format([$dt]);
+$tc = clone $t;
+date_default_timezone_set('America/New_York');
+var_dump($t->format([$dt]) === $tc->format([$dt]));
+?>
+--EXPECT--
+int(1)
+int(2)
+int(1)
+string(26) "2. Januar 2024 um 03:04:05"
+string(23) "d. MMMM y 'um' HH:mm:ss"
+string(11) "{0, number}"
+string(3) "1.5"
+bool(true)