Ответ 1
Любая стратегия хранения данных о времени и дате в PostgreSQL должна, IMO, опираться на эти два момента:
- Ваше решение никогда не должно зависеть от настроек часового пояса сервера или клиента.
- В настоящее время PostgreSQL (как и большинство баз данных) не имеет типа данных для хранения полной даты и времени с часовым поясом. Итак, вам нужно выбрать тип данных
Instant
илиLocalDateTime
.
Мой рецепт следует.
Если вы хотите записать физический момент, когда произошло определенное событие (истинная "timestamp", обычно какое-то событие создания/изменения/удаления), тогда используйте:
- Java:
Instant
(Java 8 или Jodatime). - JDBC:
java.sql.Timestamp
- PostgreSQL:
TIMESTAMP WITH TIMEZONE
(TIMESTAMPTZ
)
(Не позволяйте специфическим типам данных PostgreSQL WITH TIMEZONE
/WITHOUT TIMEZONE
сбить вас с толку: ни один из них на самом деле не хранит часовой пояс)
Немного шаблонного кода: ниже предполагается, что ps
является PreparedStatement
, rs
a ResultSet
и tzUTC
является статическим Calendar
объектом, соответствующим часовому поясу UTC
.
public static final Calendar tzUTC = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Запишите Instant
в базу данных TIMESTAMPTZ
:
Instant instant = ...;
Timestamp ts = instant != null ? new Timestamp(instant.toEpochMilli()) : null;
ps.setTimestamp(col, ts, tzUTC); // column is TIMESTAMPTZ!
Считать Instant
из базы данных TIMESTAMPTZ
:
Timestamp ts = rs.getTimestamp(col,tzUTC); // column is TIMESTAMPTZ
Instant inst = ts !=null ? Instant.ofEpochMilli(ts.getTime()) : null;
Это безопасно работает, если ваш тип PG TIMESTAMPTZ
(в этом случае calendarUTC
не действует в этом коде; но всегда рекомендуется не зависеть от часовых поясов по умолчанию).
"Безопасно" означает, что результат не будет зависеть от часового пояса сервера или базы данных или информации о часовых поясах: операция полностью обратима, и что бы ни случилось с настройками часовых поясов, вы всегда получите тот же самый "момент времени" "Вы изначально были на стороне Java.
Если вместо временной метки (мгновенного на физической временной шкале) вы имеете дело с "гражданским" локальным датой-временем (то есть набором полей {year-month-day hour:min:sec(:msecs)}
), вы будете использовать :
- Java:
LocalDateTime
(Java 8 или Jodatime). - JDBC:
java.sql.Timestamp
- PostgreSQL:
TIMESTAMP WITHOUT TIMEZONE
(TIMESTAMP
)
Считайте LocalDateTime
из базы данных TIMESTAMP
:
Timestamp ts = rs.getTimestamp(col, tzUTC); //
LocalDateTime localDt = null;
if( ts != null )
localDt = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts.getTime()), ZoneOffset.UTC);
Запишите LocalDateTime
в базу данных TIMESTAMP
:
Timestamp ts = null;
if( localDt != null)
ts = new Timestamp(localDt.toInstant(ZoneOffset.UTC).toEpochMilli()), tzUTC);
ps.setTimestamp(colNum,ts, tzUTC);
Опять же, эта стратегия безопасна, и вы можете спокойно спать: если вы сохранили 2011-10-30 23:59:30
, вы получите эти точные поля (час = 23, минута = 59... и т.д.) всегда, независимо от того, что - даже если завтра меняется часовой пояс вашего сервера (или клиента) Postgresql, или JVM, или часовой пояс вашей ОС, или если ваша страна изменяет свои правила перехода на летнее время и т.д.
Добавлено: Если вы хотите (это кажется естественным требованием) сохранить полную спецификацию даты и времени (a ZonedDatetime
: метка времени вместе с часовым поясом, который неявно также включает в себя полную гражданскую информацию о дате и времени - плюс часовой пояс))... тогда у меня для вас плохие новости: у PostgreSQL для этого нет типа данных (насколько мне известно, ни других баз данных). Вы должны разработать свое собственное хранилище, возможно, в паре полей: это могут быть два вышеуказанных типа (сильно избыточные, но эффективные для извлечения и вычисления) или один из них плюс смещение времени (вы теряете информацию о часовом поясе, некоторые вычисления становятся трудно, а некоторые невозможно), или один из них плюс часовой пояс (в виде строки; некоторые вычисления могут быть чрезвычайно дорогостоящими).