2025年4月5日土曜日

Kotlin Multiplatformで日時をフォーマットする

Kotlin Multiplatformで日付を言語ロケールに合わせてフォーマットしたい場合はどうしたらいいでしょうか。 Kotlin Multiplatformで日時を扱うには kotlinx-datetime ライブラリを利用することが一般的だと思います。このライブラリは基本的な日時操作機能を提供していますが、ロケールに合わせたフォーマット機能はありません。

expect/actualで実装する

現状では expect/actual 機構を使って、各プラットフォーム固有の日時フォーマットを実装するのが現実的です。 以下はその実装例です。

共通モジュールでの定義

まず、共通モジュールに機能を定義します。

// 共通モジュールに定義
expect class DateTimeFormatter() {
    fun format(instant: Instant, timeZone: TimeZone): String
}

JVM(Android)実装

JVMプラットフォームではJava標準ライブラリの日時フォーマット機能を使います。

actual class DateTimeFormatter {
    private val formatter = java.time.format.DateTimeFormatter
        .ofLocalizedDateTime(java.time.format.FormatStyle.MEDIUM)
        .withLocale(java.util.Locale.getDefault())

    actual fun format(instant: Instant, timeZone: TimeZone): String {
        val javaInstant = java.time.Instant.ofEpochSecond(
            instant.epochSeconds, instant.nanosecondsOfSecond.toLong()
        )
        val javaZoneId = java.time.ZoneId.of(timeZone.id)
        return formatter.format(java.time.ZonedDateTime.ofInstant(javaInstant, javaZoneId))
    }
}

iOS実装

iOS(Native)ターゲットでは、Objective-C/Swiftの日時フォーマットAPI(NSDateFormatter)を使用します。

actual class DateTimeFormatter {
    actual fun format(instant: Instant, timeZone: TimeZone): String {
        val nsDate = NSDate.dateWithTimeIntervalSince1970(instant.epochSeconds.toDouble())
        val formatter = NSDateFormatter().apply {
            dateStyle = NSDateFormatterStyle.NSDateFormatterMediumStyle
            timeStyle = NSDateFormatterStyle.NSDateFormatterMediumStyle
            timeZone = NSTimeZone.timeZoneWithName(timeZone.id)
        }
        return formatter.stringFromDate(nsDate)
    }
}

JavaScript実装

WebやNode.jsなどのJavaScriptターゲットでは、Intl APIを活用します。

actual class DateTimeFormatter {
    actual fun format(instant: Instant, timeZone: TimeZone): String {
        val date = Date(instant.toEpochMilliseconds())
        return date.toLocaleString(undefined, object : DateTimeFormatOptions {
            override var timeZone = timeZone.id
            override var dateStyle = "medium"
            override var timeStyle = "medium"
        })
    }
}

実際の使用例

上記の実装を実際に呼び出すには下のコードのようにします。

import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime

fun main() {
    val now = Clock.System.now()
    val formatter = DateTimeFormatter()
    val localTimeZone = TimeZone.currentSystemDefault()

    // 現在時刻をローカルのタイムゾーンとロケールでフォーマット
    val formattedDateTime = formatter.format(now, localTimeZone)
    println(formattedDateTime)
}

この例ではシステムの現在時刻を取得し、ユーザーのシステムデフォルトのタイムゾーンとロケールに合わせてフォーマットしています。

まとめ

Kotlin Multiplatformでのロケール対応の日時フォーマットは、今のことろKotlinの世界だけでは実現できません。今後、kotlinx.datetimeライブラリなどで対応が進められるかもしれません。それまではこの記事で紹介した方法で対応しましょう。