Jak przekonwertować ZonedDateTime na Date?

105

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?

Milen Kovachev
źródło
Istnieją dwie klasy „Date” wbudowane w Javę java.util.Datei java.sql.Date.
Basil Bourque

Odpowiedzi:

170

Możesz przekonwertować ZonedDateTime na moment, którego możesz użyć bezpośrednio z Date.

Date.from(java.time.ZonedDateTime.now().toInstant());
Slim Soltani Dridi
źródło
29
Nie, będzie to domyślna bieżąca data w systemie strefy.
Slim Soltani Dridi
6
@MilenKovachev Twoje pytanie nie ma sensu - data nie ma strefy czasowej - reprezentuje tylko chwilę w czasie.
assylias
1
@assylias Właściwie to twoje stwierdzenie nie ma sensu. Format, w jakim przechowywany jest czas, wskazuje na strefę czasową. Data jest oparta na UTC, niestety Java robi głupie rzeczy i nie traktuje jej jako takiej, a na dodatek uważa czas za lokalny TZ zamiast UTC. Sposób przechowywania danych w Data, LocalDateTime, ZonedDateTime implikuje strefę czasową. Sposób, w jaki są wykorzystywane, wygląda tak, jakby TZ nie istniały, co jest po prostu błędne. java.util.Date ma domyślnie TZ maszyny JVM. Fakt, że ludzie traktują to jako coś innego (łącznie z dokumentacją java!) To po prostu źli ludzie.
David
5
@David uh no - a Dateto 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 ...
assylias
2
Twoja odpowiedź niepotrzebnie wymaga ZonedDateTime. java.time.InstantKlasa jest bezpośrednio zamiennik java.util.Date, zarówno reprezentujący moment w UTC choć Instantużywa rozdzielczości drobniejsza od nanosekund zamiast milisekund. Date.from( Instant.now() )powinno być twoim rozwiązaniem. Lub jeśli o to chodzi, po prostu new Date()ma ten sam efekt, rejestrując bieżący moment w UTC.
Basil Bourque,
69

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.Datei Instantreprezentują 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ż masz ZonedDateTime, dostosuj się do UTC, wyodrębniając plik Instant.

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ły java.util.Date. Wyodrębnij Instantz ZonedDateTime i przejdź do java.util.Date.from().

Utrata danych

Zauważ, że będziesz cierpieć z powodu utraty danych , ponieważ Instantśledzi nanosekundy od epoki, podczas gdy java.util.Dateśledzenie milisekund od epoki.

diagram porównujący rozdzielczości milisekund, mikrosekund i nanosekund

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::setDefaultzmiany tego ustawienia domyślnego w czasie wykonywania!

Cassandra TimestampType

Każ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 Timestamptyp 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 Instantclass' toStringrealizacja 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.Dateinstancję (nie mylić z java.sqlDate). Możesz więc utworzyć juDate z tego instantTruncatedToMillisecondsw powyższym kodzie.

java.util.Date dateForCassandra = java.util.Date.from( instantTruncatedToMilliseconds );

Jeśli robisz to często, możesz stworzyć jedną linijkę.

java.util.Date dateForCassandra = java.util.Date.from( zdt.toInstant() );

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:

ZonedDateTime = Instant + ZoneId

Po prostu wyodrębniamy część Instant, która jest już w UTC (zasadniczo w UTC, przeczytaj dokument klasy, aby uzyskać szczegółowe informacje).


Tabela typów dat i godzin w Javie, zarówno nowoczesnych, jak i starszych


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, i SimpleDateFormat.

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?

Tabela opisująca bibliotekę java.time, która ma być używana z którą wersją Javy lub Androida

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 .

Basil Bourque
źródło
Wspaniałe wyjaśnienie, bardzo szczegółowe. Dziękuję bardzo!
Gianmarco F.
5

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:

Date date = DateTimeUtils.toDate(zdt.toInstant());

Przeczytaj również porady zawarte w odpowiedzi Basil Bourque

David Rawson
źródło
1
ThreeTen Backport (i ThreeTenABP) zawierają DateTimeUtilsklasę z metodami konwersji, więc użyłbym Date date = DateTimeUtils.toDate (zdt.toInstant ()); `. To nie jest tak niskie.
Ole VV
4

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();
    }
Asanka Siriwardena
źródło
1

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);
Peter Lawrey
źródło
Przepraszam, skąd pochodzą czasowe i wszystkie te stałe?
Milen Kovachev
@MilenKovachev A ZonedDateTime jest instancją Temporal
Peter Lawrey
Dzięki, ale powinieneś był wspomnieć, że zredagowałeś swoją odpowiedź, aby mój komentarz nie wydawał się głupi.
Milen Kovachev
2
@MilenKovachev możesz usunąć komentarz, ale to nie jest głupie pytanie.
Peter Lawrey
1
Nie mogę zrozumieć, dlaczego ta odpowiedź została odrzucona. Instants śledzi sekundy i nanosekundy. Dates śledzenia milisekund. Ta konwersja z jednego do drugiego jest poprawna.
scottb
1

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);
    }
}
beehuang
źródło
1
Wydaje się, że jest to jeden z najbardziej złożonych sposobów w każdej z 7 odpowiedzi. Czy ma jakieś zalety? Myślę, że nie. Naprawdę nie ma potrzeby przechodzenia przez formatowanie i analizowanie.
Ole VV
Dzięki, pytasz. Ponieważ Date evilAns = Date.from (java.time.ZonedDateTime.ofInstant (now, zoneId) .toInstant ()) ;; To nie działa!!!!!
beehuang
Uruchomiłem fragment kodu sekund tuż przed 17:08 w mojej strefie czasowej i otrzymałem ans=Mon Jun 18 01:07:56 CEST 2018, co jest niepoprawne, a potem wrongAns=Sun Jun 17 17:07:56 CEST 2018, co jest poprawne.
Ole VV
Nie mam problemu z uruchomieniem aplikacji na moim komputerze. Ale kiedy używasz dockera, ma problem. Ponieważ strefa czasowa w Docker nie jest strefą czasową w komputerze. Powinieneś ustawić poprawną strefę czasową w kodzie. Na przykład Azja / Tajpej. Następnie poprawi się w AWS, Docker, GCP lub dowolnym komputerze.
beehuang
@ OleV.V. Czy rozumiesz ?? lub masz jakieś pytania?
beehuang
1

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

Date date1 = Timestamp.valueOf(zdt.toLocalDateTime());

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);
Avec
źródło
0

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

Date.from(zonedDateTime.withZoneSameLocal(ZoneId.systemDefault()).toInstant())

do wtorku 01 lipca 00:00:00 CEST 2014 i do

Date.from(zonedDateTime.toInstant())

do pon. 30 czerwca 22:00:00 UTC 2014

dwe
źródło
-1

Jeśli jesteś zainteresowany tylko teraz, po prostu użyj:

Date d = new Date();
Jacob Eckel
źródło
Interesuje mnie teraz, ale UTC teraz.
Milen Kovachev
2
Tak, to będzie teraz UTC, data nie jest lepsza.
Jacob Eckel
3
Nie, nie będzie. Będzie to teraz w lokalnej strefie czasowej systemu. Data nie przechowuje żadnych informacji o strefie czasowej, ale wykorzystuje bieżący czas systemowy i ignoruje bieżącą strefę czasową systemu, więc nigdy nie konwertuje jej z powrotem na UTC
David
2
@David Date zachowuje czas w stosunku do epoki UTC, więc jeśli weźmiesz nową Date () z systemu w USA i inny obiekt z komputera w Japonii, będą one identyczne (spójrz na długość, jaką oba przechowują wewnętrznie).
Jacob Eckel
1
@JacobEckel - Tak, ale jeśli umieścisz 9 rano w dacie, podczas gdy twoje tz to US tz, a następnie zmienisz na JP tz i utworzysz nową datę z 9 rano, wewnętrzna wartość w Date będzie inna. Gdy tylko wrzucisz DST do miksu, nie możesz niezawodnie używać Date, chyba że twoja aplikacja ZAWSZE działa specjalnie w UTC, co może się zmienić z wielu powodów, z których większość obraca się wokół złego kodu.
David