0

I have the following code which I believe should output fr_FR as the locale but for some reason is outputting en_US_POSIX (it does this on any timezone). What have I done wrong?

$loc = IntlCalendar::createInstance(new DateTimeZone('Europe/Paris'));
echo $loc->getLocale(Locale::VALID_LOCALE);

For refs: https://www.php.net/manual/en/intlcalendar.createinstance.php and https://www.php.net/manual/en/intlcalendar.getlocale.php

As it appears this isn't the correct way (even though the code is valid) - is there a more suitable way to find a "default" locale for a given timezone?

Antony
  • 3,875
  • 30
  • 32
  • The manual clearly states that if you don't pass a locale as second parameter, the default will be used. And that default is the `intl.default_locale` configuration setting. – CBroe Jul 26 '23 at 11:36
  • Did you assume there was any _connection_ between the time zone, and the locale? No, why would there be. If I am looking at an American website that happens to tell me about an event taking place in Japan - then despite the Japanese time zone of that event, I probably still want to see the date in American "format". – CBroe Jul 26 '23 at 11:43

3 Answers3

1

You can start by getting the country (and country code) associated with a given timezone:

$userTimezone = new DateTimeZone('Europe/Paris');

$location = $userTimezone->getLocation();
/*
array(4) {
  ["country_code"]=>  string(2) "FR"
  ["latitude"]=>  float(48.866659999999996)
  ["longitude"]=>  float(2.3333299999999895)
  ["comments"]=>  string(0) ""
}
*/

$countryCode = $location['country_code'];

You can then combine that information with the resources available in the ICU library to get the most-likely language of a given country code:

// From @ausi's answer in https://stackoverflow.com/a/58512299/1456201
function getLanguage(string $country): string {
    $subtags = \ResourceBundle::create('likelySubtags', 'ICUDATA', false);
    $country = \Locale::canonicalize('und_'.$country);
    $locale = $subtags->get($country) ?: $subtags->get('und');
    return \Locale::getPrimaryLanguage($locale);
}

Note that that will not be correct for every user. It's a decent default starting place, but you should always ask the user what their language preference is.

$possibleLocale = getLanguage($countryCode) . '_' . $countryCode; // fr_FR
Jim
  • 3,210
  • 2
  • 17
  • 23
0

You set the time zone to that of Paris. But you didn't set the locale. They're different things. Locale defines language and formatting conventions, whereas time zone sets the rules for converting UTC to local time and back again. What you have defined is suitable for An American in Paris. That's a valid use case, especially in August!

Try this:

$loc = IntlCalendar::createInstance( new DateTimeZone( 'Europe/Paris' ), 'fr_FR' );
echo $loc->getLocale( Locale::VALID_LOCALE );
O. Jones
  • 103,626
  • 17
  • 118
  • 172
  • I like your illustration. I was trying to work out a "default" locale for a given timezone and had thought this was the best way ... if not is there a better way? – Antony Jul 26 '23 at 12:02
0

The ICU/INTL locale is not based on the timezone. It's using the default value that can be set either through the PHP ini settings or anywhere in your code.

In order for the locale to be different you can change it in the INI settings (this will be used by any PHP script that runs using that INI file). Alternatively, you can specify it in your code either very early on (similar to using the INI setting), or you can specify it in various INTL class methods (such as when you have a user-specific locale you want to use to display information formatted in a way the user might expect), but not affect code outside of the user-specific portion:

// These values could be pulled from any source
// such as database.
$userLocale = 'fr_FR';
$userTimezone = new DateTimeZone('Europe/Paris');

$userCalendar = IntlCalendar::createInstance($userTimezone, $userLocale);
echo $userCalendar->getLocale(Locale::VALID_LOCALE); // fr_FR
Jim
  • 3,210
  • 2
  • 17
  • 23
  • Ah but I'm trying to find out what the locale is from a given timezone. It seemed on reading this was the best way to do that. If not, is there a better way? – Antony Jul 26 '23 at 12:01
  • @Antony See my new answer. – Jim Jul 26 '23 at 12:59