Enhver strategi til lagring af dato-og-tidsdata i PostgreSQL bør, IMO, stole på disse to punkter:
- Din løsning bør aldrig afhænger af serverens eller klientens tidszoneindstilling.
- I øjeblikket har PostgreSQL (som de fleste databaser) ikke en datatype til at gemme en fuld dato-og-klokkeslæt med tidszone. Så du skal vælge mellem en
Instant
eller enLocalDateTime
datatype.
Min opskrift følger.
Hvis du vil optage det fysiske øjeblik når en bestemt begivenhed fandt sted (et ægte "tidsstempel " , typisk en eller anden oprettelse/ændring/sletningshændelse), brug derefter:
- Java:
Instant
(Java 8 eller Jodatime). - JDBC:
java.sql.Timestamp
- PostgreSQL:
TIMESTAMP WITH TIMEZONE
(TIMESTAMPTZ
)
(Lad ikke PostgreSQL ejendommelige datatyper WITH TIMEZONE
/WITHOUT TIMEZONE
forvirre dig:ingen af dem gemmer faktisk en tidszone)
Noget kedelkode:det følgende antager, at ps
er en PreparedStatement
, rs
et ResultSet
og tzUTC
er en statisk Calendar
objekt svarende til UTC
tidszone.
public static final Calendar tzUTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Skriv Instant
til databasen TIMESTAMPTZ
:
Instant instant = ...;
Timestamp ts = instant != null ? Timestamp.from(instant) : null;
ps.setTimestamp(col, ts, tzUTC); // column is TIMESTAMPTZ!
Læs Instant
fra databasen TIMESTAMPTZ
:
Timestamp ts = rs.getTimestamp(col,tzUTC); // column is TIMESTAMPTZ
Instant inst = ts !=null ? ts.toInstant() : null;
Dette fungerer sikkert, hvis din PG-type er TIMESTAMPTZ
(I så fald er calendarUTC
har ingen effekt i den kode; men det er altid tilrådeligt ikke at være afhængig af standardtidszoner). "Sikkert" betyder, at resultatet ikke vil afhænge af server- eller databasetidszone , eller tidszoneoplysninger:handlingen er fuldt reversibel, og uanset hvad der sker med tidszoneindstillinger, vil du altid få det samme "øjeblikkelige tid", som du oprindeligt havde på Java-siden.
Hvis du i stedet for et tidsstempel (et øjeblik på den fysiske tidslinje) har at gøre med en "civil" lokal dato-tid (det vil sige sættet af felter {year-month-day hour:min:sec(:msecs)}
), skal du bruge:
- Java:
LocalDateTime
(Java 8 eller Jodatime). - JDBC:
java.sql.Timestamp
- PostgreSQL:
TIMESTAMP WITHOUT TIMEZONE
(TIMESTAMP
)
Læs LocalDateTime
fra 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
til databasen TIMESTAMP
:
Timestamp ts = null;
if( localDt != null)
ts = new Timestamp(localDt.toInstant(ZoneOffset.UTC).toEpochMilli()), tzUTC);
ps.setTimestamp(colNum,ts, tzUTC);
Igen, denne strategi er sikker, og du kan sove roligt:hvis du har gemt 2011-10-30 23:59:30
, vil du hente disse præcise felter (time=23, minut=59... osv.) altid, uanset hvad - selvom tidszonen for din Postgresql-server (eller klient) i morgen ændres, eller din JVM eller dit OS-tidszone, eller hvis dit land ændrer sine sommertidsregler osv.
Tilføjet:Hvis du ønsker (det virker som et naturligt krav) at gemme den fulde datetime-specifikation (en ZonedDatetime
:tidsstemplet sammen med tidszonen, som implicit også inkluderer den fulde civile datetime info - plus tidszonen)... så har jeg dårlige nyheder til dig:PostgreSQL har ikke en datatype for dette (hverken andre databaser, så vidt jeg ved) . Du skal udtænke dit eget lager, måske i et par felter:kunne være de to ovennævnte typer (meget overflødige, men effektive til hentning og beregning), eller en af dem plus tidsforskydningen (du mister tidszoneinformationen, nogle beregninger bliver vanskelige, og nogle umulige), eller en af dem plus tidszonen (som streng; nogle beregninger kan være ekstremt dyre).