Search code examples
phplocalefreebsdstrftime

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.


Solution

  • 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)