Прочетете Въпроса Най-добри практики за лятно часово време и часови зони. По същество твоят е дубликат.
Сървъри в UTC
Да, обикновено сървърите трябва да имат операционната система на UTC като часова зона или ако не е предоставена, използвайте GMT
или часовата зона на Рейкявик Исландия. Вашата реализация на Java вероятно приема тази настройка като своя собствена текуща часова зона по подразбиране.
Посочете часова зона
Но не зависи от часовата зона, която е зададена на UTC. Системният администратор може да го промени. И всеки Java код във всяка нишка на всяко приложение във вашата JVM може да промени текущата часова зона по подразбиране на JVM по време на изпълнение като извикате TimeZone.setDefault
. Затова вместо това си направете навик винаги да указвате желаната/очакваната часова зона, като предавате незадължителния аргумент във вашия Java код.
Считам за недостатък в дизайна, че всяка рамка за дата-време би направила часовата зона незадължителна. Да бъдеш незадължителен създава безкрайни количества объркване, защото програмистите, както всички останали, несъзнателно мислят от гледна точка на собствената си лична часова зона, освен ако не бъдат подканени. Така че твърде често при работа с дата и час не се обръща внимание на въпроса. Добавете проблема, че JVM по подразбиране варира. Между другото, същото за Locale
, едни и същи проблеми, винаги трябва да бъдат посочени изрично.
UTC
Вашата бизнес логика, съхранение на данни и обмен на данни почти винаги трябва да се извършват в UTC. Почти всяка база данни има функция за настройка на всяко въвеждане в UTC и съхраняване в UTC.
Когато представяте дата и час на потребител, настройте се в очакваната часова зона. Когато сериализирате стойност за дата и час, използвайте низовите формати на ISO 8601. Вижте отговора на VickyArora специално за Oracle (аз съм човек на Postgres). Не забравяйте да прочетете внимателно документа и да практикувате, като експериментирате, за да разберете напълно поведението на вашата база данни. SQL спецификацията не е посочена много в това отношение и поведението варира значително.
java.sql
Не забравяйте, че когато използвате Java и JDBC, ще използвате java.sql.Timestamp
и свързани типове данни. Те винаги са в UTC, автоматично. В бъдеще очаквайте да видите JDBC драйвери, актуализирани, за да използват директно новите типове данни, дефинирани в рамката java.time, вградена в Java 8 и по-нови.
java.time
Старите класове са остарели от java.time. Научете се да използвате java.time като избягвате стария java.util.Date/.Calendar и направете живота си с програмиране много по-приятен.
Докато вашият JDBC драйвер не бъде актуализиран, можете да използвате удобните методи за преобразуване, вградени в java.time. Вижте примерите по-нататък, където Instant
е момент в UTC и ZonedDateTime
е Мигновено, коригирано в часова зона.
Instant instant = myJavaSqlTimestamp.toInstant();
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
За да отидете в другата посока.
java.sql.Timestamp myJavaSqlTimestamp = java.sql.Timestamp.from( zdt.toInstant() );
Ако имате нужда от оригинална часова зона, съхранете я
Ако вашите бизнес изисквания смятат часовата зона на оригиналните входни данни за важна, която трябва да бъде запомнена, тогава запазете това изрично като отделна колона в таблицата на вашата база данни. Можете да използвате отместване от UTC, но това не предоставя пълна информация. Часовата зона е отместване плюс набор от правила за минало, настояще и бъдещо управление на аномалии като лятно часово време. Така че правилното име на часовата зона е най-подходящо, като например America/Montreal
.
Само за дата е двусмислено
Казахте, че събирате много стойности само за дата, без време на деня и без часова зона. Класът за това в java.time е LocalDate
. Както при LocalTime
и LocalDateTime
, частта „Местно...“ не означава конкретно населено място, следователно няма часова зона и следователно няма точка от времевата линия – няма реално значение.
Имайте предвид, че стойността само за дата е двусмислена по дефиниция. Във всеки един момент датата варира в целия свят. Например, малко след полунощ в Париж Франция е нов ден, но в Монреал Квебек датата все още е „вчера“.
Обикновено в бизнеса някаква часова зона е имплицитна, дори несъзнателно интуитивна. Несъзнателната интуиция относно точките от данни обикновено не работи добре в дългосрочен план, особено в софтуера. По-добре е да посочите изрично каква часова зона е предназначена. Можете да съхраните предвидената зона до датата, като например друга колона в таблицата на базата данни, или можете да направите коментар във вашия програмен код. Вярвам, че би било много по-добре и по-безопасно да се съхранява стойност за дата и час. И така, как да трансформираме само дата в дата и час?
Често нов ден е моментът след полунощ, първият момент от деня. Може да си помислите, че това означава час от деня 00:00:00.0
но не винаги. Лятно часово време (DST) и евентуално други аномалии може да изместят първия момент към различно време на стенен часовник. Нека java.time определи правилното време от деня за първия момент, преминаващ през LocalDate
клас и неговия atStartOfDay
метод.
ZoneId zoneId = ZoneId.of( "America/Montreal" );
LocalDate today = LocalDate.now( zoneId );
ZonedDateTime todayStart = today.atStartOfDay( zoneId );
В някои бизнес контексти може да се определи (или да се приеме) нов ден като работно време. Например, да речем, че издател в Ню Йорк означава 9 сутринта по местно време, когато казват „черновата на книгата е до 2 януари“. Нека вземем това време от деня за тази дата в тази часова зона.
ZoneId zoneId = ZoneId.of( "America/New_York" );
ZonedDateTime zdt = ZonedDateTime.of( 2016 , 1 , 2 , 9 , 0 , 0 , 0 , zoneId );
Какво означава това за автора, работещ в Нова Зеландия? Настройте конкретната й часова зона за представяне пред нея, като извикате withZoneSameInstant
.
ZoneId zoneId_Pacific_Auckland = ZoneId.of( "Pacific/Auckland" );
ZonedDateTime zdt_Pacific_Auckland = zdt.withZoneSameInstant( zoneId_Pacific_Auckland );
База данни
За съхранение на база данни ние трансформираме в Instant
(момент на времевата линия в UTC) и се предава като java.sql.Timestamp
както се вижда по-горе.
java.sql.Timestamp ts = java.sql.Timestamp.from( zdt.toInstant() );
Когато бъде извлечен от базата данни, трансформирайте обратно към дата и час в Ню Йорк. Конвертиране от java.sql.Timestamp
към Instant
, след което приложете часова зона ZoneId
за да получите ZonedDateTime
.
Instant instant = ts.toInstant();
ZoneId zoneId = ZoneId.of( "America/New_York" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
Ако вашият драйвер за база данни отговаря на JDBC 4.2 или по-нова версия, може да сте в състояние да предавате/извличате директно типовете java.time, вместо да конвертирате към/от типове java.sql. Опитайте PreparedStatement::setObject
и ResultSet::getObject
методи.