Alla strategier för att lagra datum- och tiddata i PostgreSQL bör, IMO, förlita sig på dessa två punkter:
- Din lösning bör aldrig beror på serverns eller klientens tidszonsinställning.
- För närvarande har PostgreSQL (som de flesta databaser) ingen datatyp för att lagra en full datum och tid med tidszon. Så du måste välja mellan en
Instant
eller enLocalDateTime
datatyp.
Mitt recept följer.
Om du vill spela in det fysiska ögonblicket när en viss händelse inträffade (en sann "tidsstämpel " , vanligtvis någon skapande/ändring/borttagningshändelse), använd sedan:
- Java:
Instant
(Java 8 eller Jodatime). - JDBC:
java.sql.Timestamp
- PostgreSQL:
TIMESTAMP WITH TIMEZONE
(TIMESTAMPTZ
)
(Låt inte PostgreSQL märkliga datatyper WITH TIMEZONE
/WITHOUT TIMEZONE
förvirra dig:ingen av dem lagrar faktiskt en tidszon)
Någon typkod:följande antar att ps
är en PreparedStatement
, rs
en ResultSet
och tzUTC
är en statisk Calendar
objekt som motsvarar UTC
tidszon.
public static final Calendar tzUTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Skriv Instant
till databasen TIMESTAMPTZ
:
Instant instant = ...;
Timestamp ts = instant != null ? Timestamp.from(instant) : null;
ps.setTimestamp(col, ts, tzUTC); // column is TIMESTAMPTZ!
Läs Instant
från databasen TIMESTAMPTZ
:
Timestamp ts = rs.getTimestamp(col,tzUTC); // column is TIMESTAMPTZ
Instant inst = ts !=null ? ts.toInstant() : null;
Detta fungerar säkert om din PG-typ är TIMESTAMPTZ
(I så fall, calendarUTC
har ingen effekt i den koden; men det är alltid tillrådligt att inte vara beroende av standardtidszoner). "Säkert" betyder att resultatet inte beror på serverns eller databasens tidszon , eller tidszonsinformation:operationen är helt reversibel, och vad som än händer med tidszonsinställningarna kommer du alltid att få samma "instant of time" som du ursprungligen hade på Java-sidan.
Om du istället för en tidsstämpel (ett ögonblick på den fysiska tidslinjen) har att göra med en "civil" lokal datum-tid (det vill säga uppsättningen fält {year-month-day hour:min:sec(:msecs)}
), skulle du använda:
- Java:
LocalDateTime
(Java 8 eller Jodatime). - JDBC:
java.sql.Timestamp
- PostgreSQL:
TIMESTAMP WITHOUT TIMEZONE
(TIMESTAMP
)
Läs LocalDateTime
från databasen TIMESTAMP
:
Timestamp ts = rs.getTimestamp(col, tzUTC); //
LocalDateTime localDt = null;
if( ts != null )
localDt = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts.getTime()), ZoneOffset.UTC);
Skriv LocalDateTime
till databasen TIMESTAMP
:
Timestamp ts = null;
if( localDt != null)
ts = new Timestamp(localDt.toInstant(ZoneOffset.UTC).toEpochMilli()), tzUTC);
ps.setTimestamp(colNum,ts, tzUTC);
Återigen, den här strategin är säker och du kan sova lugnt:om du lagrade 2011-10-30 23:59:30
, du kommer att hämta de exakta fälten (timme=23, minut=59... etc) alltid, oavsett vad - även om tidszonen för din Postgresql-server (eller klient) ändras i morgon, eller din JVM eller ditt OS-tidszon, eller om ditt land ändrar sina sommartidsregler etc.
Tillagd:Om du vill (det verkar vara ett naturligt krav) lagra den fullständiga datetime-specifikationen (en ZonedDatetime
:tidsstämpeln tillsammans med tidszonen, som implicit också inkluderar den fullständiga civila datetime info - plus tidszonen)... så har jag dåliga nyheter till dig:PostgreSQL har ingen datatyp för detta (inte heller andra databaser, såvitt jag vet) . Du måste skapa din egen lagring, kanske i ett par fält:kan vara de två ovanstående typerna (mycket redundant, men effektiv för hämtning och beräkning), eller en av dem plus tidsförskjutningen (du tappar tidszonsinformationen, vissa beräkningar blir svårt, och vissa omöjligt), eller en av dem plus tidszonen (som sträng; vissa beräkningar kan vara extremt kostsamma).