시간대
ClickHouse가 DateTime 문자열을 변환하는 방식
DateTime 문자열 값을 변환할 때 다음 규칙을 사용합니다.
- 컬럼이 시간대와 함께 정의된 경우(
DateTime64(9, ‘Asia/Tokyo’)), 문자열 값은 해당 시간대의 타임스탬프로 처리됩니다.2026-01-01 13:00:00은UTC기준으로2026-01-01 04:00:00이 됩니다. - 컬럼에 시간대 정의가 없으면 서버 시간대만 사용됩니다. 중요:
session_timezone설정은 아무런 영향을 주지 않습니다. 따라서 서버 시간대가UTC이고 세션 시간대가America/Los_Angeles인 경우,2026-01-01 13:00:00은UTC시간으로 기록됩니다. - 시간대 정의가 없는 컬럼에서 값을 읽을 때는
session_timezone이 사용되고, 설정되지 않은 경우에는 서버 시간대가 사용됩니다. 따라서 타임스탬프를 문자열로 읽을 때는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는 시간대 변환의 영향을 받지 않으며 최신 시간 API의 일부이므로 사용을 권장합니다.
java.sql.Date 사용
LocalDate는 Java 8에서 도입되었습니다. 그전에는 날짜를 기록하고 읽는 데 java.sql.Date를 사용했습니다. 내부적으로 이 클래스는 인스턴트(시간상의 절대적인 한 시점을 나타내는 값)를 감싸는 래퍼입니다. 이 때문에 toString()은 JVM의 시간대에 따라 다른 날짜를 반환합니다. 따라서 드라이버가 값을 주의 깊게 구성해야 하며, 사용자도 이 점을 알고 있어야 합니다.
Calendar 기반 재해석
java.sql.ResultSet에는 Calendar를 받아 날짜 값을 가져오는 메서드가 있으며, java.sql.PreparedStatement에도 이와 유사한 메서드가 있습니다. 이는 JDBC 드라이버가 지정된 시간대에 맞춰 날짜 값을 다시 해석할 수 있도록 설계된 기능입니다. 예를 들어, DB에 2026-01-01이라는 값이 저장되어 있지만 애플리케이션에서는 이 날짜를 Tokyo 시간대의 자정으로 해석하려고 할 수 있습니다. 이 경우 반환되는 java.sql.Date 객체는 특정 시점을 갖게 되며, 이를 로컬 시간대로 변환하면 시차 때문에 날짜가 달라질 수 있습니다. LocalDate에서도 java.time.LocalDate#atStartOfDay(java.time.ZoneId)를 사용하면 같은 동작을 구현할 수 있습니다.
ClickHouse JDBC 드라이버는 항상 로컬 날짜의 자정을 가리키는 java.sql.Date 객체를 반환합니다. 다시 말해, 날짜가 2026-01-01이면 이는 JVM 시간대의 2026-01-01 12:00 AM을 의미합니다(동작 방식은 PostgreSQL 및 MariaDB JDBC 드라이버와 동일합니다).
시간
’6:30’은 어디서 읽어도 동일합니다.
ClickHouse Time 타입
Time 및 Time64는 25.6에 도입되었습니다. 그전에는 대신 타임스탬프 타입인 DateTime 및 DateTime64를 사용했습니다(이 가이드의 뒷부분에서 설명합니다). Time은 초를 나타내는 32비트 정수로 저장되며, 범위는 [-999:59:59, 999:59:59]입니다. Time64는 부호 없는 Decimal64로 인코딩되며, 정밀도(precision)에 따라 서로 다른 시간 단위를 저장합니다. 일반적으로는 3(밀리초), 6(마이크로초), 9(나노초)를 사용합니다. 정밀도 값의 범위는 [0, 9]입니다.
Java 타입 매핑
Time 및 Time64를 읽어 LocalDateTime으로 저장합니다. 이는 음수 시간 범위를 지원하기 위해서입니다(LocalTime은 이를 지원하지 않음). 이 경우 날짜 부분은 epoch 날짜인 1970-01-01이므로 음수 값은 이 날짜보다 이전 시점을 가리킵니다.
시간 타입의 주요 지원은 LocalTime(값이 하루 이내인 경우)과 전체 값 범위를 다루기 위한 Duration을 사용해 구현됩니다. LocalDateTime은 읽기 전용으로만 사용할 수 있습니다.
java.sql.Time 사용
java.sql.Time은 LocalTime 범위 내에서만 사용할 수 있습니다. 내부적으로 java.sql.Time은 문자열 리터럴로 변환됩니다. PreparedStatement#setTime()에 Calendar 매개변수를 사용하면 값을 변경할 수 있습니다.
toTime 함수
toTime은 항상Date,DateTime또는 이와 유사한 유형을 인수로 받습니다. 문자열은 허용되지 않습니다. 관련 이슈: https://github.com/ClickHouse/ClickHouse/issues/89896toTimeWithFixedDate의 별칭입니다.- 시간대 관련 이슈가 있습니다: https://github.com/ClickHouse/ClickHouse/pull/90310
타임스탬프
1970-01-01 00:00:00 UTC를 기준으로 경과한 초 수로 어떤 시점이든 표현합니다(Unix 시간 이전의 타임스탬프는 음수, 이후의 타임스탬프는 양수로 나타냅니다). 관측자가 UTC 시간대를 사용하거나 로컬 시간대 대신 이를 사용할 경우, 이 표현 방식은 계산하고 처리하기 쉽습니다.
ClickHouse 타임스탬프 타입
DateTime(32비트 정수, 해상도는 항상 초)과 DateTime64(64비트 정수, 해상도는 정의에 따라 달라짐) 타임스탬프 타입이 있습니다. 값은 항상 UTC 타임스탬프로 저장됩니다. 즉, 숫자로 표현할 때는 시간대 변환이 적용되지 않습니다.
문자열 표현과 시간대 동작
- 컬럼 정의에 시간대가 지정되지 않은 상태에서 쓰기 시 문자열이 전달되면, 서버 시간대 기준으로 UTC 타임스탬프 숫자로 변환됩니다. 이런 컬럼에서 값을 읽을 때는 UTC 타임스탬프가 서버 또는 session 시간대를 사용하는 리터럴 타임스탬프로 변환됩니다(비슷한 방식이 시간대가 명시적으로 정의되지 않은 표현식의 타임스탬프 리터럴에도 적용됩니다).
- 컬럼 정의에 시간대가 지정된 경우에는 모든 문자열 변환에 해당 시간대만 사용됩니다. 이는 시간대가 지정되지 않은 경우의 로직과 다르므로, 쿼리에서 각 컬럼에 데이터가 어떻게 기록되는지 정확히 이해해야 합니다.
- 시간대가 포함된 포맷의 날짜가 문자열로 전달되면 변환 함수가 필요합니다. 일반적으로
parseDateTimeBestEffort를 사용합니다.
JDBC 드라이버의 타임스탬프 처리 방식
DateTime 및 DateTime64는 클라이언트에서 java.time.ZonedDateTime으로 읽고 저장하므로, 이러한 값을 다른 시간대로 변환하는 데 도움이 됩니다(시간대 정보는 유지됩니다).
toDateTime64에서 흔히 겪는 문제
toDateTime64가 서버 시간대를 사용하며 원래 시간대는 인식하지 못하기 때문입니다.
변환 표
Date 컬럼에는 시간 정보가 없으므로 java.sql.Timestamp로 읽을 수 없습니다.
드라이버는 정수 값을 어떤 날짜/시간 값으로도 변환하지 않습니다. pstmt.setLong("timestamp", 1772132359L)를 호출하면 1772132359가 숫자 형태로 server에 기록되며, 이는
초 단위의 UTC Unix 타임스탬프로 처리됩니다.
PreparedStatement#setObject로 값 쓰기
PreparedStatement#setObject(column, value)로 값을 설정할 때 값이 어떻게 변환되는지 보여줍니다.
컬럼의 유형은 알 수 없는 것으로 간주해야 합니다. prepared statement에 무엇을 전달할지는 애플리케이션에서 결정해야 합니다.
ResultSet#getObject로 값 읽기
ResultSet#getObject(column, class)로 읽을 때 값이 어떻게 변환되는지 보여줍니다.
캘린더 기반 메서드 사용하기
PreparedStatement#setTime(param, value, calendar) 및 PreparedStatement#setDate(param, value, calendar)로 저장했다면 ResultSet#getTime(column, calendar) 및 ResultSet#getDate(column, calendar)를 사용하십시오.