⏱️

DbUnitのTIMESTAMP型のカラムに正確な日時を渡す

2020/11/04に公開

DbUnitで(ファイルではなく)コード上でアサーションの期待値を設定したいとき、ITableのインスタンスを作りますが、その際Datatype.TIMESTAMP型のカラムにはjava.sql.Timestamp型の値を自分で作って入れるのが一番正確でよさそうという話です。

コード例はKotlinです。(Javaや他のJVM言語を使用している場合は適宜読み替えてください)

課題

RDBのTimestamp型のカラムをアサートしたい。
以下のようなイメージ。この例では、timestampがそれにあたる。

import org.dbunit.Assertion
import org.dbunit.database.IDatabaseConnection
import org.dbunit.dataset.Column
import org.dbunit.dataset.DefaultTable
import org.dbunit.dataset.datatype.DataType

fun assertionExample() {
    val expected = DefaultTable("test_table", arrayOf(
        Column("value", DataType.VARCHAR),
        Column("timestamp", DataType.TIMESTAMP)
    ))
    expected.addRow(arrayOf(
        "test_value",
        "2020-11-04T19:00:54.663674+09:00"
    ))

    val connection: IDatabaseConnection = createConnection() // connectionの生成は省略
    val actual = connection.createDataSet().getTable("test_table")

    Assertion.assertEquals(expected, actual)
}

しかし、TIMESTAMP型のカラムに入れられる値はなかなか限られていて、たとえばこの例(ISO8601形式の文字列を入れる場合)は以下のエラーで失敗する。

org.dbunit.dataset.datatype.TypeCastException: Unable to typecast value <2020-11-04T19:00:54.663674+09:00> of type <java.lang.String> to TIMESTAMP

では、どのような値なら入れられるのか?

解決

利用側でjava.sql.Timestamp型を作れば、正確な日時を渡すことができる。

今回の例では、以下のようにして渡すことになる。

// importは省略

fun assertionExample() {
    val offsetDateTime = OffsetDateTime.parse("2020-11-04T19:00:54.663674+09:00")
    val instant = offsetDateTime.toInstant()
    val expectedTimestamp = java.sql.Timestamp.from(instant)

    val expected = DefaultTable("test_table", arrayOf(
        Column("value", DataType.VARCHAR),
        Column("timestamp", DataType.TIMESTAMP)
    ))
    expected.addRow(arrayOf(
        "test_value",
        expectedTimestamp
    ))

    // アサーションは省略
}

説明

TimestampDataType型のコードを読むと、typeCastというメソッドでさまざまな値からjava.sql.Timestamp型への変換に対応していることがわかる。
ここで変換した値が、最終的なアサーションに使われる。

しかし、おおむねレガシーなjava.util.Dateを中心とした構成になっていて、Java8で導入されたNew Date/Time APIにまつわる型や文字列フォーマットには対応していない。
また、Date型の精度はミリ秒であり、ナノ秒単位の値を保持することができない。そのため、どのような渡し方をした場合でも、Date型を経由した時点で精度がミリ秒に落ちてしまい、ナノ秒単位で値が保持されているTimestampとのアサーションが失敗する事態に陥る。

結果として、最も正確な値でアサーションするには、New Date/Time APIの型だけを経由して、利用側でjava.sql.Timestamp型を作って渡すのがよいということになる。

Discussion