Help us understand the problem. What is going on with this article?

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

はじめに

和暦を含んだ文字列をタイムスタンプに変更する処理を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のソースコードを探索していきました。

https://github.com/php/php-src/blob/852485d8ecd784153e41e565a0a87abf99cf4e0d/ext/intl/dateformat/dateformat_parse.c#L48

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側に対応してもらいたいところです。いつになるんだろ。

参考リンク

showcase-tv
Webサイト最適化技術により成約率を高める「ナビキャストシリーズ」の提供および、DMPを活用したWebマーケティング支援
https://www.showcase-tv.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした