PHPで1年なしで日付をローカライズすることに関する長い話

簡単なタスクから始めましょう。ローカライズされた日付を表示します。1日、ロケール言語の月のフルネーム、および1年が必要です。 最近では、非常に簡単です。 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を見てみましょう。



Twitterの日付形式



ツイートが最近公開された場合、Twitterは適切にフォーマットされた時間間隔を表示します。 ツイートがかなり前に発生した場合、適切にフォーマットされた日付が表示されます。 さらに、ツイートが今年公開された場合、その日には年がありません。 そしてそれは素晴らしいことです、それは良いUXの一部です。



これは、ユーザーがこれがいつ発生したかをすばやく理解するという考え方です。 余分なデータはノイズです。



これで理由がわかります。 タスクに戻りましょう。



わかりました。つまり、これには別の形式( LONG



など)が必要ですが、年はありませんよね?



そうではありません。 IntlDateFormatter



FULL



LONG



MEDIUM



およびSHORT



4つの標準形式のみがあり、それぞれに年があります。



 <?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



書式設定した後に何が起こるのかを1年だけ減らしたいですか? そうだと思います。 例がなければ、問題が何であるかを理解することは困難です。 見てみましょう。



私たちが持っていることを思い出させてください。



 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で、開発されたエコシステムと最も強力なコミュニティの1つを備えた成人言語では、基本的な機能がない可能性がありますか? 悲しいが本当:可能。



少なくとも1つの非常に重要なことがintl



ありませんintl



DateTimePatternGenerator



クラスです。 それは、私たちの小さな問題とそのような他のすべてを解決するために正確に作られています。



待って、待って、どんなICUなの?



ICUは「Unicodeの国際コンポーネント」です。Unicodeの国際化コンポーネントです。



ICU Webサイトからの引用。



ICUは、ソフトウェアアプリケーションのUnicodeおよびグローバリゼーションサポートを提供する、成熟した、広く使用されているC / C ++およびJavaライブラリのセットです。 ICUは広く移植可能で、すべてのプラットフォーム上で、C / C ++とJavaソフトウェア間で同じ結果をアプリケーションに提供します。


...



書式設定:選択したロケールの規則に従って、数値、日付、時刻、通貨額を書式設定します。 これには、月と日の名前の選択した言語への翻訳、適切な略語の選択、フィールドの正しい順序付けなどが含まれます。 このデータは、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をインストールする必要があります(または、最初にICUでPHPをビルドする必要があります)。 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ウェブサイトからの別の引用:



このクラスは、「yy-MM-dd」などの日付形式パターンの柔軟な生成を提供します。



ユーザーは、連続したパターンを追加してジェネレーターを構築できます。 それが完了すると、「スケルトン」を使用してクエリを作成できます。これは、目的のフィールドと長さだけを含むパターンです。 ジェネレータは、そのスケルトンに対応する「最適な」パターンを返します。



通常、このクラスは特定のロケールのデータで事前に構築されているため、人々が使用する主なメソッドはgetBestPattern(Stringスケルトン)です。 ただし、ジェネレータは他のデータから直接構築することもできます。


これはまさに私たちが必要とするものです! いわゆる「スケルトン」(フォーマット結果に含まれる日付の部分)をgetBestPattern



メソッドに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 ۱۰ ﻑﻭﺭیﻩٔ ﻡ.
      
      





ユフ! ダダッド、私は愚かに「望まれる結果」ブロックをコピーしました。 実際、何がそこにあるのかわかりませんが、そうなることを望みます。



では、今すぐカスタム形式を使用したい場合はどうすればよいですか?



その結果、2番目の明確な決定に至りました。プロジェクトがサポートする各ロケールの各カスタムパターンを含む構成を生成することです。 そして、新しいロケールが表示されるたびにそれを行います。



これは簡単なスニペットです。



 <?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", // ۱۰ فوریهٔ م. ],
      
      





たくさんのロケールがあるとき、それは大変な仕事になります、私を覚えておいてください( 静かな叫び声が聞こえます )。



それだけではありません。 そのような日付を時間とともに表示する場合は、2倍のパターンを生成する必要があります。 別々にフォーマットされた日付と時刻を取得して結合することはできないからです。 日付の最後に、最初に、または途中で時間を追加しますか?



 <?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の世界の誰もがこれをどのように行うのでしょうか?



これは信じがたいことですが、 彼らは日付のローカライズについてまったく心配しないか、間違っています。



PHPで作成されたいくつかのCMSのソースコードを調べました。



これはフレームワークのタスクではないと考えているので、フレームワークを見ませんでした。 ただし、何らかの基本的なサポートがあるかもしれません... 実際、私はYii2を試してみましたが、 純粋なintl



使用を推奨してい
ます。 それでは、より良いCMSを入手しましょう。


Drupal



最初の検索で、私は興味をそそるタイトルの驚くべきチケットに出くわしました-「日付の国際サポートが壊れています、それを削除してください」。 ロルスト!? そして、これは冗談ではありません、彼らは本当にそれをしました。



intlは削除されました



一般に、それらはほぼ同じ方法で日付をフォーマットする問題を解決します(カスタム構成のパック)

、ただし、パッチ(スクリーンショットを参照)の前に、ローカライズパターン専用のintl



キーがありました。 そして今、彼らは気にしません。



また、正しく理解した場合(私はDrupalのアクティブユーザーではありません)、CMSをインストールした後、各ユーザーはこれらすべてのパターンをペンで手動で登録する必要があります。 ロケールごと。 まあ、またはそこに私が知らない何かがありますが、それはそのように見えます。



8.1で行われる方法は次のとおりです。



日付形式8.1



しかし、バージョン9.xでは



日付形式9.x



(彼らはまだこのブランチからintl



キーをカットできていないようです)



一般的に、これはそれほど悪いことではありませんが、CMSユーザーとして、どのような種類の日付フォーマット方法が使用されているかを知るために、考えられるすべての文化を調査したくありません。 このすべての作業はすでにCLDRで行われています。 もちろん、カスタムパターンが必要な場合もありますが、私が同意するのは、結果として表示する日付と時刻の部分を正確に示すことだけです(「日と月のみ、お願いします」または「親切にしてください。秒」)。



ワードプレス



私はWordPressとほぼ同じ関係を持っていますが、アクティブユーザーではないため、Githubで検索を使用しました。 ここで関心のある主なfunctions.php



は、 functions.php



date_i18n



のようfunctions.php



(ところで、皆さん、wtf?5.2k行のコードの関数を含むファイル?真剣に?既に2016年があります)。



wordpress date_i18n



私は正直に、それがどのように機能するかを理解しようとして30分を費やしました。 しかし...しかし...はい、それを見てください。



wordpress date_i18nの内容



Goddamn ...要するに、それは確かに正しく実装された日付のローカライズのようには見えませんが、少なくともdate_format



ためです。 彼らはもはや月と曜日の名前をローカライズしようとしているようです。 私が間違っている場合、鑑定家は私を修正します。



Joomla!



ジュムラにはまったく触れませんでした。 したがって、スキームは同じです。



最終的には、各ロケールに設定する必要がある事前定義されたフォーマットの束であるDrupalとほぼ同じです。



デフォルトのインストールのen-GB



en-GB Joomla設定



これらの手紙を見ますか? Jumlaはdate_format



(他の多くの場合と同様に)使用し、 intl



は使用しないことを教えてくれます。 それは正当化されますか? 私の意見では、いいえ。 カーネルにはバージョン5.3のintl



が含まれており、既にヤード7にPHP 7があり、最新のコードの最小要件は5.5です。 つまり、フレームワークがサードパーティの拡張機能や言語の最新バージョンのチップを使用しない場合、コードがアイロンでも動作するはずであり、すでに古いアイロンでコードを実行するユーザーがすでに多くいるので、私は完全に理解していますバージョン。 しかし、これは明らかにそうではありません。 私たちはそれを「レガシー」または「技術的義務」と呼びます。さもなければ何とか続けます。



Modx革命



transport.core.system_settings.php







get.class.php







modifier.date_format.php







strftime



date_format



(正しいローカリゼーションの誤った感覚を与えるフォーマットがあります)よりも少し良く聞こえますが、ローカライズについて話すときは何もありませんが、この関数は必要なことをしません。



Magento2



Magentoは常識的なCMSではなく、eコマースプラットフォームですが、広く知られているほか、独自のフレームワークも備えています。 したがって、何があっても。



そして、これが私のレビューで日付のローカライズがほぼ正しく行われる唯一のコードベースであると言わざるを得ません! これはIntlDateFormatter



を使用して出会った唯一のコードであり、さらに、書式設定コンポーネントの基礎として使用されます。







しかし、彼らのコードは完璧ではありません。 Timezone.phpを調べます。







年なしで日付をフォーマットしようとする場所を見つけることができませんでした。 しかし、彼らは少し前にやったのと同じ間違いを犯しているように感じます。「長い年の日付」をフォーマットするときに年を置き換えようとしています。 かどうか? 私は通常の第一人者ではありません(先読みと後読みに精通していますが)ので、 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にまったく同じパターンジェネレーターがないことのもう1つの確認です。



また、 getDateTimeFormat



は明らかな間違いです。 日付と時刻のパターンを連結します。 いいえ、おい、日付と時刻が表示される順序はすべてのロケールで同じではありません。これはすでに上で見ました。



こちらご覧ください 。 とにかく素晴らしい仕事、Magento!



あなたが最初に問題に気づいたと言いたいですか?



まったくありません。 HHVMの連中はこのジェネレーターを長い間移植しています-https ://github.com/facebook/hhvm/commit/bc84daf7816e4cd268da59d535dcadfc6cf01085 尊敬する!

また、PHPトラッカーにバグがあります-https : //bugs.php.net/bug.php? id= 70377「DateTimePatternGeneratorをintlに追加してください」。 ところで、あなたもこの問題の影響を受けている場合は投票してください。 突然、この投票は本当に何かを意味します。 〜ところで、CSRFに対する保護はありません〜。



誰でも批判できます。 自分で取ってやります



まあ、一般的に、コミュニティの注意を問題にintl



、不足しているものをintl



に追加するように開発者の1人に頼もうとするいくつかのかなり哀れな試みの後、私はそうしましたintl



://github.com/ksimka/intl_dtpg 本質的に唯一の関数(関数またはクラスメソッドの形式)を実装するこの拡張機能は、「この日付と時刻の部分のセットに最も適したパターンを見つけます」。 同じDateTimePatternGenerator::getBestPattern



(このクラスにはまだ多くの興味深いことがありますが、個人的には、今のところこのメソッドのみが非常に不足しています)。



しかし、問題は、私がC ++またはCのいずれかを急いでいないということです。したがって、あなた自身の危険とリスクでそれを使用してください(実装後、すべてがそこでうまくいくかどうかを教えます)。 拡張機能は、examples + google + stackoverflowメソッドを使用して、PHPCPPに基づくC ++で記述されています。 したがって、どんな改善も大歓迎です。



一般に、現在、PHPコア開発チーム、port、pliz、誰か、 intl



これらの欠落部分、または少なくともzadolbat PHP開発者からのリクエストがあります。 go



を強制的に訓練すると脅して、私はあなたを強制したと言うことができます。



それだけです 最後まで読んでくれてありがとう!




All Articles