Перевод часов в России, опять… и php5-intl


    Доброе %время суток%.

    Предыстория


    Ничего не предвещало беды. Задолго до были обновлены tzdata и всё, до чего могли руки дотянуться. Но в очередной момент перевода часов мой сайт стал выдавать московское время на час больше. Беглая проверка показала, что сама OS, mysql и php (функция date) возвращают время корректно и указывают часовой пояс +3 для Москвы. Собака же зарыта оказалась в хорошем расширении php5-intl. Функция format класса IntlDateFormatter упорно возвращала часовой пояс +4 для Москвы.

    Исследование


    В начале гугл не хотел мне помогать… но вскоре я узнал о таком звере, как icu и еще одной базе временных зон, которые, как ни странно, так и не были обновлены в моей системе. И всё бы хорошо… apt-get update… apt-get upgrade… и… отсутствие обновления для libicu фиг знает сколько времени. Даже хорошие люди баг завели в Debian по этому поводу. Но воз и ныне там. Гугл упорно ничего хорошего не советовал. Всё упиралось в ручную сборку свежей версии. Этот вариант даже был скромно освещен на хабре. Я, как человек далекий от мира Linux, чуть было не угробил систему в попытках что либо собрать. В итоге, с помощью хорошего друга lsh и совместного усиленного гугления мы нашли простой способ обновить данные для libicu не прибегая к компиляции или другим шаманствам.

    Решение


    Оказалось, что libicu, будучи даже скомпилированной с прямым включением данных таймзон и т.д. внутрь .so, оставляет возможность подсунуть свежую их версию через указания папки, откуда она могла бы их взять. Делается это через переменную окружения ICU_DATA. Найдя все кусочки пазла и пройдя пару неудачных попыток появилось простое и готовое к употреблению решение:

    • Необходимо скачать свежие версии данных таймзон и т.д. по ссылке с оф.сайта icu-project.
      Файлы необходимо взять из папочки «le» (little-endian, вероятно), по крайней мере именно они подошли мне на системе с Intel'овским процком и Debian x64. Там еще есть папочка «be» (big-endian) и загадочная «ee» (кто знает, для кого она?)
    • Разместить скаченные 4 файла в директорию, например: /opt/icu/icudt48l/ (для версии libicu 4.8)
    • Указать соответствующую переменную окружения:
      • Для php-cgi указать её можно в системном /etc/environment:
        ICU_DATA=/opt/icu/

      • Для php-fpm в настройках конкретного воркера:
        env[ICU_DATA]=/opt/icu/

      • Для Apache необходимо включить mod_env и в htaccess прописать:
        SetEnv ICU_DATA /opt/icu/


    И, нет, я не ошибся разместив файлы в icu/icudt48l/ а в путях указав только папку icu. Всё дело в том, что libicu сам подставляет к пути ICU_DATA папку исходя из своей версии (зачем?!). Так что, в будущем, при обновлении libicu, папку придется переименовать в другое имя. Судя по наблюдением, название папки формируется как:
    icudt - icu data
    48 - версия libicu (4.8 -> 48, 3.6 -> 36)
    l - порядок байт (l - little-endian, b - big-endian)
    
    Соответственно папку вам необходимо называть исходя из версии libicu в вашей системе.

    Перезапуск php-fpm и… профит ) теперь можно смело убрать костыль с подстановкой вместо Europe/Moscow таймзону Europe/Minsk :)

    Кстати, вопрос, почему php-fpm игнорирует глобальный env? Если кто найдет способ указать глобально для всех воркеров php-fpm данную настройку, тому большое человеческое спасибо.
    @symbix отмечает, что если в php-fpm выключить настройку clear_env, то и все воркеры php-fpm начнут видеть глобальный env, т.е. не придется для каждого воркера отдельно указывать env[ICU_DATA]. Но проблемой становится уже то, что как раз воркеры начинают видеть лишнего, а не только нужную нам строку.


    Как ни странно, но этот простой способ было сложно нагуглить. Я, как и многие до меня, столкнувшись с этой проблемой, пытались пересобирать, вытаскивать из свежих тестовых сборок дистрибутивов и много еще чего. И что еще более странно, так это то, что данный способ только мельком упоминается на сайте самих icu. Надеюсь я закрыл для вас этот вопрос.

    Спасибо и безбажного вам кодинга!
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 23
    • +1
      >И, нет, я не ошибся разместив файлы в icu/icudt48l/ а в путях указав только папку icu.
      Кстати, забавное дополнение. Чтение всей документации я не осилил, хотя там про это должно быть. А про переменную окружения и необходимость создания поддиректории узнал из выхлопа strace. =)
      • +1
        >>Кстати, вопрос, почему php-fpm игнорирует глобальный env?

        Оно?

        List of pool directives
        clear_env boolean
        Clear environment in FPM workers.

        php.net/manual/en/install.fpm.configuration.php
        • 0
          Да, похоже оно. Но получается как то не секурно. Либо мы чистим evn для всех воркеров и указываем у каждого из них в настройках свои env, либо до воркеров доносим весь env целиком. Было бы здорово найти способ указать конкретную переменную окружения для всех воркеров, но без доступа воркеров к оригинальному env.
        • +4
          Пример типичного преступления в программировании. Вместо того чтобы опереться на стандартные библиотеки, разработчики этой php-intl встраивают свою реализацию функциональности tzdata. При этом наверняка зависимость от libc уже есть, так что от такой реализации ничего не экономится.
          • +2
            У libc есть фатальный недостаток.
          • +1
            так libicu стандартная библиотека (99.9%, что она используется в вашем браузере), в php-intl нет встроенной tzdata (есть в самом php но это другая история)
            А у libicu еще и свой формат с миллисекундами (помоему), но в качесве источника при конвертации используется Olson tz data
            • +2
              Остается только вопрос, почему столь важную библиотеку не чешутся регулярно обновлять. Точнее даже не обновлять (в плане версии), а пересобирать с новыми TZ и т.д. Собственно я находил в том же Debian'e и более ранний баг, нежели тот, что указан в статье, когда говорили об косяке с TZ для Киева (это был 2012 год кажется), фикс по этому багу сделали в 2013 и он до сих пор только в Experemental дистрибутиве. Ппц какой то.
              • +2
                То есть виновата не php-intl, а другая библа?

                Насколько я понимаю, база Олсона является корневым источником информации о часовых поясах. Но в системе логично было бы иметь ровно один пакет, который часто обновляется из этой базы, а все другие пакеты использовали бы его.

                Кстати, почему не делают динамическое обновление? Если уже используется ntp-сервер, который довольно часто лазит в интернет, то почему бы ему (или другому, специально выделенному, демону) дополнительно не ходить изредка за tzdata?
              • 0
                Дело в том, что в libc не выставлены наружу функции для работы с таймзонами. Единственное, что можно сделать — это поменять таймзону для своего процесса, что, мягко говоря, не подходит для многопоточных или асинхронных программ.
                • 0
                  Но можно было провести разрез пониже: использовать готовый интерфейс доступа к базе tzdata. Даже без учета скоропалительности правительства РФ было бы естественно предположить, что поддержка базы в актуальном состоянии — дело трудное и неблагодарное, и что лучше оставить это дело тем, кто уже впрягся этим заниматься.
                  • +1
                    Так его нету, он внутри libc запрятан. Всё, что торчит наружу — это функция tzset. Или вы предлагаете внутри libc провести разрез иначе?
                    • 0
                      Хм, действительно, tzset() работает с глобальными переменными, что не очень хорошо. Тем не менее, разрез существует, правда, корявый: самостоятельно парсить файлы, принадлежащие tzdata (из /usr/share/zoneinfo/ или где они там живут). Конечно, неудобно, но зато гарантируется, что при обновлении tzdata ничего не потеряется. Еще нужно подумать о том, как быть со старыми процессами, которые живут дольше, чем возраст последнего обновления tzdata, но эта проблема должна решаться на другом уровне.
                    • 0
                      Вообще, если обратить внимание на tzdata, можно заметить, что последняя и без правительства РФ обновляется раз по 10 в год, а то и больше. Вроде бы icu не используют tzdata из-за уже упомянутого «фатального недостатка», но на поверку — tzdata обновляется гораздо лучше; думаю — будет всё-таки неплохо, если кто-то допишет поддержку/заведёт форк icu с поддержкой tzdata.
                • 0
                  Данный способ не работает для libicu 3.6 и php-cgi 5.2.17
                  • 0
                    А вы папку какую указывали? по логике нужно класть в папку по виду /opt/icu/icudt36l/ (для вашей версии). Но опять же, мы с lsh, узнали эту папку по сути прямым трейсом пхп во время запроса к intlDateFormatter::format. Таким образом и определили точный путь, куда необходимо класть файлы. Так же попадались в документации по ICU какие то фразы про ограничения на старые версии, где это не работает. Но вроде всё это касалось ICU 2.0 и ниже (но могу ошибаться).

                    Вы можете обратиться к lsh думаю он не откажет вам в помощи, как получить эту самую папку.
                    • 0
                      Опять же… у вас просто зона не пофиксилась, или intlDateFormatter перестал работать? У нас функция переставала работать, если пути правильные, а вот версия самих файлок .res не правильная (le/be).
                      • 0
                        Я указывал /opt/icu/icudt36/. Указал /opt/icu/icudt36l/ и заработало, но только в cli. Через веб всё равно показывает +4.
                        • 0
                          Вы что используете для сервера приложений? Apache+mod_php или php-fpm? Если апачь, то в htaccess необходимо указать переменную окружения ICU_DATA, если php-fpm, то рецепт есть в статье. Ибо, вероятно, как и в случае с php-fpm системные переменные окружения не применяются к воркерам php в рамках веб-сервера.

                          P.S. В апаче похоже нужно включать mod_env, что бы заработала возможность указания envilopment variables: stackoverflow.com/questions/17550223/set-an-environment-variable-in-htaccess-and-retrieve-it-in-php
                          • 0
                            php-cgi. помогло добавление модуля timezonedb
                            • 0
                              Это вы решили параллельную проблему с обновлением TZ самого php

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Интересные публикации