Fusos horários
Como o ClickHouse converte strings de DateTime
DateTime:
- Se uma coluna for definida com um fuso horário (
DateTime64(9, ‘Asia/Tokyo’)), o valor da string será tratado como um timestamp nesse fuso horário.2026-01-01 13:00:00será2026-01-01 04:00:00no horárioUTC. - Se uma coluna não tiver definição de fuso horário, apenas o fuso horário do servidor será usado. Importante: a configuração
session_timezonenão tem efeito. Portanto, se o fuso horário do servidor forUTCe o fuso horário da sessão forAmerica/Los_Angeles,2026-01-01 13:00:00será gravado no horárioUTC. - Quando um valor é lido de uma coluna sem definição de fuso horário,
session_timezoneé usado ou, se não estiver definido, o fuso horário do servidor. É por isso que a leitura de timestamps como strings pode ser afetada porsession_timezone. Não há nada de errado com isso, mas é importante ter isso em mente.
Gravando timestamps em diferentes fusos horários
us-west, com fuso horário local UTC-8, e precisamos gravar um timestamp local 2026-01-01 02:00:00, que em UTC é 2026-01-01 10:00:00:
- Gravá-lo como string exige convertê-lo para o fuso horário do servidor ou da coluna.
- Gravá-lo como uma estrutura de tempo nativa da linguagem exige que o driver conheça o fuso horário de destino, mas:
- Isso nem sempre é possível
- A API do driver não foi bem projetada para isso
- A única forma é descrever quais transformações serão realizadas para que a aplicação possa compensar (ou gravar um Unix timestamp como número)
APIs de timestamp do Java e JDBC
- Use a classe
Timestamp, que na verdade é um timestamp Unix.- Quando usada com um objeto
Calendar, ela permite reinterpretar oTimestampno fuso horário do calendário. Timestamptem um calendário interno que não é muito evidente.
- Quando usada com um objeto
- Use a classe
LocalDateTime, que é fácil de converter para qualquer fuso horário, mas não há nenhum método que permita informar um fuso horário de destino. - Use a classe
ZonedDateTime, que ajuda na conversão de fuso horário ao gravar em umDateTimesem fuso horário (porque sabemos que devemos usar o fuso horário do servidor).- Mas gravar um
ZonedDateTimeem uma coluna com fuso horário definido exige que o usuário compense a conversão do driver.
- Mas gravar um
- Use
Longpara gravar milissegundos de timestamp Unix. - Use
Stringpara fazer todas as conversões do lado da aplicação (o que não é muito portátil).
Data
Date e Date32 para armazenar datas. Ambos os tipos usam um número de dias desde a epoch (1970-01-01). Date usa apenas números positivos de dias, portanto seu intervalo vai até 2149-06-06. Date32 aceita números negativos de dias para abranger datas anteriores a 1970-01-01, mas seu intervalo é menor (de 1900-01-01 a 2100-01-01, em que 0 é 1970-01-01). O ClickHouse interpreta 2026-01-01 como 2026-01-01 em qualquer fuso horário, e não há parâmetro de fuso horário nas definições de coluna.
Usando java.time.LocalDate
java.time.LocalDate. O cliente usa essa classe para armazenar os valores das colunas Date e Date32 (lendo LocalDate.ofEpochDay((long)readUnsignedShortLE())).
Recomendamos usar java.time.LocalDate porque ela não é afetada por conversões de fuso horário e faz parte da API moderna de data e hora.
Usando java.sql.Date
LocalDate foi introduzido no Java 8. Antes disso, java.sql.Date era usado para gravar e ler datas. Internamente, essa classe é um wrapper em torno de um instante (um valor de tempo que representa um ponto absoluto no tempo). Por causa disso, toString() retorna uma data diferente dependendo do fuso horário em que a JVM está. Isso exige que o driver construa os valores com cuidado e que o usuário esteja ciente disso.
Reinterpretação baseada em calendário
java.sql.ResultSet tem um método para obter valores de data que aceita um Calendar, e há um método semelhante em java.sql.PreparedStatement. Isso foi projetado para permitir que o driver JDBC reinterprete um valor de data no fuso horário especificado. Por exemplo, o DB tem o valor 2026-01-01, mas a aplicação quer ver essa data como meia-noite em Tokyo. Isso significa que o objeto java.sql.Date retornado receberá um instante específico e, ao ser convertido para o fuso horário local, poderá se tornar uma data diferente por causa da diferença de horário. Podemos obter o mesmo resultado com LocalDate usando java.time.LocalDate#atStartOfDay(java.time.ZoneId).
O driver JDBC do ClickHouse sempre retorna um objeto java.sql.Date que corresponde à data local à meia-noite. Em outras palavras, se a data for 2026-01-01, queremos dizer 2026-01-01 12:00 AM no fuso horário da JVM (o mesmo comportamento dos drivers JDBC do PostgreSQL e do MariaDB).
Hora
’6:30’ é o mesmo, independentemente de onde for lido.
Tipos Time do ClickHouse
Time e Time64 foram introduzidos na versão 25.6. Antes disso, usavam-se os tipos de timestamp DateTime e DateTime64 (abordados mais adiante neste guia). Time é armazenado como um inteiro de 32 bits que representa um número de segundos e tem intervalo [-999:59:59, 999:59:59]. Time64 é codificado como um Decimal64 sem sinal e armazena diferentes unidades de tempo, dependendo da precisão. As opções mais comuns são 3 (milissegundos), 6 (microssegundos) e 9 (nanossegundos). O intervalo de valores de precisão é [0, 9].
Mapeamento de tipos em Java
Time e Time64 e os armazena como LocalDateTime. Isso é feito para dar suporte ao intervalo de tempo negativo (LocalTime não oferece esse suporte). Nesse caso, a parte da data é a data epoch 1970-01-01, portanto os valores negativos ficarão antes dessa data.
O suporte principal para tipos de tempo é implementado com LocalTime (quando o valor está dentro de um dia) e Duration, para abranger o intervalo completo de valores. LocalDateTime pode ser usado apenas para leitura.
Usando java.sql.Time
java.sql.Time se limita ao intervalo de LocalTime. Internamente, java.sql.Time é convertido em um literal de string. O valor pode ser alterado ao usar um parâmetro Calendar com PreparedStatement#setTime().
A função toTime
toTimesempre exigeDate,DateTimeou outro tipo semelhante. Não aceita strings. Problema relacionado: https://github.com/ClickHouse/ClickHouse/issues/89896- É um alias de
toTimeWithFixedDate. - Há um problema relacionado ao fuso horário: https://github.com/ClickHouse/ClickHouse/pull/90310
Timestamp
1970-01-01 00:00:00 UTC (um número negativo de segundos representa um timestamp anterior ao tempo Unix, e um número positivo representa um posterior). Essa representação é fácil de calcular e manipular se o observador estiver no fuso horário UTC ou o utilizar em vez do fuso horário local.
Tipos de timestamp no ClickHouse
DateTime (inteiro de 32 bits, com resolução sempre em segundos) e DateTime64 (inteiro de 64 bits, cuja resolução depende da definição). Os valores são sempre armazenados como timestamps UTC. Isso significa que, quando representados como números, nenhuma conversão de fuso horário é aplicada.
Representação textual e comportamento do fuso horário
- Se nenhum fuso horário for especificado na definição da coluna e uma string for fornecida na escrita, ela será convertida do fuso horário do servidor para um timestamp UTC numérico. Quando um valor for lido dessa coluna, ele será convertido de um timestamp UTC para um timestamp literal usando o fuso horário do servidor ou da sessão (uma abordagem semelhante é aplicada a literais de timestamp em expressões em que o fuso horário não é definido explicitamente).
- Se um fuso horário for especificado na definição da coluna, somente esse fuso horário será usado em todas as conversões de string. Isso contrasta com a lógica usada quando nenhum fuso horário é especificado, portanto é necessário entender bem como os dados são gravados em cada coluna da consulta.
- Se uma data for fornecida como string em um formato que inclua um fuso horário, será necessária uma função de conversão. Normalmente, usa-se
parseDateTimeBestEffort.
Como o driver JDBC trata timestamps
DateTime e DateTime64 são lidos e armazenados no cliente como java.time.ZonedDateTime, o que facilita a conversão desses valores para qualquer outro fuso horário (as informações de fuso horário são preservadas).
Armadilha comum ao usar toDateTime64
toDateTime64 usa o fuso horário do servidor e não leva em conta o fuso horário de origem.
Tabelas de conversão
Date não podem ser lidas como java.sql.Timestamp porque não têm componente de hora.
O driver não converte valores inteiros em nenhum tipo de valor de data/hora. Chamar pstmt.setLong("timestamp", 1772132359L) fará com que 1772132359 seja gravado como um número no servidor, que o tratará como
um timestamp Unix UTC em segundos.
Gravando valores com PreparedStatement#setObject
PreparedStatement#setObject(column, value):
O tipo da coluna deve ser considerado desconhecido. Cabe ao aplicativo decidir o que passar para a instrução preparada.
Lendo valores com ResultSet#getObject
ResultSet#getObject(column, class):
Usando métodos baseados em calendário
ResultSet#getTime(column, calendar) e ResultSet#getDate(column, calendar) se os valores tiverem sido armazenados com PreparedStatement#setTime(param, value, calendar) e PreparedStatement#setDate(param, value, calendar), respectivamente.