🕘

Kotlinで時間経過と時差を考慮して日時表示するテクニック

2024/08/02に公開

Kotlinで日時表示を出し分けたくて、その方法を調べました。

やりたいこと

1. 投稿日の年月を条件次第で省略表示する

あるポストの投稿日時2024/1/1 9:00 だとして、

  • 閲覧日時2024/1/1 10:00 なら
    • 同日なので、投稿日時9:00 と出す
  • 閲覧日時2024/1/2 9:00 なら
    • 日をまたいだので、投稿日時1/1 9:00 と出す
  • 閲覧日時2025/1/1 9:00 なら
    • 年をまたいだので、投稿日時2024/1/1 9:00 と出す

SNSとかでよくあるやつです。

2. 投稿と閲覧が別の地域で行われたら、時差を考慮する

あるポストの投稿場所東京投稿日時2024/1/1 9:00 だとして、

  • 閲覧場所ワシントン閲覧日時2024/1/1 10:00 なら
    • -17時間の時差で違う年になってしまうので、投稿日時2023/12/31 17:00 と出す

この複雑なロジックをKotlinで実装してみます。

やりかた

実装

2つのInstantZonedDateTimeに変換して計算します。

fun formatPostedTime(
    postedTime: Instant,
    browseTime: Instant,
    locale: Locale,
    zoneId: String,
): String {
    val zone = ZoneId.of(zoneId, ZoneId.SHORT_IDS)
    val postedTimeZoned = ZonedDateTime.ofInstant(postedTime, zone)
    val browseTimeZoned = ZonedDateTime.ofInstant(browseTime, zone)

    val sameDay = postedTimeZoned.toLocalDate() == browseTimeZoned.toLocalDate()
    val sameYear = postedTimeZoned.year == browseTimeZoned.year

    val pattern = if (sameDay) {
        "H:mm"
    } else if (sameYear) {
        "M/d H:mm"
    } else {
        "yyyy/M/d H:mm"
    }
    return postedTimeZoned.format(DateTimeFormatter.ofPattern(pattern, locale))
}

テスト

パラメタライズドテストで、いろんな条件をテストします。サマータイムの切り替えの境目も考慮します。

@RunWith(Parameterized::class)
class FormatPostedTimeTest(
    private val testCase: String,
    private val postedUTCTime: String,
    private val browseUTCTime: String,
    private val locale: Locale,
    private val zoneId: String,
    private val expected: String,
) {
    private companion object {
        @JvmStatic
        @Parameterized.Parameters(name = "{0}")
        fun testParameters(): List<Array<Any>> {
            return listOf(
                arrayOf(
                    "東京、同年、同日",
                    "2024-01-01T00:00:00Z",
                    "2024-01-01T01:00:00Z",
                    Locale.JAPAN,
                    "JST",
                    "9:00"
                ),
                arrayOf(
                    "東京、同年、別日",
                    "2024-01-01T00:00:00Z",
                    "2024-01-02T00:00:00Z",
                    Locale.JAPAN,
                    "JST",
                    "1/1 9:00"
                ),
                arrayOf(
                    "東京、別年",
                    "2024-01-01T00:00:00Z",
                    "2025-01-01T00:00:00Z",
                    Locale.JAPAN,
                    "JST",
                    "2024/1/1 9:00"
                ),
                arrayOf(
                    "ワシントン、同年、同日",
                    "2024-01-01T08:00:00Z",
                    "2024-01-01T09:00:00Z",
                    Locale.US,
                    "PST",
                    "0:00"
                ),
                arrayOf(
                    "ワシントン、同年、別日",
                    "2024-01-01T08:00:00Z",
                    "2024-01-02T08:00:00Z",
                    Locale.US,
                    "PST",
                    "1/1 0:00"
                ),
                arrayOf(
                    "ワシントン、別年",
                    "2024-01-01T08:00:00Z",
                    "2025-01-01T08:00:00Z",
                    Locale.US,
                    "PST",
                    "2024/1/1 0:00"
                ),
                arrayOf(
                    "ワシントン、別年、サマータイム開始直前",
                    "2024-03-10T09:59:59Z",
                    "2025-03-10T09:59:59Z",
                    Locale.US,
                    "PST",
                    "2024/3/10 1:59"
                ),
                arrayOf(
                    "ワシントン、別年、サマータイム開始直後",
                    "2024-03-10T10:00:00Z",
                    "2025-03-10T10:00:00Z",
                    Locale.US,
                    "PST",
                    "2024/3/10 3:00"
                ),
                arrayOf(
                    "ワシントン、別年、サマータイム終了直前",
                    "2024-11-03T08:59:59Z",
                    "2025-11-03T08:59:59Z",
                    Locale.US,
                    "PST",
                    "2024/11/3 1:59"
                ),
                arrayOf(
                    "ワシントン、別年、サマータイム終了直後",
                    "2024-11-03T09:00:00Z",
                    "2025-11-03T09:00:00Z",
                    Locale.US,
                    "PST",
                    "2024/11/3 1:00"
                )
            )
        }
    }

    @Test
    fun testFormatPostedTime() {
        val actual = formatPostedTime(
            postedTime = Instant.parse(postedUTCTime),
            browseTime = Instant.parse(browseUTCTime),
            locale = locale,
            zoneId = zoneId
        )
        assertEquals(expected, actual)
    }
}

テスト結果

テストは全パス!!

補足

Androidで実装するなら、getBestDateTimePatternを使って地域に応じた時刻表示(ワシントンならJanuary 2, 2024, 10:00)に変換、いわゆる多言語対応も簡単にできます!

以上、普段の開発で気づいたことの共有でした!

宣伝

#CybozuSumerBlogFes2024 にも、アジャイルのふりかえりに関する記事を投稿します。お楽しみに!
https://cybozu.github.io/summer-blog-fes-2024/

Discussion