Jakákoli strategie pro ukládání dat data a času v PostgreSQL by se měla, IMO, spoléhat na tyto dva body:
- Vaše řešení by nemělo nikdy závisí na nastavení časového pásma serveru nebo klienta.
- V současné době PostgreSQL (jako většina databází) nemá datový typ pro uložení úplného datum a čas s časovým pásmem. Musíte se tedy rozhodnout mezi
Instant
neboLocalDateTime
datový typ.
Můj recept následuje.
Pokud chcete zaznamenat fyzické okamžité kdy došlo k určité události (skutečné „časové razítko " , obvykle nějaká událost vytvoření/úpravy/smazání), pak použijte:
- Java:
Instant
(Java 8 nebo Jodatime). - JDBC:
java.sql.Timestamp
- PostgreSQL:
TIMESTAMP WITH TIMEZONE
(TIMESTAMPTZ
)
(Nenechte PostgreSQL zvláštní datové typy WITH TIMEZONE
/WITHOUT TIMEZONE
zmást vás:žádný z nich ve skutečnosti neukládá časové pásmo)
Nějaký standardní kód:následující předpokládá, že ps
je PreparedStatement
, rs
ResultSet
a tzUTC
je statický Calendar
objekt odpovídající UTC
časové pásmo.
public static final Calendar tzUTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Napište Instant
do databáze TIMESTAMPTZ
:
Instant instant = ...;
Timestamp ts = instant != null ? Timestamp.from(instant) : null;
ps.setTimestamp(col, ts, tzUTC); // column is TIMESTAMPTZ!
Přečtěte si Instant
z databáze TIMESTAMPTZ
:
Timestamp ts = rs.getTimestamp(col,tzUTC); // column is TIMESTAMPTZ
Instant inst = ts !=null ? ts.toInstant() : null;
Toto funguje bezpečně, pokud je váš typ PG TIMESTAMPTZ
(V takovém případě calendarUTC
nemá na tento kód žádný vliv; ale vždy je vhodné nezáviset na výchozích časových pásmech). "Bezpečně" znamená, že výsledek nezávisí na časovém pásmu serveru nebo databáze , neboli informace o časových pásmech:operace je plně vratná a ať se stane s nastavením časových pásem cokoli, vždy získáte stejný „okamžitý okamžik“, jaký jste původně měli na straně Java.
Pokud místo časového razítka (okamžik na fyzické časové ose) máte co do činění s "civilním" místním datem a časem (tj. sada polí {year-month-day hour:min:sec(:msecs)}
), použijete:
- Java:
LocalDateTime
(Java 8 nebo Jodatime). - JDBC:
java.sql.Timestamp
- PostgreSQL:
TIMESTAMP WITHOUT TIMEZONE
(TIMESTAMP
)
Přečtěte si LocalDateTime
z databáze TIMESTAMP
:
Timestamp ts = rs.getTimestamp(col, tzUTC); //
LocalDateTime localDt = null;
if( ts != null )
localDt = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts.getTime()), ZoneOffset.UTC);
Napište LocalDateTime
do databáze TIMESTAMP
:
Timestamp ts = null;
if( localDt != null)
ts = new Timestamp(localDt.toInstant(ZoneOffset.UTC).toEpochMilli()), tzUTC);
ps.setTimestamp(colNum,ts, tzUTC);
Opět platí, že tato strategie je bezpečná a můžete klidně spát:pokud jste uložili 2011-10-30 23:59:30
, získáte tato přesná pole (hodina=23, minuta=59... atd.) vždy, bez ohledu na to, co se děje – i když se zítra změní časové pásmo vašeho Postgresql serveru (nebo klienta) nebo vaše JVM nebo časové pásmo vašeho OS, nebo pokud vaše země upraví svá pravidla DST atd.
Přidáno:Pokud chcete (zdá se, že je to přirozený požadavek) uložit úplnou specifikaci data a času (ZonedDatetime
:časové razítko spolu s časovou zónou, která implicitně zahrnuje i úplné civilní datum a čas - plus časové pásmo)... pak pro vás mám špatnou zprávu:PostgreSQL pro to nemá datový typ (ani jiné databáze, pokud vím) . Musíte si vymyslet vlastní úložiště, možná ve dvojici polí:mohly by to být dva výše uvedené typy (vysoce redundantní, i když efektivní pro vyhledávání a výpočty), nebo jeden z nich plus časový posun (ztratíte informace o časovém pásmu, některé výpočty se stanou obtížné a některé nemožné), nebo jeden z nich plus časové pásmo (jako řetězec; některé výpočty mohou být extrémně nákladné).