概要
Android アプリにて、まれに日付に表記が未来になる事象がありました。
原因は SimpleDateFormat がスレッドセーフではないのに、複数スレッドから使用していたことが原因でした。
SimpleDateFormat を使い続けるなら各スレッドごとにインスタンス生成、もしくは、 DateTimeFormatter や ThreeTenABP などを利用すると良いでしょう。
事象
2019-10-05
と表示されるべき所に 4520-10-05
のような未来の日付が表示されることがある。
(再現性はまちまち)
再現確認
private val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.JAPAN)
fun hoge(){
(1..20).forEach {
Thread {
val dateTime = sdf.parse("2019-10-05T01:23:45+09:00")?.time ?: 0
Log.d("★", sdf.format(Date(dateTime)))
}.start()
}
}
実行結果
2019-10-05T01:23:45+0900
4520-10-05T01:23:45+0900 // !!
2019-10-05T01:23:45+0900
2019-10-05T01:23:45+0900
2019-10-05T01:23:45+0900
2019-10-05T01:23:45+0900
4520-10-05T01:23:45+0900 // !!
2019-10-05T01:23:45+0900
2019-10-05T01:23:45+0900
2019-10-05T01:23:45+0900
2019-10-05T01:23:45+0900
2019-10-05T01:23:45+0900
2019-10-05T01:23:45+0900
2019-10-05T01:23:45+0900
2019-10-05T01:23:45+0900
4520-10-05T01:23:45+0900 // !!
2019-10-05T01:23:45+0900
2019-10-05T01:23:45+0900
2019-10-05T01:23:45+0900
2019-10-05T01:23:45+0900
仕様
Synchronization
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
たしかに、スレッドセーフではない旨が書かれていますね。
対応
修正範囲をミニマムにするのであれば、SimpleDateFormat を使うときに都度生成すれば事足ります。
private val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.JAPAN)
↓
private val sdf get() = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.JAPAN)
また、DateTimeFormatter や ThreeTenABP などを利用するという手もあります。
さいごに
ハマった人の救いになれば幸いです。