Qualquer estratégia para armazenar dados de data e hora no PostgreSQL deve, IMO, contar com estes dois pontos:
- Sua solução deve nunca dependem da configuração de fuso horário do servidor ou cliente.
- Atualmente, o PostgreSQL (como a maioria dos bancos de dados) não possui um tipo de dados para armazenar um arquivo completo data e hora com fuso horário. Então, você precisa decidir entre um
Instant
ou umLocalDateTime
tipo de dados.
Minha receita segue.
Se você quiser gravar o instantâneo físico quando um determinado evento ocorreu, (um verdadeiro "timestamp " , normalmente algum evento de criação/modificação/exclusão), então use:
- Java:
Instant
(Java 8 ou Jodatime). - JDBC:
java.sql.Timestamp
- PostgreSQL:
TIMESTAMP WITH TIMEZONE
(TIMESTAMPTZ
)
(Não deixe que tipos de dados peculiares do PostgreSQL
WITH TIMEZONE
/WITHOUT TIMEZONE
confundir você:nenhum deles realmente armazena um fuso horário) Algum código clichê:o seguinte assume que
ps
é uma PreparedStatement
, rs
um ResultSet
e tzUTC
é um Calendar
estático objeto correspondente a UTC
fuso horário. public static final Calendar tzUTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Escreva
Instant
para o banco de dados TIMESTAMPTZ
:Instant instant = ...;
Timestamp ts = instant != null ? Timestamp.from(instant) : null;
ps.setTimestamp(col, ts, tzUTC); // column is TIMESTAMPTZ!
Leia
Instant
do banco de dados TIMESTAMPTZ
:Timestamp ts = rs.getTimestamp(col,tzUTC); // column is TIMESTAMPTZ
Instant inst = ts !=null ? ts.toInstant() : null;
Isso funciona com segurança se seu tipo de PG for
TIMESTAMPTZ
(Nesse caso, o calendarUTC
não tem efeito nesse código; mas é sempre aconselhável não depender de fusos horários padrão). "Com segurança" significa que o resultado não dependerá do fuso horário do servidor ou do banco de dados , ou informações de fusos horários:a operação é totalmente reversível e, aconteça o que acontecer com as configurações de fusos horários, você sempre obterá o mesmo "instante de tempo" que tinha originalmente no lado do Java. Se, em vez de um carimbo de data/hora (um instante na linha do tempo física), você estiver lidando com uma data e hora local "civil" (ou seja, o conjunto de campos
{year-month-day hour:min:sec(:msecs)}
), você usaria:- Java:
LocalDateTime
(Java 8 ou Jodatime). - JDBC:
java.sql.Timestamp
- PostgreSQL:
TIMESTAMP WITHOUT TIMEZONE
(TIMESTAMP
)
Leia
LocalDateTime
do banco de dados TIMESTAMP
:Timestamp ts = rs.getTimestamp(col, tzUTC); //
LocalDateTime localDt = null;
if( ts != null )
localDt = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts.getTime()), ZoneOffset.UTC);
Escreva
LocalDateTime
para o banco de dados TIMESTAMP
: Timestamp ts = null;
if( localDt != null)
ts = new Timestamp(localDt.toInstant(ZoneOffset.UTC).toEpochMilli()), tzUTC);
ps.setTimestamp(colNum,ts, tzUTC);
Novamente, essa estratégia é segura e você pode dormir tranquilamente:se você armazenou
2011-10-30 23:59:30
, você recuperará esses campos precisos (hora =23, minuto =59 ... etc) sempre, não importa o que aconteça - mesmo que amanhã o fuso horário do seu servidor Postgresql (ou cliente) mude, ou sua JVM ou fuso horário do seu sistema operacional, ou se o seu país modificar suas regras de horário de verão, etc. Adicionado:Se você quiser (parece um requisito natural) armazenar a especificação completa de data e hora (um
ZonedDatetime
:o carimbo de data e hora junto com o fuso horário, que implicitamente também inclui as informações completas de data e hora civil - mais o fuso horário) ... . Você deve criar seu próprio armazenamento, talvez em um par de campos:podem ser os dois tipos acima (altamente redundantes, embora eficientes para recuperação e cálculo), ou um deles mais o deslocamento de tempo (você perde as informações de fuso horário, alguns cálculos se tornam difícil, e alguns impossíveis), ou um deles mais o fuso horário (como string; alguns cálculos podem ser extremamente caros).