Давайте начнём с простой задачки — вывести локализованную дату: там должен быть день, полное название месяца на языке локали и полный год. В наше время это действительно очень просто. В PHP есть своё i18n-расширение intl, которое входит в ядро с версии 5.3. И в этом intl есть класс IntlDateFormatter, у которого в свою очередь предопределено несколько форматов. Используем его LONG формат.

<?php

foreach (['en_US', 'ru_RU', 'es_ES', 'fa_IR'] as $locale) {
    $formatter = new IntlDateFormatter(
        $locale, 
        IntlDateFormatter::LONG, 
        IntlDateFormatter::NONE, 
        'Europe/Moscow'
    );
    echo $formatter->format(1455111783), PHP_EOL;
}

Результат:

February 10, 2016
10 февраля 2016 г.
10 de febrero de 2016
?? ?????? ???? ?. // вот тут вообще-то RTL-текст, но я хз как это правильно оформить

Пока неплохо. А теперь давайте слегка изменим условия: «вывести локализованную дату: там должен быть день и полное название месяца на языке локали». То есть, мы не хотим отображать год.

Желаемый результат выглядит так:

February 10
10 февраля
10 de febrero
?? ?????? ?. // вот тут я не уверен, что это желаемый результат, но надеюсь, никто не заметит

Вообще-то, теперь задача уже не так проста, как кажется.

Честно говоря, весь этот пост будет только и исключительно об этой задаче.

Погоди-погоди, а тебе это вообще зачем?


Некоторые, наверное, могут удивиться, какого вообще мне понадобился именно такой формат? Это довольно очевидно, если вы работаете с какой-то системой, имеющей отображение в виде ленты событий с отметками времени. Но если нет, давайте просто взглянем на Твиттер.

twitter date formats

Если твит был опубликован недавно, Твиттер покажет вам грамотно отформатированный интервал времени. Если же твит случился давно, вы увидите грамотно отформатированную дату. Более того, если твит был опубликован в этом году, то года в этой дате не будет. И это отлично, это часть хорошего UX.

Идея в том, чтобы пользователи максимально быстро понимали, когда это случилось. Лишние данные это шум.

Теперь вы знаете, зачем. Вернёмся к нашей задаче.

ОК, значит для этого должен быть отдельный формат, типа LONG, но без года, так?


Не так. В IntlDateFormatter только четыре стандартных формата: FULL, LONG, MEDIUM и SHORT, и в каждом из них присутствует год.

<?php

$formats = [
    IntlDateFormatter::FULL, 
    IntlDateFormatter::LONG, 
    IntlDateFormatter::MEDIUM, 
    IntlDateFormatter::SHORT,   
];
foreach ($formats as $format) {
    $formatter = new IntlDateFormatter(
        'en_US', 
        $format, 
        IntlDateFormatter::NONE, 
        'Europe/Moscow'
    );
    echo $formatter->format(1455111783), PHP_EOL;
}

Результат:

Wednesday, February 10, 2016
February 10, 2016
Feb 10, 2016
2/10/16

Если вы немного подумаете, то станет ясно, что невозможно заранее определить константы для каждого кастомного формата, который когда-либо может прийти в голову разработчику, дизайнеру или менеджеру.

Ха, я только что придумал простое решение!


Да ладно? Дай угадаю: ты хочешь попросту вырезать год из того, что получится после форматирования с LONG, так? Готов спорить, что так. Без примеров может быть сложновато понять, в чём проблема. Давайте просто посмотрим.

Напоминаю, что мы имеем.

February 10, 2016
10 февраля 2016 г.
10 de febrero de 2016
?? ?????? ???? ?.

Вырезаем год.

February 10, 
10 февраля г.
10 de febrero de
?? ?????? ?. 

Видите все эти остаточные артефакты типа , и г. и de и определённо что-то ещё в последней строчке, чего я не могу выделить?

Поэтому — нет, это даже и близко не решение. К слову, ничего стыдного в этом нет, это было и моим первым «быстрым решением».

Ладно, там есть паттерны, давайте используем паттерны!


Да, IntlDateFormatter в действительности внутри работает только с паттернами (константы форматов просто преобразуются в соответствующий паттерн), и при создании вы можете указать свой собственный.

Паттерн состоит из нескольких предопределённых последовательностей букв.

Посмотрим… Похоже, нам нужен паттерн "d MMMM".

<?php

foreach (['en_US', 'ru_RU', 'es_ES', 'fa_IR'] as $locale) {
    $formatter = new IntlDateFormatter(
        $locale, 
        IntlDateFormatter::NONE, 
        IntlDateFormatter::NONE, 
        'Europe/Moscow',
        null,
        "d MMMM"
    );
    echo $formatter->format(1455111783), PHP_EOL;
}

Результат:

10 February
10 февраля
10 febrero
?? ??????

Выглядит отлично! Хотя постой, это ещё что за? Ох...

Напоминаю, мы хотим

February 10
10 февраля
10 de febrero
?? ?????? ?. 

Нет, ни фига не отлично, лишь одно совпадение. Всё потому, что паттерном вы указали не только части даты (день и месяц), но ещё и порядок, в котором они должны идти, и разделители. Как если бы вы сказали «сначала должен идти номер дня, затем полное название месяца, а между ними пробел». Для любой локали. Это полная ерунда.

Правда в том, что локаль это не только язык, это ещё и паттерн форматирования даты. И паттерн это не только что включить в результат, но также в каком порядке это расставить.

<?php

foreach (['en_US', 'ru_RU', 'es_ES', 'fa_IR'] as $locale) {
    $formatter = new IntlDateFormatter(
        $locale, 
        IntlDateFormatter::LONG, 
        IntlDateFormatter::NONE, 
        'Europe/Moscow'
    );
    echo $formatter->getPattern(), PHP_EOL;
}

Результат:

MMMM d, y
d MMMM y 'г'.
d 'de' MMMM 'de' y
d MMMM y G

Смотрите, они все разные.

Но должно же быть готовое решение! Не может же быть, что его нет! Или...?


вздох Да, я думал точно так же. Как это вообще возможно, что в PHP, во взрослом языке с развитой экосистемой и одним из мощнейших коммьюнити, может не быть какого-то базового функционала? Sad but true: возможно.

В intl отсутствует как минимум одна очень важная штука — DateTimePatternGenerator класс из ICU. Он сделан ровно для того, чтобы решать нашу маленькую задачку и все прочие подобные.

Стой-стой-стой, что ещё за ICU?


ICU это "International Components for Unicode" — компоненты интернационализации для юникода.

Цитаты с сайта ICU.

ICU is a mature, widely used set of C/C++ and Java libraries providing Unicode and Globalization support for software applications. ICU is widely portable and gives applications the same results on all platforms and between C/C++ and Java software.

...

Formatting: Format numbers, dates, times and currency amounts according the conventions of a chosen locale. This includes translating month and day names into the selected language, choosing appropriate abbreviations, ordering fields correctly, etc. This data also comes from the Common Locale Data Repository.

Короче, это такой крутой набор библиотек. Расширение intl само по себе никакой магии не умеет, это что-то типа прокси к этим библиотекам.

$ php -i | grep intl -A5
intl

Internationalization support => enabled
version => 1.1.0
ICU version => 56.1
ICU Data version => 56.1

Чтобы использовать IntlDateFormatter, в вашей системе должен быть установлен ICU (или же нужно изначально собрать PHP с ICU). Разные версии ICU будут давать разные результаты форматирования.

$ dpkg -S icu
libicu52:amd64: /usr/lib/x86_64-linux-gnu/libicule.so.52.1
libicu52:amd64: /usr/lib/x86_64-linux-gnu/libicule.so.52
libicu52:amd64: /usr/lib/x86_64-linux-gnu/libicutest.so.52
...

(В системе установлена версия 52.1, а PHP, как можно видеть выше, собран с 56.1. Это нормально.)

Ясно. Ты упомянул какой-то DateTimePatternGenerator, рассказывай


Точно, DateTimePatternGenerator, это, на мой взгляд, наиболее магическая штука в ICU из тех, что про форматирование даты-времени.

Ещё одна цитата с сайта ICU:

This class provides flexible generation of date format patterns, like "yy-MM-dd".

The user can build up the generator by adding successive patterns. Once that is done, a query can be made using a "skeleton", which is a pattern which just includes the desired fields and lengths. The generator will return the "best fit" pattern corresponding to that skeleton.

The main method people will use is getBestPattern(String skeleton), since normally this class is pre-built with data from a particular locale. However, generators can be built directly from other data as well.

Это прям то, что нам надо! Мы скармливаем так называемый "skeleton" (части даты, которые нужно включить в результат форматирования) методу getBestPattern, и он возвращает наиболее подходящий паттерн, а с ним мы уже знаем, что делать: передаём в IntlDateFormatter — и готово!

Как это могло бы работать.

$skeleton = "MMMMd";
foreach (['en_US', 'ru_RU', 'es_ES', 'fa_IR'] as $locale) {
    $pgen = new IntlDateTimePatternGenerator($locale);
    $pattern = $pgen->getBestPattern($skeleton);

    $formatter = new IntlDateFormatter(
        $locale, 
        IntlDateFormatter::NONE, 
        IntlDateFormatter::NONE, 
        'Europe/Moscow',
        null,
        $pattern
    );
    echo $formatter->format(1455111783), PHP_EOL;
}

Результат (вероятно был бы таким):

February 10
10 февраля
10 de febrero
?? ?????? ?. 

Йуххху! Дадада, я тупо скопировал блок «желаемый результат». На самом деле я не знаю, что там будет, но я надеюсь, что будет так.

Так что делать, если я хочу прямо сейчас использовать кастомный формат?


В итоге я пришёл ко второму очевидному решению: нагенерить конфиг, содержащий каждый кастомный паттерн для каждой локали, которую поддерживает ваш проект. И делать это при появлении каждой новой локали.

Вот простой сниппет.

<?php

// ...
foreach ($locales as $locale) {
    $pattern = <<<CONFIG
        '%s' => [
            'medium_no_year' => "%s", // %s
            'long_no_year' => "%s", // %s
        ],

CONFIG;

    $mediumF = new IntlDateFormatter($locale, IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE);
    $longF = new IntlDateFormatter($locale, IntlDateFormatter::LONG, IntlDateFormatter::NONE);

    printf(
        $pattern,
        $locale,
        $mediumF->getPattern(),
        $mediumF->format(1455111783),
        $longF->getPattern(),
        $longF->format(1455111783)
    );
}

В итоге у вас будет что-то типа

        'en_US' => [
            'medium_no_year' => "MMM d, y", // Feb 10, 2016
            'long_no_year' => "MMMM d, y", // February 10, 2016
        ],
        'ru_RU' => [
            'medium_no_year' => "d MMM y 'г'.", // 10 февр. 2016 г.
            'long_no_year' => "d MMMM y 'г'.", // 10 февраля 2016 г.
        ],
        'es_ES' => [
            'medium_no_year' => "d MMM y", // 10 feb. 2016
            'long_no_year' => "d 'de' MMMM 'de' y", // 10 de febrero de 2016
        ],
        'fa_IR' => [
            'medium_no_year' => "d MMM y G", // ?? ?????? ???? ?.
            'long_no_year' => "d MMMM y G", // ?? ?????? ???? ?.
        ],

Затем нужно вручную пройтись по всем этим строкам и удалить из них часть, отвечающую за отображение года.

        'en_US' => [
            'medium_no_year' => "MMM d", // Feb 10
            'long_no_year' => "MMMM d", // February 10
        ],
        'ru_RU' => [
            'medium_no_year' => "d MMM", // 10 февр.
            'long_no_year' => "d MMMM", // 10 февраля
        ],
        'es_ES' => [
            'medium_no_year' => "d MMM", // 10 feb.
            'long_no_year' => "d 'de' MMMM", // 10 de febrero
        ],
        'fa_IR' => [
            'medium_no_year' => "d MMM", // ?? ?????? ?.
            'long_no_year' => "d MMMM", // ?? ?????? ?.
        ],

Когда у вас десятки локалей, это становится изнурительной работой, ПРОСТО ПОВЕРЬТЕ МНЕ (слышится тихий плач).

И это ещё не всё. Если вам захочется выводить такую дату со временем, вам придётся нагенерить в два раза больше паттернов. Потому что нельзя просто взять и соединить отформатированные отдельно дату и время. Добавить время в конец даты, в начало или куда-то в середину?

<?php

foreach (['en_US', 'ru_RU', 'es_ES', 'fa_IR'] as $locale) {
    $pattern = <<<CONFIG
        '%s' => [
            'medium_no_year-short' => "%s", // %s
            'long_no_year-short' => "%s", // %s
        ],

CONFIG;

    $mediumF = new IntlDateFormatter($locale, IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT);
    $longF = new IntlDateFormatter($locale, IntlDateFormatter::LONG, IntlDateFormatter::SHORT);

    printf(
        $pattern,
        $locale,
        $mediumF->getPattern(),
        $mediumF->format(1455111783),
        $longF->getPattern(),
        $longF->format(1455111783)
    );
}

Результат:

        'en_US' => [
            'medium_no_year-short' => "MMM d, y, h:mm a", // Feb 10, 2016, 2:43 PM
            'long_no_year-short' => "MMMM d, y 'at' h:mm a", // February 10, 2016 at 2:43 PM
        ],
        'ru_RU' => [
            'medium_no_year-short' => "d MMM y 'г'., H:mm", // 10 февр. 2016 г., 14:43
            'long_no_year-short' => "d MMMM y 'г'., H:mm", // 10 февраля 2016 г., 14:43
        ],
        'es_ES' => [
            'medium_no_year-short' => "d MMM y H:mm", // 10 feb. 2016 14:43
            'long_no_year-short' => "d 'de' MMMM 'de' y, H:mm", // 10 de febrero de 2016, 14:43
        ],
        'fa_IR' => [
            'medium_no_year-short' => "d MMM y G?? H:mm", // ?? ?????? ???? ?.?? ??:??
            'long_no_year-short' => "d MMMM y G? ???? H:mm", // ?? ?????? ???? ?.? ???? ??:??
        ],

Удаляем часть, ответственную за год, опять, да.

        'en_US' => [
            'medium_no_year-short' => "MMM d, h:mm a", // Feb 10, 2:43 PM
            'long_no_year-short' => "MMMM d, 'at' h:mm a", // February 10, at 2:43 PM
        ],
        'ru_RU' => [
            'medium_no_year-short' => "d MMM, H:mm", // 10 февр., 14:43
            'long_no_year-short' => "d MMMM, H:mm", // 10 февраля, 14:43
        ],
        'es_ES' => [
            'medium_no_year-short' => "d MMM H:mm", // 10 feb. 14:43
            'long_no_year-short' => "d 'de' MMMM, H:mm", // 10 de febrero, 14:43
        ],
        'fa_IR' => [
            'medium_no_year-short' => "d MMM?? H:mm", // ?? ???????? ??:??
            'long_no_year-short' => "d MMMM? ???? H:mm", // ?? ??????? ???? ??:??
        ],

Да вы только посмотрите на это, даже запятая в фарси это не обычная «наша» запятая, а ?, вы просто не можете угадать, как именно собрать результат из отдельных даты и времени.

Но как все остальные в мире PHP делают это?


В это сложно поверить, но они либо вообще не парятся с локализацией дат, либо делают это неправильно.

Я заглянул в исходники нескольких CMS, сделанных на PHP.

Я не стал смотреть фреймворки, ибо я считаю, что это не задача фреймворка. Хотя, может быть какая-то совсем уж базовая поддержка… Ну, может быть. Вообще, я попробовал, посмотрел на Yii2, и они просто рекомендуют использовать чистый intl. Так что давайте лучше CMS.

Drupal


С первым же поиском я наткнулся на изумительный тикет с интригующим названием — "Date intl support is broken, remove it". Лолшто!? И это не шутка, они и правда это сделали.

intl was removed

Вообще, они решают задачу форматирования дат приблизительно так же (пачка кастомных конфигов)
, но до патча (смотрите на скриншот) там был ключ intl как раз для паттерна локализации. А теперь им просто пофиг.

Ещё, если я правильно понял (я не достаточно активный пользователь Друпала), каждый пользователь после установки CMS должен сам ручками прописать все эти паттерны. Для каждой локали. Ну, или там есть что-то такое, о чём я не знаю, но выглядит это именно так.

Вот как это сделано в 8.1

date formats 8.1

А вот в версии 9.x

date formats 9.x

(Похоже, что они ещё не успели выпилить ключи intl из этой ветки)

Вообще, это не так уж плохо, но как пользователь CMS я нисколечки не хочу изучать все возможные культуры, чтобы выяснить, какие там у них принято использовать паттерны форматирования дат. Вся эта работа уже сделана в CLDR. Конечно, иногда мне нужны кастомные паттерны, но всё, на что я согласен, это указать, какие именно части даты и времени я хочу видеть в результате («только день и месяц, пожалуйста» или «будьте добры, мне бы месяц и время без секунд»).

WordPress


С WordPress у меня примерно те же отношения, я не из числа активных пользователей, поэтому я воспользовался поиском на Гитхабе. Похоже, что основная интересующая нас фунция здесь это date_i18n из functions.php (кстати, ребятушки, wtf? Файл с функциями из 5.2k строк кода? Серьёзно? У нас тут 2016-ый уже.).

wordpress date_i18n

Я честно потратил с полчаса, пытаясь понять, как оно работает. Но… но… да вы только гляньте на это.

wordpress date_i18n contents

Срань господня… Короче, оно точно не выглядит как правильно реализованная локализация дат, да хотя бы из-за date_format. Кажется, они пытаются локализовать названия месяцев и дней недели, не более. Знатоки поправят меня, если я ошибаюсь.

Joomla!


Джумлу я вообще ни разу не трогал. Поэтому схема та же.

В итоге: почти то же самое, что и в Друпале, пачка предопределённых форматов, которые нужно задать для каждой локали.

en-GB из дефолтной установки

en-GB Joomla config

Видите эти буквы? Они говорят нам о том, что Джумла использует date_format (как и многие другие) и не использует intl. Оправданно ли это? По-моему, нет. intl с версии 5.3 входит в ядро, а у нас на дворе уже PHP 7, а 5.5 это минимальнейшее требование для большей части современного кода. То есть, я абсолютно понимаю, когда фреймворки не используют какие-то сторонние расширения или фишки последних версий языка, потому что они считают, что их код должен работать хоть на утюге, и у них уже масса пользователей, у которых код работает на утюгах старых версий. Но здесь явно не тот случай. Назовём это «легаси» или «технический долг» или как-нибудь ещё и продолжим.

ModX Revolution


transport.core.system_settings.php



get.class.php



modifier.date_format.php



strftime звучит чуть лучше, чем date_format (там есть форматы, которые дают ложное ощущение правильной локализации) или ничего, когда мы говорим о локализации, но и эта функция не делает то, что нам нужно.

Magento2


Magento это не CMS в общепринятом смысле, это e-commerce платформа, но она широко известна, и у неё даже есть отдельный собственный фреймворк. Поэтому чего бы и нет.

И тут я должен сказать, что это единственная кодовая база в моём обзоре, в которой локализация дат сделана почти правильно! Это единственный код, в котором я встретил использование IntlDateFormatter, более того, он используется как основа компонента форматирования.



Но и их код не идеален. Смотрим в Timezone.php.



Я не смог найти места, в которых они бы пытались форматировать дату без года. Но есть ощущение, что они делают ту же ошибку, что и мы чуть ранее, пытаясь заменять год при форматировании «даты с длинным годом». Или же нет? Я не гуру регулярок (хоть и знаком с "lookahead" и "lookbehind"), поэтому я лучше просто выполню код из getDateFormatWithLongYear и посмотрю, что случится.

<?php

foreach (['en_US', 'ru_RU', 'es_ES', 'fa_IR'] as $locale) {
    $dateFormat = (new \IntlDateFormatter(
        $locale,
        \IntlDateFormatter::SHORT,
        \IntlDateFormatter::NONE
    ))->getPattern();

    $formatWithLongYear = preg_replace(
        '/(?<!y)yy(?!y)/',
        'Y',
        $dateFormat
    );

    $formatter = new \IntlDateFormatter(
        $locale, 
        \IntlDateFormatter::NONE, 
        \IntlDateFormatter::NONE, 
        null, 
        null, 
        $formatWithLongYear
    );
    echo $formatter->format(1455111783), PHP_EOL;
}

Результат:

2/10/2016
10.02.2016
10/2/2016
????/?/?? ?.

Похоже на успех! Ладно, иногда этот трюк срабатывает. Тут важнее сам факт того, что они вот так извращаются. Это ещё одно подтверждение, что в PHP не хватает того самого генератора паттернов.

А getDateTimeFormat это очевидная ошибка. Они конкатенируют паттерны даты и времени. Нет, чуваки, порядок отображения даты и времени не один и тот же во всех локалях, выше мы уже видели это.

Ещё можно заглянуть сюда. В любом случае — отличная работа, Magento!

Не хочешь ли ты сказать, что ты первый, кто заметил проблему?


Совсем нет. Ребята из HHVM давно портировали этот генератор — https://github.com/facebook/hhvm/commit/bc84daf7816e4cd268da59d535dcadfc6cf01085. Респект!
А ещё есть баг в трекере PHP — https://bugs.php.net/bug.php?id=70377 "Please add DateTimePatternGenerator to intl". Кстати, проголосуйте, если вас тоже задевает эта проблема. Вдруг там это голосование действительно что-то значит. ~Кстати, там нет защиты от CSRF~.

Критиковать любой может. Вот взял бы да сам и сделал!


Ну, вообще, после нескольких довольно жалких попыток обратить внимание сообщества на проблему и попросить кого-то из разработчиков добавить недостающие вещи в intl, я сделал — https://github.com/ksimka/intl_dtpg. Это расширение, реализующее по сути единственную функцию (в виде функции или метода класса) «найди наиболее подходящий паттерн для вот этого набора частей даты и времени». Тот самый DateTimePatternGenerator::getBestPattern (там в этом классе ещё много интересного есть, но лично мне критически не хватает пока только вот этого метода).

Но проблема в том, что я не шарю ни в C++, ни в C. Поэтому используйте его на свой страх и риск (после внедрения у нас я расскажу, всё ли там хорошо). Расширение написано на C++ на базе PHPCPP методом examples+google+stackoverflow. Поэтому любые улучшения крайне приветствуются.

А вообще, тут же у нас есть ребята из PHP core dev team, портируйте уже, плиз, эти недостающие части в intl кто-нибудь, или хотя бы задолбайте разработчиков PHP просьбами. Можно говорить, что я вас заставил, угрожая насильно обучить go.

На этом всё. Спасибо, что дочитали до конца!
А как у вас?

Проголосовало 211 человек. Воздержалось 54 человека.

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

Комментарии (37)


  1. Fedcomp
    09.03.2016 11:38
    -10

    Шел 2016 год, мы писали расширения для php на PHPCPP/C/C++ и похоже это не перевод.
    https://packagist.org/search/?q=icu
    Не уверен что по ссылке выше есть именно нужный вам компонент, но в случае если вы хотите написать какой то класс/пакет которым будет пользоваться современный разработчик то вам стоит узнать о composer https://getcomposer.org/ (судя по php расширению вы о нем знаете)


    1. MaxxArts
      09.03.2016 11:55
      +10

      Шел 2016 год, мы писали расширения для php на PHPCPP/C/C++ и похоже это не перевод.

      Эээ, простите, а на чём ещё писать расширения, требующие работы с библиотеками на C/C++? Я понимаю, есть всякие модные Зефиры, но они, кажется, совсем не для этого.

      Не уверен что по ссылке выше есть именно нужный вам компонент

      А я уверен, что нет.

      судя по php расширению вы о нем знаете

      Очень хочется съязвить, но постараюсь ответить по делу. Попытался понять, как вы сделали такой вывод, но безуспешно. Поэтому попрошу уточнения — с чего вы взяли?

      Composer — это менеджер зависимостей, там расширение PHP я могу лишь указать в требованиях к пакету, мол, пакет X требует расширение phpx или либу lib-x. Как через него распространять само расширение, расскажите.

      В общем, я не понял, что вы хотели сказать.


      1. Fedcomp
        09.03.2016 21:23
        -1

        А я уверен, что нет

        Вы действительно уверены что это не то?
        http://symfony.com/blog/new-in-symfony-2-6-farewell-to-icu-component
        вместо того чтобы тащить системную библиотеку, проще притащить php библиотеку per project.


        1. zelenin
          10.03.2016 03:46
          +1

          это частичная реимплементация того функционала, который уже есть в php из коробки. Цель: отвязаться от версии icu, установленной в системе.
          Именно к тому, что есть из коробки и написано расширение. Так что это действительно не то.


          1. Fedcomp
            14.03.2016 13:21

            К тому что из коробки написано то что не из коробки, следовательно все плюсы теряются. Вместо того чтобы допилить то что более портабельно, ставить system-wide костыли.


            1. zelenin
              14.03.2016 13:27

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


              1. MaxxArts
                14.03.2016 18:22

                Подождите, вы сейчас обсуждаете компонент, в репозитории которого написано "[DEPRECATED] This repository only exists for BC compatibility with old versions of Symfony. Recent versions comes with ICU data.", я правильно понимаю?


                1. zelenin
                  14.03.2016 18:25

                  статья по ссылке посвящена как раз уходу от symfony icu к symfony intl. Последнее мы и обсуждаем.


                  1. MaxxArts
                    14.03.2016 18:26
                    +1

                    А, сорри, это тот компонент, в репозитории которого написано "The replacement layer is limited to the locale "en". If you want to use other locales, you should install the intl PHP extension instead.", да?


                    1. zelenin
                      14.03.2016 19:14

                      может быть.


    1. zelenin
      09.03.2016 12:07
      +5

      лично я не очень понял ваш коммент. phpcpp плохо? причем тут композер, если человек написал расширение для php, а не php-библиотеку? Или вы хотите сказать, что лучше бы он написал бибилиотеку, чем расширение?
      PS По ссылке как раз одни обертки над intl.
      PPS выше уже автор задал похожие вопросы.


  1. zelenin
    09.03.2016 12:19
    -2

    вообще одно понятно: делать надо было именно библиотекой, т.к. ценность расширения только в поводе для неплохой статьи. "Аудитория" расширения против библиотеки будет отличаться в десятки раз.


    1. MaxxArts
      09.03.2016 12:53
      +8

      ценность расширения только в поводе для неплохой статьи

      Вот это сейчас обидно было. Статью я писал где-то 2-3 недели, искал готовые решения, спрашивал у разработчиков, изучал крупные кодовые базы, изучал ICU и код на C++, который использует ICU. Расширение написано исключительно как акт отчаяния.

      делать надо было именно библиотекой

      Как именно сделать это библиотекой? Написать пустую обёртку над классом расширения? Допустим. Но как это поможет пользователям? Собирать расширение всё равно нужно руками под свою систему. И так будет до тех пор, пока разработчики PHP не включат нечто подобное в intl.

      Уже второй комментарий, намекающий, что лучше было бы это вообще на чистом PHP реализовать. И я начинаю задумываться: или же вы просто невнимательно прочитали пост, или, может быть, я что-то упускаю? В случае последнего искренне буду благодарен за пояснения. Потому что с моей точки зрения вы предлагаете портировать ICU на PHP и после поддерживать постоянно этого зверя.


      1. zelenin
        09.03.2016 15:46

        нет-нет, я про варианты, когда портируют password api или datetimeimmutable в php 5.5- — полное мимикрирование апи.
        Вы упускаете то, что внутри генератора достаточно тривиальная логика работы с локалью и ресурсами. Берем реализацию icu на java, ищем генератор, тратим часа 3 на переписывание класса из 1500 строк на php.
        ICU портировать не надо, т.к. весь необходимый функционал для портирования одного класса уже есть: Locale и ResourceBundle.


        1. MaxxArts
          09.03.2016 16:19

          Я глянул в http://source.icu-project.org/repos/icu/icu4j/trunk/main/classes/core/src/com/ibm/icu/text/DateTimePatternGenerator.java, и один только список импортов (не включая структуры данных, части которых нет в PHP) приводит в ужас

          import com.ibm.icu.impl.ICUCache;
          import com.ibm.icu.impl.ICUResourceBundle;
          import com.ibm.icu.impl.PatternTokenizer;
          import com.ibm.icu.impl.SimpleCache;
          import com.ibm.icu.impl.SimpleFormatterImpl;
          import com.ibm.icu.impl.Utility;
          import com.ibm.icu.util.Calendar;
          import com.ibm.icu.util.Freezable;
          import com.ibm.icu.util.ICUCloneNotSupportedException;
          import com.ibm.icu.util.ULocale;
          import com.ibm.icu.util.ULocale.Category;
          import com.ibm.icu.util.UResourceBundle;
          import com.ibm.icu.util.UResourceBundleIterator;

          Ну то есть, может я суперслоупок, но это точно не 3 часа для меня, даже не 3 дня. Возможно, кто-то мог бы взяться за это, было бы отлично. Но стоит помнить, что всё это нужно затем поддерживать в актуальном состоянии. Сейчас — обновили ICU, пересобрали PHP, и поехали дальше.

          ICU портировать не надо, т.к. весь необходимый функционал для портирования одного класса уже есть: Locale и ResourceBundle.

          Можно чуть подробнее об этом?


          1. zelenin
            09.03.2016 16:38
            +1

            один только список импортов (не включая структуры данных, части которых нет в PHP) приводит в ужас

            особо ничего не вижу. Calendar, Locale, ResourceBundle есть. Остальное, судя по названиям, инфраструктурные плюшки, типа итераторов и токенайзеров.
            В то же время конечно я могу быть не прав, но беглый взгляд на код опасений не подтвердил.

            Ну то есть, может я суперслоупок, но это точно не 3 часа для меня, даже не 3 дня. Возможно, кто-то мог бы взяться за это, было бы отлично. Но стоит помнить, что всё это нужно затем поддерживать в актуальном состоянии. Сейчас — обновили ICU, пересобрали PHP, и поехали дальше.
            да не нужно ничего тут поддерживать. ICU — это api для работы с cldr. Портируя один класс из java-имплементации, вы просто делаете свой бэкенд над cldr, но пользуясь уже готовым api intl. intl особо не поддерживается — раз написано и все. только с каждой новой версией фич больше становится (то есть расширяется апи работы с cldr).

            Можно чуть подробнее об этом?
            Locale — понятно, ResourceBundle — способ низкоуровнево запрашивать инфу из cldr. Именно это все и юзается в паттерн генераторе.
            Отличную работу в этом направлении провел наш дорогой SamDark
            slides.rmcreative.ru/2015/webconf-i18n-l10n/# на ютубе наверное видео есть
            github.com/samdark/intl-icu-data-tables


            1. MaxxArts
              09.03.2016 18:13
              +1

              Блин, убедили. Если я окончательно сойду с ума, портирую всё это дело на PHP. После месяца исследований этой темы у меня оформилась аллергия на ICU и intl, так что это случится не скоро.


              1. zelenin
                09.03.2016 18:26
                +1

                icu и intl — это очень сложно, но очень гибко.

                На всякий случай для не разобравшихся в мешанине терминов поясню:
                есть каталог различной локализационной информации — cldr. Существует библиотека ICU, которая включает в себя cldr и предлагает некое api для работы с этими данными (реализации на c и java). есть расширение php intl, являющееся бэкендом к сишному icu.
                Я предлагаю реализовать нереализованный в php intl класс DatePatternGenerator, взяв его реализацию из java icu, воспользовавшись уже реализованными классами php intl.


                1. zelenin
                  09.03.2016 18:29

                  я честно говоря не проходил по ссылкам — не знаю в каком виде issue по добавлению этого класса в php intl. Но вообще апи расширяется достаточно активно — в 5.6 или 7 кол-во классов увеличилось чуть ли не в два раза, если не больше. Правда документация всех новых классов равна нулю)


      1. Skull
        09.03.2016 22:50

        2-3 недели дату форматировать??? 98% разрабочиков (включая меня) забили бы и пошли дальше.

        Но тема интересная и слог у вас отличный. Пишите еще.


        1. MaxxArts
          11.03.2016 11:26

          98% разрабочиков (включая меня) забили бы и пошли дальше.

          Представьте, что вы заходите на сайт, а там даты типа «Февраль. 10» или «10-февраля,» или ещё что-то такое невразумительное. Это примерно так видят даты пользователи из других стран, когда вы для них плохо локализуете. Представили? Вот я постоянно заставляю себя это представлять, и это отлично помогает не забивать.


  1. savostin
    09.03.2016 13:55

    Я бы начал изучать именно с того, как делает Twitter ;)


    1. MaxxArts
      09.03.2016 14:11

      Не знал, что Твиттер написан на PHP, да ещё и выложен в открытый доступ. Пошёл изучать!


      1. savostin
        09.03.2016 14:16

        А это потому, что Twitter сделал это по-человечески — на Javascript.
        Тут вам и отсутствие необходимости знать часовой пояс клиента, тут и динамическое отображение (25 минут назад) и пр.


        1. happyproff
          09.03.2016 14:29

          А как на Javascript можно по-человечески, легко и просто получить локализованную дату без года?


          1. savostin
            09.03.2016 14:36

            Я бы начал отсюда


        1. MaxxArts
          09.03.2016 15:36

          А это потому, что Twitter сделал это по-человечески — на Javascript.

          Это неправда. Достаточно посмотреть в исходный код ленты Твиттера: он приходит с отформатированными датами уже с сервера.

          Тут вам и отсутствие необходимости знать часовой пояс клиента

          Это вообще как так? Вероятно, вы имели в виду «нет необходимости хранить временную зону пользователя на сервере»? Допустим. Но локализовывать дату нужно не всегда на клиенте. Иногда, скажем, это дата в письме: «тогда-то случилось/случится то-то, приходите и будет круто». И тут без какого-либо знания временной зоны пользователя уже никак.

          Кроме всего этого есть легаси код, есть технические требования, от которых не уйти.


        1. artyfarty
          09.03.2016 15:37
          +3

          Javascript. Даты. По-человечески. Это в языке, где штатные даты самые смехотворные на свете, где месяцы нумеруют от нуля, а дни от единицы.

          https://twitter.com/recursecenter/status/704429884374958080


  1. lorc
    09.03.2016 16:19

    Возможно, глупый вопрос, но зачем было писать конфиг руками?
    Ведь можно было бы взять тот же С++ или Java, и с помощью getBestPattern() сгенерить сразу правильный конфиг для всех-всех локалей в мире. Это конечно костыль, но менее костыльный, чем ручная правка строк форматирования.


    1. MaxxArts
      09.03.2016 16:23

      Хороший вопрос, на самом деле. Такой вариант я рассматривал как резервный. Так сложилось, что я ни в C++, ни в Java не умею на достаточном уровне. Честно говоря, я рассчитывал, что есть какая-то готовая утилита, которую можно было бы подёргать как раз для этих целей. Искал, не нашёл. Решил попробовать написать расширение. Случайно получилось, поэтому так.


  1. xRay
    09.03.2016 16:31
    +1

    На стороне клиента можно использовать http://momentjs.com


  1. xRay
    09.03.2016 16:40
    -1

    А вот реализация на PHP https://github.com/fightbulc/moment.php


    1. zelenin
      09.03.2016 17:02
      +2

      это все понятно. но cldr — обширный кладезь любой информации, связанной с интернационализацией, и время это только небольшая часть. ICU — стандарт де-факто.
      И между прочим и в браузерах уже началась появляться поддержка icu в js (https://habrahabr.ru/post/218481/), поэтому все либы типа momentjs умрут. Глупо юзать свой велосипед, писать к нему ручками двадцать-тридцать локалей (https://github.com/fightbulc/moment.php/tree/master/src/Locales), если у тебя есть обширное апи для работы с любыми локалями и доступ к самой полной информации в мире.
      Поэтому лучший вариант не делать с нуля поделку, а дописать функциональность.


      1. zelenin
        09.03.2016 17:13
        +1

        https://github.com/2amigos/transliteration-helper/tree/master/src/dosamigos/yii/helpers/data
        аналогичная ситуация — для транслитерации используются какие-то таблицы. Вариант транслитерации один для всех языков и неизвестно откуда взят (тут проблема в различном подходе к транслитерации в разных языках).
        Intl позволяет транслитерировать в зависимости от локали, внутри локали можно выбрать разные способы (типа iso, рекомендация такого института итд), можно переопределить, добавить свои дополнительные правила, а также доп. фильтры типа нормализации юникода итд.


    1. redc0de
      09.03.2016 17:17

      Нет поддержки всех локалей (ru_RU например)


      1. savostin
        11.03.2016 14:22

        А там просто


        1. MaxxArts
          11.03.2016 16:40

          То, что я вижу по ссылке, — не полноценная локаль. Это только переводы. Как я писал, локаль, это ещё и порядок размещения даты и времени. Но это так, к слову. Само собой, эта либа к полноценной локализации не имеет отношения.