タイムゾーン
ClickHouse における DateTime 文字列の変換方法
DateTime 文字列の値を変換する際に、次のルールが適用されます。
- カラムがタイムゾーン付きで定義されている場合 (
DateTime64(9, ‘Asia/Tokyo’)) 、その文字列の値はそのタイムゾーンの タイムスタンプ として扱われます。2026-01-01 13:00:00は、UTCでは2026-01-01 04:00:00になります。 - カラムにタイムゾーンの定義がない場合は、server のタイムゾーンのみが使用されます。重要:
session_timezone設定は影響しません。したがって、server のタイムゾーンがUTCで、session のタイムゾーンがAmerica/Los_Angelesであっても、2026-01-01 13:00:00はUTC時刻として書き込まれます。 - タイムゾーン定義のないカラムから値を読み取る場合は、
session_timezoneが使用され、設定されていなければ server のタイムゾーンが使用されます。そのため、タイムスタンプ を文字列として読み取る際はsession_timezoneの影響を受けることがあります。これは問題ではありませんが、覚えておく必要があります。
異なるタイムゾーン間でのタイムスタンプの書き込み
UTC-8 の us-west リージョンで動作するアプリケーションがあり、UTC では 2026-01-01 10:00:00 に相当するローカルタイムスタンプ 2026-01-01 02:00:00 を書き込む必要があるとします。
- これを文字列として書き込むには、サーバーのタイムゾーンまたはカラムのタイムゾーンに変換する必要があります。
- これを言語ネイティブの時刻構造として書き込むには、ドライバーが対象のタイムゾーンを認識している必要がありますが、次のような問題があります。
- それが常に可能とは限りません
- この用途に対してドライバー API の設計が十分ではありません
- 唯一の方法は、どのような変換が行われるかを明示し、アプリケーション側で補正できるようにすることです (または Unix タイムスタンプ を数値として書き込むことです)
Java と JDBC のタイムスタンプ API
- 実質的には Unix タイムスタンプ である
Timestampクラスを使用する方法。Calendarオブジェクトと組み合わせて使用すると、カレンダーのタイムゾーンでTimestampを再解釈できます。Timestampには、分かりにくい内部カレンダーがあります。
- 任意のタイムゾーンに簡単に変換できる
LocalDateTimeクラスを使用する方法。ただし、変換先のタイムゾーンを渡すためのメソッドはありません。 ZonedDateTimeクラスを使用する方法。これは、タイムゾーンを持たないDateTimeに書き込む際のタイムゾーン変換に役立ちます (サーバーのタイムゾーンを使うことが分かっているためです) 。- ただし、タイムゾーンが定義されたカラムに
ZonedDateTimeを書き込む場合は、ユーザー側でドライバーによる変換を補正する必要があります。
- ただし、タイムゾーンが定義されたカラムに
Longを使用して Unix タイムスタンプ のミリ秒を書き込む方法。Stringを使用して、すべての変換をアプリケーション側で行う方法 (あまり移植性は高くありません) 。
Date
Date と Date32 があります。どちらの型も、Epoch (1970-01-01) からの日数を使用します。Date では正の値の日数のみを使用するため、扱える範囲は 2149-06-06 までです。Date32 は 1970-01-01 より前の日付も扱えるように負の値の日数に対応していますが、範囲はより狭くなります (1900-01-01 から 2100-01-01 までで、0 は 1970-01-01 を表します) 。ClickHouse は 2026-01-01 をどのタイムゾーンでも 2026-01-01 として扱い、カラム定義には タイムゾーン パラメータはありません。
java.time.LocalDate の使用
java.time.LocalDate です。クライアントは、このクラスを使用して Date および Date32 カラムの値を保持します (LocalDate.ofEpochDay((long)readUnsignedShortLE()) を読み取る場合) 。
java.time.LocalDate は タイムゾーン の変換の影響を受けず、モダンな time API の一部でもあるため、使用を推奨します。
java.sql.Date の使用
LocalDate は Java 8 で導入されました。それ以前は、日付の読み書きには java.sql.Date が使われていました。内部的には、このクラスは instant (絶対的な時点を表す時刻値) のラッパーです。そのため、toString() は JVM の タイムゾーン によって異なる日付を返します。したがって、ドライバー側では値を慎重に構築する必要があり、ユーザー側もこの点を理解しておく必要があります。
カレンダーベースの再解釈
java.sql.ResultSet には、Calendar を受け取って日付値を取得するメソッドがあり、java.sql.PreparedStatement にも同様のメソッドがあります。これは、JDBCドライバーが指定されたタイムゾーンで日付値を再解釈できるようにするために設計されたものです。たとえば、DB に 2026-01-01 という値が入っていても、アプリケーションではこの日付を Tokyo の午前0時として扱いたい場合があります。つまり、返される java.sql.Date オブジェクトは特定の時点を指すことになり、ローカルタイムゾーンに変換すると、時差の影響で別の日付になる可能性があります。LocalDate でも、java.time.LocalDate#atStartOfDay(java.time.ZoneId) を使えば同じことができます。
ClickHouse JDBCドライバーは常に、ローカル日付の午前0時を指す java.sql.Date オブジェクトを返します。つまり、日付が 2026-01-01 の場合、それは JVM のタイムゾーンでの 2026-01-01 12:00 AM を意味します (この動作は PostgreSQL および MariaDB の JDBCドライバーと同じです) 。
Time
’6:30’ はどこで読み取っても同じです。
ClickHouse Time 型
Time と Time64 は 25.6 で導入されました。それ以前は、代わりにタイムスタンプ型の DateTime と DateTime64 が使われていました (このガイドの後半で説明します) 。Time は秒数を表す 32 ビット整数として格納され、範囲は [-999:59:59, 999:59:59] です。Time64 は符号なしの Decimal64 としてエンコードされ、精度に応じて異なる時間単位を格納します。一般的な値は 3 (ミリ秒) 、6 (マイクロ秒) 、9 (ナノ秒) です。精度の値の範囲は [0, 9] です。
Java 型マッピング
Time と Time64 を読み取り、LocalDateTime として格納します。これは負の時間範囲をサポートするためです (LocalTime ではサポートできません) 。この場合、日付部分にはエポック日付 1970-01-01 が使われるため、負の値はこの日付より前になります。
時間型のサポートの中核は、LocalTime (値が1日以内の場合) と、値の全範囲を扱うための Duration を使って実装されています。LocalDateTime は読み取りにのみ使用できます。
java.sql.Time の使用
java.sql.Time を使用できるのは、LocalTime の範囲内に限られます。内部的には、java.sql.Time は文字列リテラルに変換されます。値は、PreparedStatement#setTime() で Calendar パラメータを使用することで変更される場合があります。
toTime 関数
toTimeには常にDate、DateTime、または同様の型が必要です。文字列は受け付けません。関連する issue: https://github.com/ClickHouse/ClickHouse/issues/89896- これは
toTimeWithFixedDateの alias です。 - タイムゾーン に関連する issue があります: https://github.com/ClickHouse/ClickHouse/pull/90310
タイムスタンプ
1970-01-01 00:00:00 UTC からの経過秒数として任意の時点を表します (秒数が負なら Unix 時間以前のタイムスタンプ、正ならそれ以後のタイムスタンプを表します) 。この表現は、観測者が UTC タイムゾーンにいる場合や、ローカルタイムゾーンではなく UTC を使用する場合には、計算や取り扱いが容易です。
ClickHouse の Timestamp 型
DateTime (32 ビット整数で、分解能は常に秒) と DateTime64 (64 ビット整数で、分解能は定義によって異なります) というタイムスタンプ型があります。値は常に UTC のタイムスタンプとして保存されます。つまり、数値として表現される場合、タイムゾーン変換は適用されません。
文字列表現とタイムゾーンの挙動
- カラム定義でタイムゾーンが指定されておらず、書き込み時に文字列が渡された場合、その文字列はサーバーのタイムゾーンから UTC のタイムスタンプ値に変換されます。このようなカラムから値を読み取る際には、UTC のタイムスタンプから、サーバーまたはセッションのタイムゾーンを用いたタイムスタンプリテラルに変換されます (同様の処理は、タイムゾーンが明示的に定義されていない式内のタイムスタンプリテラルにも適用されます) 。
- カラム定義でタイムゾーンが指定されている場合、すべての文字列変換でそのタイムゾーンのみが使用されます。これはタイムゾーンが指定されていない場合のロジックとは異なるため、クエリ内の各カラムに対してデータがどのように書き込まれるかを十分に理解しておく必要があります。
- タイムゾーンを含むフォーマットの文字列として日付が渡される場合は、変換関数が必要です。通常は
parseDateTimeBestEffortを使用します。
JDBCドライバーでのタイムスタンプの扱い
DateTime と DateTime64 は、クライアント側では java.time.ZonedDateTime として読み取られ、保存されます。これにより、このような値を任意の他のタイムゾーンに変換しやすくなります (タイムゾーン情報は保持されます) 。
toDateTime64 でよくある落とし穴
toDateTime64 がサーバーのタイムゾーンを使用し、ソースのタイムゾーンを認識しないために発生します。
変換テーブル
Date カラムには時刻部分がないため、java.sql.Timestamp として読み取ることはできません。
ドライバーは整数値を日付/時刻の値に変換しません。pstmt.setLong("timestamp", 1772132359L) を呼び出すと、1772132359 は数値としてサーバーに書き込まれ、秒単位の UTC Unix タイムスタンプ として扱われます。
PreparedStatement#setObject で値を書き込む
PreparedStatement#setObject(column, value) で設定したときに、値がどのように変換されるかを示しています。
カラムの型は不明なものとして扱う必要があります。プリペアドステートメントに何を渡すかは、アプリケーション側で判断する必要があります。
ResultSet#getObject を使用した値の読み取り
ResultSet#getObject(column, class) で読み取った際に、値がどのように変換されるかを示しています。
カレンダーベースのメソッドの使用
PreparedStatement#setTime(param, value, calendar) および PreparedStatement#setDate(param, value, calendar) を使って格納されている場合は、対応する ResultSet#getTime(column, calendar) と ResultSet#getDate(column, calendar) を使用してください。