LoginSignup
3
1

More than 3 years have passed since last update.

PHP-Intlを新元号対応(令和対応)する

Last updated at Posted at 2019-06-04


2020年6月11日: 追記
更新依頼をかけてくださった親切な方のおかげで現在は令和対応しています。(コメント欄参照)

remirepo の libicu62 を 62.2 に更新してもらいました。
現在は普通にしていれば対応していると思います。
https://github.com/remicollet/remirepo/issues/149

ただし、ICU 62のままなので、元年対応はありません。
(将来的には ICU 65 か何かに更新する予定と上の issue に書いてあります)

はじめに

和暦を含んだ文字列をタイムスタンプに変更する処理をPHPのextensionであるIntlを使って実現してました。


$ php artisan tinker
Psy Shell v0.9.9 (PHP 7.2.19 — cli) by Justin Hileman
>>> $formatter = \IntlDateFormatter::create(
             "ja_JP@calendar=japanese",
             \IntlDateFormatter::FULL,
             \IntlDateFormatter::FULL,
             "Asia/Tokyo",
             \IntlDateFormatter::TRADITIONAL,
             'Gyy年MM月dd日'
         );
=> IntlDateFormatter {#2665}

>>> $formatter->parse('平成31年6月1日')
=> 1559314800

>>> $formatter->parse('令和1年6月1日')
=> false

やっぱ令和はPHP7.2系の最新のバージョンでもだめだよねー。

環境

PHPはremi経由で入れています。


$ php -v
PHP 7.2.19 (cli) (built: May 29 2019 11:04:13) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

$ sudo yum list installed | grep php-intl                                                  
php72-php-intl.x86_64                 7.2.19-2.el7.remi              @remi-safe

調査

元号をタイムスタンプ変換しているところはどこか?PHPのソースコードを探索していきました。

dateformat_parse.c
static void internal_parse_to_timestamp(IntlDateFormatter_object *dfo, char* text_to_parse, size_t text_len, int32_t *parse_pos, zval *return_value)
{
        ...
    timestamp = udat_parse( DATE_FORMAT_OBJECT(dfo), text_utf16, text_utf16_len, parse_pos, &INTL_DATA_ERROR_CODE(dfo));
        ...
}

udat_parseはなんぞや?udat_parseでググるとICUというCのライブラリに。
http://www.icu-project.org/apiref/icu4c/udat_8h.html

令和対応は入っているか?ICU 令和でググると先人の知恵が記事になってました。これよりICUのバージョンが64.2以上だと良さそうです。
【最終版】新元号に対応するために ICU を用いて和暦の動作確認をする

php-intlの依存性チェックをすると、バージョンは62のようです。なるほど。


$ yum deplist --disablerepo=base --enablerepo=remi-php72 php-intl
   ...
  dependency: libicudata.so.62()(64bit)
   provider: libicu62.x86_64 62.1-3.el7.remi
  dependency: libicui18n.so.62()(64bit)
   provider: libicu62.x86_64 62.1-3.el7.remi
  dependency: libicuio.so.62()(64bit)
   provider: libicu62.x86_64 62.1-3.el7.remi
  dependency: libicuuc.so.62()(64bit)
   provider: libicu62.x86_64 62.1-3.el7.remi
   ...

試行錯誤

libicu62.x86_64libicu64.x86_64に差し替えたphp-intlなら動くんじゃないかと考えて実行してみました。

下記のQiita記事を参考にしました。
さくらのレンタルサーバPHP7環境でintlモジュール有効化

php-intlをアンインストールし、作業用とビルドしたintl.soを置くフォルダを作成

$ yum remove libicu*
$ mkdir -p ~/usr/local/src
$ mkdir -p ~/usr/local/php/extension

ICUのビルド

$ cd ~/usr/local/src
$ wget http://download.icu-project.org/files/icu4c/64.2/icu4c-64_2-src.tgz
$ tar zxvf icu4c-64_2-src.tgz
$ cd icu/source
$ ./configure --prefix=$HOME/usr/local
$ gmake
$ gmake install

php-7.2.19に同梱されているintlをビルド

$ cd ~/usr/local/src
$ wget https://github.com/php/php-src/archive/php-7.2.19.zip
$ unzip php-7.2.19.zip
$ mv php-src-php-7.2.19/ext/intl ./intl-php-7.2.19
$ rm -rf php-src-php-7.2.19
$ cd intl-php-7.2.19
$ phpize
$ ./configure --with-icu-dir=$HOME/usr/local --with-php-config=/usr/bin/php-config
$ make

途中php_smart_str.h: No such file or directoryみたいなエラーが出る場合は、どうしたっけ…忘れました。多分、--with-php-configの指定がまずかったのだと思います。

途中make: *** [intl_convertcpp.lo] Error 1みたいなエラーが出る場合はここ参照してdevtoolset-7をインストールした上で下記コマンドを実行してください、


$ CXX=/opt/rh/devtoolset-7/root/usr/bin/g++ ./configure --with-icu-dir=$HOME/usr/local --with-php-config=/usr/bin/php-config
$ make

modulesフォルダにできたintl.soをさっき作成したフォルダへコピー

$ cp ~/usr/local/src/intl-php-7.2.19/modules/intl.so ~/usr/local/php/extension/

intlの設定ファイルを作成します。

$ sudo vi /etc/php.d/20-intl.ini

さっきビルドしたintl.soのパスを記述します。

/etc/php.d/20-intl.ini
extension=/home/dev/usr/local/php/extension/intl.so

これで動くはず。確認します。

$ php artisan tinker
Psy Shell v0.9.9 (PHP 7.2.19 — cli) by Justin Hileman
>>> $formatter = \IntlDateFormatter::create(
            "ja_JP@calendar=japanese",
            \IntlDateFormatter::FULL,
            \IntlDateFormatter::FULL,
            "Asia/Tokyo",
            \IntlDateFormatter::TRADITIONAL,
            'Gyy年MM月dd日'
        );
=> IntlDateFormatter {#2659}

>>> $formatter->parse('平成31年6月1日')
=> 1559314800

>>> $formatter->parse('令和1年6月1日')
=> 1559314800

うぇーい!

考察

とりあえずできました。

でもphp-intlのビルドでmake testするとテスト失敗が結構でます。。。

=====================================================================
FAILED TEST SUMMARY
---------------------------------------------------------------------
IntlBreakIterator::getLocale(): basic test [tests/breakiter_getLocale_basic2.phpt]
Bug #66289 Locale::lookup incorrectly returns en or en_US if locale is empty [tests/locale_bug66289.phpt]
locale_get_display_language() [tests/locale_get_display_language.phpt]
locale_get_display_name() icu >= 53.1 [tests/locale_get_display_name5.phpt]
locale_get_primary_language() [tests/locale_get_primary_language.phpt]
locale_parse_locale() icu >= 4.8 [tests/locale_parse_locale2.phpt]
=====================================================================

一応動くとはいえ無理やりですし、結構手順もめんどくさいのでInfrastructure as a codeで環境作ってる場合はどうすんのコレ…って感じではあります。本番に入れるのはどうしようと迷う感じではあります。どうしよう。はやくphp-intl側に対応してもらいたいところです。いつになるんだろ。

参考リンク

3
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1