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


<font color="red"><B>
2020年6月11日: 追記
更新依頼をかけてくださった親切な方のおかげで現在は令和対応しています。（コメント欄参照）
</B></font>

> remirepo の libicu62 を 62.2 に更新してもらいました。
> 現在は普通にしていれば対応していると思います。
> https://github.com/remicollet/remirepo/issues/149
> 
> ただし、ICU 62のままなので、元年対応はありません。
> （将来的には ICU 65 か何かに更新する予定と上の issue に書いてあります）


#はじめに

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

```shell

$ 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経由で入れています。

```shell

$ 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

```c: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 を用いて和暦の動作確認をする](https://qiita.com/midheartty/items/590b0911c000a24abe34)

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

```shell

$ 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_64`を`libicu64.x86_64`に差し替えたphp-intlなら動くんじゃないかと考えて実行してみました。

下記のQiita記事を参考にしました。
[さくらのレンタルサーバPHP7環境でintlモジュール有効化](https://qiita.com/masa-kunikata/items/bc34b33fdc169b660c58)

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`みたいなエラーが出る場合は[ここ](https://www.softwarecollections.org/en/scls/rhscl/devtoolset-7/)参照してdevtoolset-7をインストールした上で下記コマンドを実行してください、

```shell

$ 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をさっき作成したフォルダへコピー

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

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

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

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

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

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

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

# 参考リンク

* https://github.com/php/php-src/tree/master/ext/intl
* http://www.icu-project.org/apiref/icu4c/udat_8h.html
* https://qiita.com/midheartty/items/590b0911c000a24abe34
* https://qiita.com/masa-kunikata/items/bc34b33fdc169b660c58
* https://serverfault.com/questions/914288/how-to-build-standalone-intl-extension-from-bundled-source
* https://www.softwarecollections.org/en/scls/rhscl/devtoolset-7/