Fuseaux horaires
Comment ClickHouse convertit les chaînes DateTime
DateTime :
- Si une colonne est définie avec un fuseau horaire (
DateTime64(9, ‘Asia/Tokyo’)), la valeur de chaîne est alors interprétée comme un timestamp dans ce fuseau horaire.2026-01-01 13:00:00correspondra à2026-01-01 04:00:00enUTC. - Si une colonne n’a pas de définition de fuseau horaire, seul le fuseau horaire du serveur est utilisé. Important : le paramètre
session_timezonen’a aucun effet. Ainsi, si le fuseau horaire du serveur estUTCet celui de la sessionAmerica/Los_Angeles,2026-01-01 13:00:00sera écrit enUTC. - Lorsqu’une valeur est lue depuis une colonne sans définition de fuseau horaire,
session_timezoneest utilisé ou, s’il n’est pas défini, le fuseau horaire du serveur. C’est pourquoi la lecture des timestamps sous forme de chaînes peut être affectée parsession_timezone. Il n’y a rien d’anormal à cela, mais il faut le garder à l’esprit.
Écriture de timestamps entre fuseaux horaires
us-west, avec le fuseau horaire local UTC-8, et que nous devions écrire un timestamp local 2026-01-01 02:00:00, qui correspond en UTC à 2026-01-01 10:00:00 :
- L’écrire sous forme de chaîne nécessite de le convertir dans le fuseau horaire du serveur ou de la colonne.
- L’écrire sous forme de structure temporelle native au langage nécessite que le pilote connaisse le fuseau horaire cible, mais :
- Ce n’est pas toujours possible
- L’API du pilote est mal conçue pour cela
- La seule solution consiste à décrire les transformations qui seront appliquées afin que l’application puisse compenser (ou écrire un timestamp Unix sous forme numérique)
API de timestamp Java et JDBC
- Utiliser la classe
Timestamp, qui correspond en réalité à un timestamp Unix.- Lorsqu’elle est utilisée avec un objet
Calendar, elle permet de réinterpréter leTimestampdans le fuseau horaire du calendrier. Timestamppossède un calendrier interne peu évident.
- Lorsqu’elle est utilisée avec un objet
- Utiliser la classe
LocalDateTime, qui se convertit facilement vers n’importe quel fuseau horaire, mais aucune méthode ne permet de lui passer un fuseau horaire cible. - Utiliser la classe
ZonedDateTime, qui facilite la conversion de fuseau horaire lors de l’écriture dans unDateTimesans fuseau horaire (puisque nous savons qu’il faut utiliser le fuseau horaire du serveur).- En revanche, écrire un
ZonedDateTimedans une colonne avec un fuseau horaire défini oblige l’utilisateur à compenser la conversion du pilote.
- En revanche, écrire un
- Utiliser
Longpour écrire des millisecondes de timestamp Unix. - Utiliser
Stringpour effectuer toutes les conversions côté application (ce qui n’est pas très portable).
Date
Date et Date32 permettent de stocker des dates. Tous deux utilisent un nombre de jours écoulés depuis l’epoch (1970-01-01). Date n’utilise que des nombres de jours positifs, sa plage se terminant donc au 2149-06-06. Date32 gère les nombres de jours négatifs pour couvrir les dates antérieures au 1970-01-01, mais sa plage est plus restreinte (du 1900-01-01 au 2100-01-01, où 0 correspond à 1970-01-01). ClickHouse considère 2026-01-01 comme 2026-01-01 quel que soit le fuseau horaire, et il n’existe pas de paramètre de fuseau horaire dans les définitions de colonnes.
Utilisation de java.time.LocalDate
java.time.LocalDate. Le client utilise cette classe pour stocker la valeur des colonnes Date et Date32 (lecture de LocalDate.ofEpochDay((long)readUnsignedShortLE())).
Nous recommandons d’utiliser java.time.LocalDate, car cette classe n’est pas affectée par les changements de fuseau horaire et fait partie de l’API moderne de gestion du temps.
Utilisation de java.sql.Date
LocalDate a été introduit dans Java 8. Auparavant, java.sql.Date était utilisé pour écrire/lire des dates. En interne, cette classe est un wrapper autour d’un instant (une valeur temporelle représentant un point absolu dans le temps). De ce fait, toString() renvoie une date différente selon le fuseau horaire de la JVM. Cela oblige le pilote à construire soigneusement les valeurs et demande à l’utilisateur d’en être conscient.
Réinterprétation basée sur le calendrier
java.sql.ResultSet possède une méthode pour obtenir des valeurs de date qui accepte un Calendar, et il existe une méthode similaire dans java.sql.PreparedStatement. Cela a été conçu pour permettre au ClickHouse JDBC pilote de réinterpréter une valeur de date dans le fuseau horaire spécifié. Par exemple, la DB contient la valeur 2026-01-01, mais l’application veut interpréter cette date comme minuit à Tokyo. Cela signifie que l’objet java.sql.Date renvoyé se verra attribuer un instant précis et, une fois converti dans le fuseau horaire local, il pourra correspondre à une date différente en raison du décalage horaire. On peut obtenir le même résultat avec LocalDate en utilisant java.time.LocalDate#atStartOfDay(java.time.ZoneId).
Le ClickHouse JDBC pilote renvoie toujours un objet java.sql.Date qui correspond à la date locale à minuit. En d’autres termes, si la date est 2026-01-01, cela signifie 2026-01-01 12:00 AM dans le fuseau horaire de la JVM (le même comportement que les pilotes JDBC PostgreSQL et MariaDB).
Time
Time, comme les valeurs Date, sont dans la plupart des cas indépendantes du fuseau horaire. ClickHouse n’effectue aucune transformation des valeurs littérales d’heure vers un quelconque fuseau horaire — ’6:30’ reste identique, quel que soit l’endroit où il est lu.
Types Time de ClickHouse
Time et Time64 ont été introduits dans 25.6. Auparavant, on utilisait à la place les types d’horodatage DateTime et DateTime64 (abordés plus loin dans ce guide). Time est stocké sous la forme d’un entier 32 bits représentant un nombre de secondes, dans la plage [-999:59:59, 999:59:59]. Time64 est encodé sous la forme d’un Decimal64 non signé et stocke différentes unités de temps selon la précision. Les valeurs courantes sont 3 (millisecondes), 6 (microsecondes) et 9 (nanosecondes). La plage des valeurs de précision est [0, 9].
Correspondance des types Java
Time et Time64 et les stocke sous forme de LocalDateTime. Ce choix permet de prendre en charge les intervalles de temps négatifs (LocalTime ne les prend pas en charge). Dans ce cas, la partie date correspond à la date Epoch 1970-01-01 ; les valeurs négatives seront donc antérieures à cette date.
La prise en charge principale des types temporels repose sur LocalTime (lorsque la valeur tient sur une journée) et sur Duration pour couvrir toute la plage de valeurs. LocalDateTime ne peut être utilisé qu’en lecture.
Utilisation de java.sql.Time
java.sql.Time est limitée à l’intervalle de LocalTime. En interne, java.sql.Time est converti en chaîne littérale. La valeur peut être modifiée à l’aide d’un paramètre Calendar avec PreparedStatement#setTime().
La fonction toTime
toTimerequiert toujoursDate,DateTimeou un autre type similaire. Elle n’accepte pas les chaînes de caractères. issue associé : https://github.com/ClickHouse/ClickHouse/issues/89896- C’est un alias de
toTimeWithFixedDate. - Il existe un problème lié au fuseau horaire : https://github.com/ClickHouse/ClickHouse/pull/90310
Timestamp
1970-01-01 00:00:00 UTC (un nombre de secondes négatif représente un timestamp antérieur à l’époque Unix, et un nombre positif un timestamp postérieur). Cette représentation est facile à calculer et à manipuler si l’observateur se trouve dans le fuseau horaire UTC ou l’utilise à la place de son fuseau local.
Types de timestamp ClickHouse
DateTime (entier 32 bits, résolution toujours en secondes) et DateTime64 (entier 64 bits, dont la résolution dépend de la définition). Les valeurs sont toujours stockées sous forme de timestamps UTC. Cela signifie que, lorsqu’elles sont représentées sous forme de nombres, aucune conversion de fuseau horaire n’est appliquée.
Représentation sous forme de chaîne et comportement du fuseau horaire
- Si aucun fuseau horaire n’est spécifié dans la définition de la colonne et qu’une chaîne est fournie lors de l’écriture, elle sera convertie du fuseau horaire du serveur en un timestamp UTC numérique. Lorsqu’une valeur est lue depuis une telle colonne, elle est convertie d’un timestamp UTC en un timestamp littéral à l’aide du fuseau horaire du serveur ou de la session (une approche similaire s’applique aux timestamps littéraux dans les expressions où le fuseau horaire n’est pas explicitement défini).
- Si un fuseau horaire est spécifié dans la définition de la colonne, alors seul ce fuseau horaire est utilisé pour toutes les conversions de chaînes. Cela diffère de la logique appliquée lorsqu’aucun fuseau horaire n’est spécifié, ce qui exige de bien comprendre comment les données sont écrites pour chaque colonne de la requête.
- Si une date est fournie sous forme de chaîne dans un format qui inclut un fuseau horaire, une fonction de conversion est nécessaire. On utilise généralement
parseDateTimeBestEffort.
Comment le pilote JDBC gère les timestamps
DateTime et DateTime64 sont lus et stockés côté client sous forme de java.time.ZonedDateTime, ce qui permet de convertir ces valeurs vers n’importe quel autre fuseau horaire (les informations de fuseau horaire sont conservées).
Piège courant avec toDateTime64
toDateTime64 utilise le fuseau horaire du serveur et ne tient pas compte du fuseau horaire d’origine.
Tables de conversion
Date ne peuvent pas être lues comme des java.sql.Timestamp, car elles ne comportent pas de composante horaire.
Le pilote ne convertit pas les valeurs entières en valeurs de date/heure. L’appel à pstmt.setLong("timestamp", 1772132359L) entraîne l’écriture de 1772132359 en tant que nombre sur le serveur, où il sera interprété comme
un timestamp Unix UTC en secondes.
Écriture de valeurs avec PreparedStatement#setObject
PreparedStatement#setObject(column, value) :
Le type de la colonne doit être considéré comme inconnu. C’est à l’application de décider ce qu’elle doit transmettre à l’instruction préparée.
Lecture des valeurs avec ResultSet#getObject
ResultSet#getObject(column, class) :
Utilisation des méthodes fondées sur le calendrier
ResultSet#getTime(column, calendar) et ResultSet#getDate(column, calendar) si les valeurs ont été stockées respectivement à l’aide de PreparedStatement#setTime(param, value, calendar) et PreparedStatement#setDate(param, value, calendar).