Próbuję ustawić w mojej bazie danych datę i godzinę niezależną od serwera i uważam, że najlepszą praktyką jest ustawienie daty i godziny UTC. Mój serwer db to Cassandra, a sterownik db dla języka Java obsługuje tylko typ Data.
Zakładając, że w moim kodzie używam nowej Javy 8 ZonedDateTime, aby uzyskać czas UTC now ( ZonedDateTime.now(ZoneOffset.UTC)
), jak mogę przekonwertować tę instancję ZonedDateTime na „starszą” klasę Date?
java
java-8
java.util.date
datetime-conversion
zoneddatetime
Milen Kovachev
źródło
źródło
java.util.Date
ijava.sql.Date
.Odpowiedzi:
Możesz przekonwertować ZonedDateTime na moment, którego możesz użyć bezpośrednio z Date.
źródło
Date
to liczba milisekund od epoki - więc jest związana z czasem UTC. Jeśli go wydrukujesz , zostanie użyta domyślna strefa czasowa, ale klasa Date nie ma informacji o strefie czasowej użytkownika ... Zobacz na przykład sekcję „mapowanie” w docs.oracle.com/javase/tutorial/datetime/iso/legacy .html A LocalDateTime jest jawnie bez odniesienia do strefy czasowej - co może być postrzegane jako mylące ...ZonedDateTime
.java.time.Instant
Klasa jest bezpośrednio zamiennikjava.util.Date
, zarówno reprezentujący moment w UTC choćInstant
używa rozdzielczości drobniejsza od nanosekund zamiast milisekund.Date.from( Instant.now() )
powinno być twoim rozwiązaniem. Lub jeśli o to chodzi, po prostunew Date()
ma ten sam efekt, rejestrując bieżący moment w UTC.tl; dr
java.util.Date.from( // Transfer the moment in UTC, truncating any microseconds or nanoseconds to milliseconds. Instant.now() ; // Capture current moment in UTC, with resolution as fine as nanoseconds. )
Chociaż powyższy kod nie miał sensu. Obie
java.util.Date
iInstant
reprezentują chwilę w UTC, zawsze w UTC. Powyższy kod ma taki sam skutek jak:new java.util.Date() // Capture current moment in UTC.
Nie ma tu korzyści z używania
ZonedDateTime
. Jeśli już maszZonedDateTime
, dostosuj się do UTC, wyodrębniając plikInstant
.java.util.Date.from( // Truncates any micros/nanos. myZonedDateTime.toInstant() // Adjust to UTC. Same moment, same point on the timeline, different wall-clock time. )
Prawidłowa inna odpowiedź
The Answer by ssoltanid poprawnie odnosi się do konkretnego pytania, jak przekonwertować obiekt java.time nowej szkoły (
ZonedDateTime
) na obiekt starej szkołyjava.util.Date
. WyodrębnijInstant
z ZonedDateTime i przejdź dojava.util.Date.from()
.Utrata danych
Zauważ, że będziesz cierpieć z powodu utraty danych , ponieważ
Instant
śledzi nanosekundy od epoki, podczas gdyjava.util.Date
śledzenie milisekund od epoki.Twoje pytanie i komentarze rodzą inne problemy.
Zachowaj serwery w czasie UTC
Twoje serwery powinny mieć ustawiony system operacyjny hosta na UTC, co jest ogólnie najlepszą praktyką. JVM wybiera to ustawienie systemu operacyjnego hosta jako domyślną strefę czasową w implementacjach Java, o których wiem.
Określ strefę czasową
Jednak nigdy nie należy polegać na bieżącej domyślnej strefie czasowej maszyny JVM. Zamiast wybierać ustawienia hosta, flaga przekazywana podczas uruchamiania maszyny JVM może ustawić inną strefę czasową. Co gorsza: każdy kod w dowolnym wątku dowolnej aplikacji w dowolnym momencie może wywołać funkcję
java.util.TimeZone::setDefault
zmiany tego ustawienia domyślnego w czasie wykonywania!Cassandra
Timestamp
TypeKażda przyzwoita baza danych i sterownik powinny automatycznie obsługiwać dostosowywanie przekazanej daty do czasu UTC do przechowywania. Nie używam Cassandry, ale wydaje się, że ma pewne podstawowe wsparcie dla daty i godziny. Dokumentacja podaje, że jego
Timestamp
typ to liczba milisekund z tej samej epoki (pierwsza chwila 1970 roku czasu UTC).ISO 8601
Ponadto Cassandra akceptuje ciągi wejściowe w standardowych formatach ISO 8601 . Na szczęście java.time używa formatów ISO 8601 jako domyślnych do analizowania / generowania ciągów. The
Instant
class'toString
realizacja zrobi ładnie.Precyzja: milisekunda vs nanosekord
Ale najpierw musimy zmniejszyć precyzję nanosekund ZonedDateTime do milisekund. Jednym ze sposobów jest utworzenie nowego Instant przy użyciu milisekund. Na szczęście java.time ma kilka przydatnych metod konwersji do iz milisekund.
Przykładowy kod
Oto przykładowy kod w Java 8 Update 60.
ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) ); … Instant instant = zdt.toInstant(); Instant instantTruncatedToMilliseconds = Instant.ofEpochMilli( instant.toEpochMilli() ); String fodderForCassandra = instantTruncatedToMilliseconds.toString(); // Example: 2015-08-18T06:36:40.321Z
Lub zgodnie z tym dokumentem sterownika Cassandra Java , możesz przekazać
java.util.Date
instancję (nie mylić zjava.sqlDate
). Możesz więc utworzyć juDate z tegoinstantTruncatedToMilliseconds
w powyższym kodzie.Jeśli robisz to często, możesz stworzyć jedną linijkę.
Ale lepiej byłoby stworzyć małą metodę użytkową.
static public java.util.Date toJavaUtilDateFromZonedDateTime ( ZonedDateTime zdt ) { Instant instant = zdt.toInstant(); // Data-loss, going from nanosecond resolution to milliseconds. java.util.Date utilDate = java.util.Date.from( instant ) ; return utilDate; }
Zwróć uwagę na różnicę w całym tym kodzie niż w pytaniu. Kod pytania próbował dostosować strefę czasową wystąpienia ZonedDateTime do UTC. Ale to nie jest konieczne. Koncepcyjnie:
Po prostu wyodrębniamy część Instant, która jest już w UTC (zasadniczo w UTC, przeczytaj dokument klasy, aby uzyskać szczegółowe informacje).
Informacje o java.time
Struktura java.time jest wbudowana w Javę 8 i nowsze. Klasy te kłopotliwe zastąpić stary starszych klas Date-Time, takich jak
java.util.Date
,Calendar
, iSimpleDateFormat
.Projekt Joda-Time , obecnie w trybie konserwacji , zaleca migrację do klas java.time .
Aby dowiedzieć się więcej, zobacz samouczek Oracle . I przeszukaj Stack Overflow, aby znaleźć wiele przykładów i wyjaśnień. Specyfikacja to JSR 310 .
Możesz wymieniać obiekty java.time bezpośrednio ze swoją bazą danych. Użyj sterownika JDBC zgodnego z JDBC 4.2 lub nowszym. Nie ma potrzeby stosowania ciągów ani
java.sql.*
klas.Skąd wziąć klasy java.time?
Projekt ThreeTen-Extra rozszerza java.time o dodatkowe klasy. Ten projekt jest poligonem doświadczalnym dla ewentualnych przyszłych dodatków do java.time. Można znaleźć kilka przydatnych klas tutaj takie jak
Interval
,YearWeek
,YearQuarter
, i więcej .źródło
Jeśli korzystasz z zaplecza ThreeTen dla Androida i nie możesz korzystać z nowszej wersji
Date.from(Instant instant)
(która wymaga minimum API 26), możesz użyć:ZonedDateTime zdt = ZonedDateTime.now(); Date date = new Date(zdt.toInstant().toEpochMilli());
lub:
Przeczytaj również porady zawarte w odpowiedzi Basil Bourque
źródło
DateTimeUtils
klasę z metodami konwersji, więc użyłbym Date date = DateTimeUtils.toDate (zdt.toInstant ()); `. To nie jest tak niskie.Oto przykład konwersji bieżącego czasu systemowego na UTC. Obejmuje formatowanie ZonedDateTime jako String, a następnie obiekt String zostanie przetworzony na obiekt daty za pomocą java.text DateFormat.
ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC); final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss"); final DateFormat FORMATTER_YYYYMMDD_HH_MM_SS = new SimpleDateFormat("yyyyMMdd HH:mm:ss"); String dateStr = zdt.format(DATETIME_FORMATTER); Date utcDate = null; try { utcDate = FORMATTER_YYYYMMDD_HH_MM_SS.parse(dateStr); }catch (ParseException ex){ ex.printStackTrace(); }
źródło
Możesz to zrobić za pomocą klas java.time wbudowanych w Javę 8 lub nowszą.
ZonedDateTime temporal = ... long epochSecond = temporal.getLong(INSTANT_SECONDS); int nanoOfSecond = temporal.get(NANO_OF_SECOND); Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);
źródło
Instant
s śledzi sekundy i nanosekundy.Date
s śledzenia milisekund. Ta konwersja z jednego do drugiego jest poprawna.Używam tego.
public class TimeTools { public static Date getTaipeiNowDate() { Instant now = Instant.now(); ZoneId zoneId = ZoneId.of("Asia/Taipei"); ZonedDateTime dateAndTimeInTai = ZonedDateTime.ofInstant(now, zoneId); try { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInTai.toString().substring(0, 19).replace("T", " ")); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }
Ponieważ
Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant());
to nie działa !!! Jeśli uruchomisz aplikację na swoim komputerze, nie ma problemu. Ale jeśli uruchomisz w dowolnym regionie AWS, Docker lub GCP, spowoduje to problem. Ponieważ komputer nie jest Twoją strefą czasową w chmurze. Powinieneś ustawić poprawną strefę czasową w kodzie. Na przykład Azja / Tajpej. Następnie poprawi się w AWS lub Docker lub GCP.public class App { public static void main(String[] args) { Instant now = Instant.now(); ZoneId zoneId = ZoneId.of("Australia/Sydney"); ZonedDateTime dateAndTimeInLA = ZonedDateTime.ofInstant(now, zoneId); try { Date ans = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInLA.toString().substring(0, 19).replace("T", " ")); System.out.println("ans="+ans); } catch (ParseException e) { } Date wrongAns = Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); System.out.println("wrongAns="+wrongAns); } }
źródło
ans=Mon Jun 18 01:07:56 CEST 2018
, co jest niepoprawne, a potemwrongAns=Sun Jun 17 17:07:56 CEST 2018
, co jest poprawne.Przyjęta odpowiedź nie działa dla mnie. Zwrócona data jest zawsze lokalną datą, a nie datą oryginalnej strefy czasowej. Mieszkam w UTC + 2.
//This did not work for me Date.from(java.time.ZonedDateTime.now().toInstant());
Wymyśliłem dwa alternatywne sposoby uzyskania prawidłowej daty z ZonedDateTime.
Powiedzmy, że masz ZonedDateTime dla Hawajów
LocalDateTime ldt = LocalDateTime.now(); ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10
lub dla UTC, zgodnie z pierwotnym zapytaniem
Instant zulu = Instant.now(); // GMT, UTC+0 ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));
Alternatywa 1
Możemy użyć java.sql.Timestamp. Jest to proste, ale prawdopodobnie wpłynie również negatywnie na integralność programowania
Alternatywa 2
Tworzymy datę z milis (odpowiedź tutaj wcześniej). Pamiętaj, że lokalny ZoneOffset jest koniecznością.
ZoneOffset localOffset = ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now()); long zonedMillis = 1000L * zdt.toLocalDateTime().toEpochSecond(localOffset) + zdt.toLocalDateTime().getNano() / 1000000L; Date date2 = new Date(zonedMillis);
źródło
W przypadku aplikacji dockera, takiej jak beehuang skomentował, należy ustawić strefę czasową.
Alternatywnie możesz użyć withZoneSameLocal . Na przykład:
2014-07-01T00: 00 + 02: 00 [GMT + 02: 00] jest konwertowany przez
do wtorku 01 lipca 00:00:00 CEST 2014 i do
do pon. 30 czerwca 22:00:00 UTC 2014
źródło
Jeśli jesteś zainteresowany tylko teraz, po prostu użyj:
Date d = new Date();
źródło