Alex G Alex G - 29 days ago 19
PHP Question

strftime() returns incorrect preferred time format

I got PHP 7.0.8 (FPM) running on FreeBSD 10.1 and nginx. I need to display time in preferred format by the country where user resides.

setlocale(LC_ALL, "ru_RU.UTF-8");
date_default_timezone_set("Europe/Moscow");
echo strftime('%X', time());
// Returns 21:23:12 (correct) because 24-hr format is preferred in Russia.

setlocale(LC_ALL, "en_US.UTF-8");
date_default_timezone_set("America/New_York");
echo strftime('%X', time());
// Returns 21:23:12 (incorrect) must return 9:23:12 pm as preferred format in U.S.


This looks like an issue with my server or PHP version, because
other users are getting correct results.


locale -a
return contains both
ru_RU.UTF-8
and
en_US.UTF-8
.

echo setlocale(LC_ALL, "en_US.UTF-8")
returns correct locale.

No special configuration is applied.

Please help me to resolve this. Thanks.


%X Preferred time representation based on locale, without the date


P.S. Preferred date
%x
works correctly displaying dd.mm.yyyy for Russia and mm/dd/yyyy for U.S.

Answer

Same on FreeBSD 10.3:

php -r 'date_default_timezone_set("Europe/Paris"); var_dump(setlocale(LC_ALL, "en_US.UTF-8"), strftime("%X", time()));'
string(11) "en_US.UTF-8"
string(8) "15:03:07"

First, ls -l /usr/share/locale/en_US.UTF-8/LC_TIME returns:

/usr/share/locale/en_US.UTF-8/LC_TIME@ -> ../en_US.ISO8859-1/LC_TIME

So en_US.UTF-8 is, in fact, a symlink to en_US.ISO8859-1.

Then, If we look into /usr/src/share/timedef/en_US.ISO8859-1.src (you need sources installed), we find:

#
# X_fmt
#
%H:%M:%S

Which explains the actual result when you would expect %I:%M:%S %p (or %r).

Possible solutions:

  • fill a bug report if you think it is relevant and/or1 patch the file above then rebuild world (I guess)
  • handle this specific case:

    echo strftime(0 === strpos(setlocale(LC_ALL, '0'), 'en_US') ? '%r' : '%X');
    
  • prefer using IntlDateFormatter which does not rely on system-locales (assumed by ICU library). Eg:

    $timefmt = new IntlDateFormatter('en_US', IntlDateFormatter::NONE, IntlDateFormatter::MEDIUM);
    $timefmt->format(date_create());
    

1 it seems that X_fmt is valued to %I:%M:%S %p in trunk and 11-STABLE

Update:

  • the commit altering X_fmt was reverted since (ie on FreeBSD >= 11, X_fmt is still defined as %H:%M:%S)
  • on FreeBSD 11, the file defining time formats is /usr/src/share/timedef/en_US.UTF-8.src (the symbolic link to en_US.ISO8859-1 locale is gone)