はじめに
i18n対応で考えないといけないことの1つに夏時間(Daylight Saving Time)があります.
夏時間のテストはいくつかの理由で難しい場合が多いです.
- サーバAPI開発中で, 海外へのサービス提供が蓋閉めされている
- 夏時間がくるまで待てない
そこで, 日本でも夏時間が導入されていることにして, 好きなタイミングでJST(Japan Standard Time)↔️JDT(Japan Daylight Saving Time)を切り替えられればテストが捗りそうです.
本稿は, そのような環境を構築するためにTZDB(Time Zone Database)をテスト用に編集して, それをシステムに認識させる方法を紹介します.
本稿執筆時点でのTZDB Versionは2018g
が最新です. 以降は最新が 2018g
の前提で話を進めます.
ThreeTenBp と ThreeTenABP
ThreeTenBpはTime Zone情報のロード周りでメモリ効率が悪いため, Android向けにThreeTenABPが提供されています.
ThreeTenBpを使用するためにはTime Zone情報を提供する必要があります.
ThreeTenABPはその手続きを肩代わりしてくれるライブラリです.
ThreeTenABPは, ThreeTenBp(/IANA)が提供する TZDB.dat
を Assetsに内包し, AndroidThreeTen.init
でこれを ZoneRulesProvider
に登録する AssetsZoneRulesInitializerを実行します.
ThreeTenABPがやっていることはこれだけです.
自前でAssetsZoneRulesInitializer
とTZDB.dat
を用意して, ZoneRulesProvider
に登録すれば同じことが実現できるので, 次のようなクラスを用意しておけば, 好きなTZDB.dat
を登録できるようになります.
class AssetsZoneRulesInitializer(private val context: Context) : ZoneRulesInitializer() {
override fun initializeProviders() {
context.assets.open("TZDB.dat").use {
ZoneRulesProvider.registerProvider(TzdbZoneRulesProvider(it))
}
}
}
// Application.onCreateで下記を実行する
ZoneRulesInitializer.setInitializer(AssetsZoneRulesInitializer(this))
今回は, テスト用に定義した TZDB.data
を作成・登録することで, 擬似的に日本にも夏時間があることにします.
カスタム TZDB.dat 生成手順
- ThreeTenBP GitHubをクローン
- IANAから最新のTZDBをダウンロード
- クローンしたソースの
src/tzdb/{tzdb-version}
に, 展開したTZDBファイルを移動 - TZDBを編集
mvn clean package -Dtzdb-jar
を実行target/threeten-TZDB-{version}.jar
からTZDB.dat
を抽出
1. ThreeTenBP GitHubをクローン
ThreeTenBP GitHub にはTZDBを読み込んでビルドし, TZDB.dat
を生成するコンパイラ TzdbZoneRulesCompiler があります.
TzdbZoneRulesProvider
に読み込ませるTZDB.dat
を生成するためにこのレポジトリをクローンします.
2. IANAから最新のTZDBをダウンロード
TZDBを管理するInternet Assigned Numbers Authority(IANA)から最新のTZDBをダウンロードすることができます.
ダウンロードできる種類がいくつかありますが, 今回はタイムゾーン情報があればよいので “tzdata2018g.tar.gz - Data Only Distribution” を選びます.
3. クローンしたソースの src/tzdb/{tzdb-version}
に, 展開したTZDBファイルを移動
手順2でダウンロードした tar.gz
を展開するとTZDBファイルが入っています.
このTZDBファイルを, 手順1でクローンしたThreeTenBp
の src/tzdb/{tzdb-version}
ディレクトリに移動します.
クローン直後は src
ディレクトリ直下に tzdb
ディレクトリはないので作成しておきます.
また, 注意点として {tzdb-version}
の名前は下記の正規表現にマッチする必要があります
[12][0-9][0-9][0-9][A-Za-z0-9._-]+
OK: 2018g
NG: tzdb-2018g
tar.gz
を展開してできるディレクトリ名には余計なプレフィックス tzdb-
が入っているので注意が必要です.
最終的に, asia
ファイルの場所は下記になります.
{threetenbp-root}/src/tzdb/2018g/asia
4. TZDBを編集
手順3で移動したTZDB情報を編集します.
今回は日本に夏時間があった場合をシミュレーションするため “Asia/Tokyo” リージョンの情報が定義されている {threetenbp-root}/src/tzdb/2018g/asia
ファイルを編集します.
日本(Asia/Tokyo)のタイムゾーン情報は 2018g
では次のように定義されています.
# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
Rule Japan 1948 only - May Sat>=1 24:00 1:00 D
Rule Japan 1948 1951 - Sep Sat>=8 25:00 0 S
Rule Japan 1949 only - Apr Sat>=1 24:00 1:00 D
Rule Japan 1950 1951 - May Sat>=1 24:00 1:00 D
日本でも過去に夏時間(夏時刻法)があったことがわかります.
TZDBのフォーマットは人間にも読めるようになっています.
フォーマットルールはzic
man pageに載っています.
これに則り, 日本に夏時間を導入するため次の1行を追加してみましょう.
Rule Japan 2018 only - Dec 23 00:00 1:00 D
これで, TZDB的には 2018/12/23 00:00:00(JST)
から日本では夏時間(JDT)が適用されるようになります.
5. mvn clean package -Dtzdb-jar
を実行
TzdbZoneRulesCompiler
を使って編集したTZDBをもとに TZDB.dat
を生成します.
ThreeTenBpのルートで下記のコマンドを実行するとTzdbZoneRulesCompiler
がビルドを始めます.
mvn clean package -Dtzdb-jar
実行するとビルドログが出力されます.
下記のように Source directory contains no valid source folders
のログが出力される場合はTZDBのディレクトリ名かパスが間違っており, TzdbZoneRulesCompiler
がTZDBをうまく認識できていない可能性があります. その場合は手順2をやり直しましょう.
Source filenames not specified, using default set
(africa antarctica asia australasia backward etcetera europe northamerica southamerica)
は, 今回特にファイル名を指定していないので出力されても問題ありません.
...
[INFO] --- exec-maven-plugin:1.2.1:java (default) @ threetenbp ---
Source filenames not specified, using default set
(africa antarctica asia australasia backward etcetera europe northamerica southamerica)
Source directory contains no valid source folders: xxx
...
編集したTZDBがうまく読み込まれなかった場合も BUILD SUCCESS
となるので注意してください. その場合, 後述の threeten-TZDB-2018g.jar
が出力されません.
6. target/threeten-TZDB-{version}.jar
から TZDB.dat
を抽出
TzdbZoneRulesCompiler
のビルド結果はThreeTenBpプロジェクトルート直下の target
ディレクトリに出力されます.
ビルドが成功すると target/threeten-TZDB-2018g.jar
が出力されます.
このJarファイルに目的のTZDB.dat
が含まれているので, それを抽出します.
下記のJarコマンドで内包されているファイルパスの一覧を取得します.
jar tf {threeten-TZDB-2018g.jar のパス}
今回のケースでは org/threeten/bp/TZDB.dat
にTZDB.dat
がありました.
同じくJar コマンドでこれを抽出します.
jar -xvf {threeten-TZDB-2018g.jar のパス} org/threeten/bp/TZDB.dat
コマンドを実行したディレクトリに org/threeten/bp/TZDB.dat
が抽出されます.
テスト
日本にも夏時間が定義されたTZDB.dat
が作成できたので, これをZoneRulesProvider
に登録します.
class AssetsZoneRulesInitializer(private val context: Context) : ZoneRulesInitializer() {
override fun initializeProviders() {
context.assets.open("TZDB.dat").use {
ZoneRulesProvider.registerProvider(TzdbZoneRulesProvider(it))
}
}
}
// Application.onCreateで下記を実行する.
// AndroidThreeTen.initは実行しない(ThreeTenABPは使わない)
ZoneRulesInitializer.setInitializer(AssetsZoneRulesInitializer(this))
この状態で, ZonedDateTime
を使って日本時間表示してみると夏時間が適用されていることがわかります.
ZonedDateTime
.now(ZoneId.of("Asia/Tokyo"))
.format(DateTimeFormatter.ISO_DATE_TIME)
// 出力: 2018-12-23T15:26:19.295+10:00[Asia/Tokyo]
ZoneId.of("Asia/Tokyo").rules.isDaylightSavings(Instant.now())
// 出力:true
以上です.
参考: