Skip to content

MessageFormatter uses default timezone to format dates #10956

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
MatTheCat opened this issue Mar 27, 2023 · 5 comments
Open

MessageFormatter uses default timezone to format dates #10956

MatTheCat opened this issue Mar 27, 2023 · 5 comments

Comments

@MatTheCat
Copy link

Description

The following code:

<?php

date_default_timezone_set('UTC');

echo MessageFormatter::formatMessage(
    'fr_FR',
    '{datetime, time, short}',
    ['datetime' => new DateTimeImmutable('2023-03-27T12:00', new DateTimeZone('Europe/Paris'))]
);

Resulted in this output:

10:00

But I expected this output instead:

12:00

PHP Version

all

Operating System

No response

@damianwadley
Copy link
Member

The main problem I see is that ICU's MessageFormatter doesn't seem to support this sort of behavior: internally, the date being passed for formatting is actually just a numeric timestamp. Which means there's a big question of what to do if you try to format a message with multiple DateTimes in different timezones...

I'm changing this to a feature request of having a way to specify the timezone on the MessageFormatter directly, instead of relying on PHP's default timezone with no way (that I saw) to change that.

@MatTheCat
Copy link
Author

Okay I guess this won’t be simple to implement 😅

As a workaround I’ll pass datetimes as strings returned by IntlDateFormatter.

@heiglandreas
Copy link
Contributor

I'd suggest to set the default timezone to Europe/Paris to get the correct result.

@MatTheCat
Copy link
Author

In my case the timezone is dynamic so I cannot act on the server's.

@nielsdos
Copy link
Member

It is actually possible to fix this by including the timezone offset in the milliseconds. So this would already work if the default timezone is set to UTC:

diff --git a/ext/intl/common/common_date.cpp b/ext/intl/common/common_date.cpp
index e4f442a8195..1cf3e7da56c 100644
--- a/ext/intl/common/common_date.cpp
+++ b/ext/intl/common/common_date.cpp
@@ -113,9 +113,13 @@ U_CFUNC zend_result intl_datetime_decompose(zend_object *obj, double *millis, Ti
 
 	if (millis) {
 		auto getTimestampMethod = static_cast<zend_function *>(zend_hash_str_find_ptr(&obj->ce->function_table, ZEND_STRL("gettimestamp")));
+		auto getOffsetMethod = static_cast<zend_function *>(zend_hash_str_find_ptr(&obj->ce->function_table, ZEND_STRL("getoffset")));
 		zval retval;
+		double timestamp;
 
 		ZEND_ASSERT(getTimestampMethod && "DateTimeInterface is sealed and thus must have this method");
+		ZEND_ASSERT(getOffsetMethod && "DateTimeInterface is sealed and thus must have this method");
+
 		zend_call_known_function(getTimestampMethod, obj, obj->ce, &retval, 0, nullptr, nullptr);
 
 		/* An exception has occurred */
@@ -130,7 +134,25 @@ U_CFUNC zend_result intl_datetime_decompose(zend_object *obj, double *millis, Ti
 			return FAILURE;
 		}
 
-		*millis = U_MILLIS_PER_SECOND * (double)Z_LVAL(retval) + (datetime->time->us / 1000);
+		timestamp = (double) Z_LVAL(retval);
+
+		zend_call_known_function(getOffsetMethod, obj, obj->ce, &retval, 0, nullptr, nullptr);
+
+		/* An exception has occurred */
+		if (Z_TYPE(retval) == IS_UNDEF) {
+			return FAILURE;
+		}
+		// TODO: Remove this when DateTimeInterface::getoffset() no longer has a tentative return type
+		if (Z_TYPE(retval) != IS_LONG) {
+			spprintf(&message, 0, "%s: %s::getoffset() did not return an int", func, ZSTR_VAL(obj->ce->name));
+			intl_errors_set(err, U_INTERNAL_PROGRAM_ERROR, message, 1);
+			efree(message);
+			return FAILURE;
+		}
+
+		timestamp += (double) Z_LVAL(retval);
+
+		*millis = U_MILLIS_PER_SECOND * timestamp + (datetime->time->us / 1000);
 	}
 
 	if (tz) {

It's incomplete though because we need to not let the default timezone influence the output or the timezone offset will be added twice. Also needs a review of all other cases that this influences but jfc the intl code is a mess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants