Зависи от вида на срещата.
Има два вида срещи:
- Момент, конкретна точка от времевата линия, игнориране на всички промени в правилата за часовата зона.
Пример:изстрелване на ракета. - Дата и час от деня, които трябва да се коригират за промени в правилата за часовата зона.
Пример:посещение при лекар/стоматолог.
Момент
Ако резервираме изстрелване на ракета, например, не ни интересува датата и часът. Ние се интересуваме само от момента, в който (а) небето се изравни и (б) очакваме благоприятно време.
Ако през това време политиците променят правилата на часовата зона, използвана на нашата площадка за изстрелване или в нашите офиси, това няма ефект върху нашата среща за изстрелване. Ако политиците, управляващи нашия сайт за стартиране, приемат лятно часово време (DST) , моментът на стартирането ни остава същият. Ако политиците, управляващи нашите офиси, решат да сменете часовника с половин час по-рано заради дипломатическите отношения със съседна държава моментът на стартиране остава същият.
За такова назначение, да, вашият подход би бил правилен. Ще запишете срещата в UTC
използвайки колона от тип TIMESTAMP WITH TIME ZONE
. При извличане настройте всяка часова зона, която потребителят предпочита.
Бази данни като Postgres
използвайте всяка информация за часовата зона, придружаваща въвеждането, за да коригирате в UTC, и след това изхвърля тази информация за часовата зона. Когато извлечете стойността от Postgres, тя винаги ще представлява дата с час от деня, както се вижда в UTC. Внимавайте, някои инструменти или междинен софтуер може да имат антифункцията да прилагат някаква часова зона по подразбиране между извличането от базата данни и доставката до вас като програмист. Но бъдете ясни:Postgres винаги запазва и извлича стойности от тип TIMESTAMP WITH TIME ZONE
в UTC, винаги UTC и offset-from-UTC
от нула часа-минути-секунди.
Ето примерен Java код.
LocalDate launchDateAsSeenInRome = LocalDate.of( 2021 , 1 , 23 ) ;
LocalTime launchTimeAsSeenInRome = LocalTime.of( 21 , 0 ) ;
ZoneId zoneEuropeRome = ZoneId.of( "Europe/Rome" ) ;
// Assemble those three parts to determine a moment.
ZonedDateTime launchMomentAsSeenInRome = ZonedDateTime.of( launchDateAsSeenInRome , launchTimeAsSeenInRome , zoneEuropeRome ) ;
За да видите същия момент в UTC, конвертирайте в Instant
. Instant
обектът винаги представлява момент, както се вижда в UTC.
Instant launchInstant = launchMomentAsSeenInRome.toInstant() ; // Adjust from Rome time zone to UTC.
Z
в края на горния пример за низ е стандартна нотация за UTC и се произнася „зулу“.
За съжаление екипът на JDBC 4.2 пренебрегна изискването за поддръжка за Instant
или ZonedDateTime
видове. Така че вашият JDBC драйвер
може или не може да чете/записва такива обекти във вашата база данни. Ако не, просто конвертирайте в OffsetDateTime
. И трите типа представляват момент, конкретна точка от времевата линия. Но OffsetDateTime
има поддръжка, изисквана от JDBC
4.2
по причини, които ми убягват.
OffsetDateTime odtLaunchAsSeenInRome = launchMomentAsSeenInRome.toOffsetDateTime() ;
Писане в база данни.
myPreparedStatement.setObject( … , odtLaunchAsSeenInRome ) ;
Извличане от база данни.
OffsetDateTime launchMoment = myResultSet.getObject( … , OffsetDateTime.class ) ;
Настройте часовата зона на Ню Йорк, желана от вашия потребител.
ZoneId zoneAmericaNewYork = ZoneId.of( "America/New_York" ) ;
ZonedDateTime launchAsSeenInNewYork = launchMoment.atZoneSameInstant( zoneAmericaNewYork ) ;
Можете да видите всички горепосочени кодове, изпълнявани на живо на IdeOne.com .
Между другото, проследяването на минали събития също се третира като момент. Кога пациентът всъщност е пристигнал за среща, кога клиентът е платил фактурата, кога нов нает е подписал документите си, кога сървърът се е сринал… всичко това се проследява като момент в UTC. Както беше обсъдено по-горе, обикновено това би било Instant
, въпреки че ZonedDateTime
&OffsetDateTime
също представляват момент. За базата данни използвайте TIMESTAMP WITH TIME ZONE
(не WITHOUT
).
Час от деня
Очаквам, че повечето бизнес-ориентирани приложения са фокусирани върху срещи от друг тип, където се стремим към дата с час от деня, а не конкретен момент.
Ако потребителят си уговори среща със своя доставчик на здравни услуги, за да прегледа резултатите от тест, той прави това за определен час от деня на тази дата. Ако междувременно политиците променят правилата на своята часова зона, премествайки часовника напред или назад с час, половин час или друг период от време, датата и часът от деня на тази медицинска среща остават същите. В действителност точката от времевата линия на първоначалното назначение ще бъде променена, след като политиците сменят часовата зона, премествайки се към по-ранна/по-късна точка от времевата линия.
За такива назначения не съхранявайте датата и часа от деня, както се вижда в UTC. Ние не използвайте тип колона на базата данни TIMESTAMP WITH TIME ZONE
.
За такива срещи ние съхраняваме датата с час от деня без никакво отношение към часовата зона. Използваме колона от база данни от тип TIMESTAMP WITHOUT TIME ZONE
(забележете WITHOUT
вместо WITH
). Съвпадащият тип в Java е LocalDateTime
.
LocalDate medicalApptDate = LocalDate.of( 2021 , 1 , 23 ) ;
LocalTime medicalApptTime = LocalTime.of( 21 , 0 ) ;
LocalDateTime medicalApptDateTime = LocalDateTime.of( medicalApptDate , medicalApptTime ) ;
Запишете това в базата данни.
myPreparedStatement.setObject( … , medicalApptDateTime ) ;
Изяснете това:LocalDateTime
обект не представлява момент, не е конкретна точка от времевата линия. LocalDateTime
обектът представлява диапазон от възможни моменти около 26-27 часа от времевата линия (диапазонът от часови зони по света). За да придадете истинско значение на LocalDateTime
, трябва да свържем предвидена часова зона.
За предвидената часова зона използвайте втора колона, за да съхраните идентификатора на зоната. Например низовете Europe/Rome
или America/New_York
. Вижте списък с имена на зони
.
ZoneId zoneEuropeRome = ZoneId.of( "Europe/Rome" ) ;
Запишете това в базата данни като текст.
myPreparedStatement.setString( … , zoneEuropeRome ) ;
Извличане. Извлечете името на зоната като текст и създайте ZoneId
обект.
LocalDateTime medicalApptDateTime = myResultSet.getObject( … , LocalDateTime.class ) ;
ZoneId medicalApptZone = ZoneId.of( myResultSet.getString( … ) ) ;
Съберете тези две части заедно, за да определите момент, представен като ZonedDateTime
обект. Направете това динамично, когато трябва да планирате календар. Но не съхрани момента. Ако политиците предефинират часовата(ите) зона(и) в бъдеще, тогава трябва да се изчисли различен момент.
ZonedDateTime medicalApptAsSeenInCareProviderZone = ZonedDateTime.of( medicalApptDateTime , medicalApptZone ) ;
Потребителят пътува до Ню Йорк, САЩ. Те трябва да знаят кога да се обадят на доставчика на здравни услуги в Милано Италия според часовниците на стената във временното им местоположение в Ню Йорк. Така че коригирайте от една часова зона в друга. Същият момент, различно време на стенен часовник.
ZoneId zoneAmericaNewYork = ZoneId.of( "America/New_York" ) ;
ZonedDateTime medicalApptAsSeenInNewYork = medicalApptAsSeenInCareProviderZone.withZoneSameInstant( zoneAmericaNewYork ) ;
tzdata
Имайте предвид, че ако правилата на желаните от вас часови зони може да се променят, трябва да актуализирате копието на дефинициите на часовите зони на вашите компютри.
Java съдържа собствено копие на tzdata , както и двигателя на базата данни на Postgres. И вашата хост операционна система също. Този конкретен код, показан тук, изисква само Java да бъде актуална. Ако използвате Postgres, за да направите корекции на часовата зона, неговите tzdata също трябва да е актуален. И за регистриране и подобни, вашата хост ОС трябва да се поддържа актуална. За правилното гледане на часовника от потребителя, операционната система на клиентската му машина също трябва да е актуална.
Внимавайте:Политиците по света са показали склонност към промяна на часовите си зони с изненадваща честота и често с малко предупреждение.
Относно java.time
java.time
framework е вграден в Java 8 и по-нови версии. Тези класове изместват проблемното старо наследство
класове дата-час като java.util.Date
, Calendar
, &SimpleDateFormat
.
За да научите повече, вижте урок за Oracle . И потърсете в Stack Overflow много примери и обяснения. Спецификацията е JSR 310 .
Joda-Time проект, сега в режим на поддръжка , съветва мигриране към java.time класове.
Можете да обменяте java.time обекти директно с вашата база данни. Използвайте JDBC драйвер
съвместим с JDBC 4.2
или по-късно. Няма нужда от низове, няма нужда от java.sql.*
класове. Hibernate 5 и JPA 2.2 поддържат java.time .
Къде да получите класовете java.time?
- Java SE 8
, Java SE 9
, Java SE 10
, Java SE 11
, и по-късно - Част от стандартния Java API с пакетна реализация.
- Java 9 донесе някои незначителни функции и поправки.
- Java SE 6
и Java SE 7
- По-голямата част от java.time функционалността е обратно пренесена към Java 6 и 7 в ThreeTen-Backport .
- Android
- По-късни версии на Android (26+) пакетни реализации на java.time класове.
- За по-ранен Android (<26), процес, известен като API обеззахаряване
носи подмножество от java.time
функционалност, която не е първоначално вградена в Android.
- Ако обеззахаряването не предлага това, от което се нуждаете, ThreeTenABP проектът адаптира ThreeTen-Backport (споменати по-горе) към Android. Вижте Как да използвате ThreeTenABP… .