【Kotlin】RFC3339 UTC「Zulu」形式とエポックミリ秒の相互変換|Google Play Billing 対応
GooglePlayBilling でサブスクリプションの情報を取得する API のバージョンが変わりました。
これについては、以前紹介した通りです。
ここで気になったのが日時の表記。
「RFC3339 UTC “Zulu” format」というのが気になりますね。
今回は、この日付のフォーマット作成やエポックミリ秒への変換を試していきます。
日付フォーマットの仕様
GooglePlayBilling の API の仕様を確認すると、以下の説明が記載されています。
A timestamp in RFC3339 UTC “Zulu” format, with nanosecond resolution and up to nine fractional digits. Examples: “2014-10-02T15:01:23Z” and “2014-10-02T15:01:23.045123456Z”.
purchases.subscriptionsv2
これまで通りエポックミリ秒でもいいのですが、レスポンスの中身を見ただけで日時がわかるというメリットはあります。
エポックミリ秒だと、日付フォーマットに変換しないとわからないですからね。
現在日時のZuluフォーマット
Kotlin で現在日時を Zulu 形式にするなら、常に UTC を指す java.time.Instant を使うのが確実です。
Instant.now().toString() は、末尾に Z が付いた RFC3339(ISO 8601)形式の文字列をそのまま返してくれます。
import java.time.Instant
fun main() {
val zulu = Instant.now().toString()
println(zulu)
}このコードを日本時間(JST)の 2022 年 11 月 16 日 19 時頃に実行すると、UTC では 9 時間前の 10 時台になるため、結果は以下のようになります(ミリ秒以下の桁数は実行環境によって前後します)。
2022-11-16T10:08:20.669Z
【注意】LocalDateTime に 'Z' を付けても UTC にはなりません
古い記事やサンプルでよく見かけるのが、次のように LocalDateTime.now() をフォーマットして、末尾に 'Z' を付ける書き方です。
// アンチパターン:ローカル時刻に 'Z' を付けているだけ
val zulu = LocalDateTime.now().format(
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
)この 'Z' はパターン内の単なる文字リテラルなので、実際にはローカル時刻(例:JST)の末尾に Z を貼り付けているだけです。
つまり、本当は UTC ではないのに「UTC(Z)」を名乗る、9 時間ずれた文字列になってしまいます。
ミリ秒 3 桁固定など書式を細かく指定したい場合は、フォーマッタに UTC を指定して Instant を渡せば、表示の Z と実際のタイムゾーンが一致します。
import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
fun main() {
val fmt = DateTimeFormatter
.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.withZone(ZoneOffset.UTC)
println(fmt.format(Instant.now()))
}Zuluフォーマットからエポックミリ秒への変換
では、GooglePlayBilling からレスポンスされた日付項目を、エポックミリ秒に変換する方法を考えてみます。
ここでは上記で使用した現在日時をサンプルとします。
Instant.parse() は末尾 Z 付きの文字列を UTC として正しく解釈し、toEpochMilli() でエポックミリ秒に変換できます。
import java.time.Instant
fun main() {
val zulu = Instant.now().toString()
val em = Instant.parse(zulu).toEpochMilli()
println(em)
}結果は以下の通りです。
1668593300669
まとめ
RFC3339 UTC 「Zulu」 形式の日付フォーマットの変換について紹介してきました。
相互変換ができるようにしておけば、日付の扱いで困ることもありませんね。
自分のシステムだけであれば日付フォーマットは統一しておけますが、外部システムが絡むと厄介。
これが正解かと言われると未知数なので、Java の新しい日付クラスの情報も追いかけておきたいところです。
