Konwertowanie między java.time.LocalDateTime i java.util.Date


484

Java 8 ma zupełnie nowy interfejs API dla daty i godziny. Jedną z najbardziej przydatnych klas w tym interfejsie API jest LocalDateTimeprzechowywanie niezależnej od strefy czasowej wartości daty z czasem.

Prawdopodobnie miliony linii kodu używają java.util.Datedo tego celu starszej klasy . W związku z tym podczas łączenia starego i nowego kodu konieczna będzie konwersja między nimi. Ponieważ wydaje się, że nie ma bezpośrednich metod osiągnięcia tego celu, w jaki sposób można to zrobić?




Odpowiedzi:


706

Krótka odpowiedź:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());

Objaśnienie: (na podstawie tego pytania o LocalDate)

Pomimo swojej nazwy, java.util.Datereprezentuje natychmiast na linii czasu, a nie „datę”. Rzeczywiste dane przechowywane w obiekcie to longliczba milisekund od 1970-01-01T00: 00Z (północ na początku 1970 GMT / UTC).

Klasa równoważna java.util.Datew JSR-310 jest Instant, dlatego istnieją wygodne metody zapewnienia konwersji do iz powrotem:

Date input = new Date();
Instant instant = input.toInstant();
Date output = Date.from(instant);

java.util.DateInstancja ma koncepcji strefy czasowej. Może się to wydawać dziwne, jeśli wywołujesz toString()a java.util.Date, ponieważ toStringdotyczy ono strefy czasowej. Jednak ta metoda faktycznie używa domyślnej strefy czasowej Javy w locie, aby podać ciąg. Strefa czasowa nie jest częścią rzeczywistego stanu java.util.Date.

A Instanttakże nie zawiera żadnych informacji o strefie czasowej. Tak więc, aby przekonwertować z Instantlokalnej daty i godziny, konieczne jest określenie strefy czasowej. Może to być strefa domyślna - ZoneId.systemDefault()- lub strefa czasowa kontrolowana przez aplikację, na przykład strefa czasowa z preferencji użytkownika. LocalDateTimema wygodną metodę fabryczną, która zajmuje zarówno strefę czasową, jak i strefę czasową:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());

Odwrotnie LocalDateTimestrefa czasowa jest określana przez wywołanie atZone(ZoneId)metody. ZonedDateTimeMożna następnie przekształcić bezpośrednio do Instant:

LocalDateTime ldt = ...
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Date output = Date.from(zdt.toInstant());

Pamiętaj, że konwersja z LocalDateTimena ZonedDateTimemoże potencjalnie spowodować nieoczekiwane zachowanie. Wynika to z faktu, że nie każda lokalna data i godzina istnieją z powodu czasu letniego. Jesienią / jesienią zachodzi nakładanie się lokalnej linii czasowej, w której ta sama lokalna data i godzina występuje dwukrotnie. Wiosną jest przerwa, w której godzina znika. Zobacz Javadoc z, atZone(ZoneId)aby uzyskać więcej definicji konwersji.

Podsumowując, jeśli podróżujesz w obie strony java.util.Datedo LocalDateTimeiz powrotem java.util.Date, możesz skończyć z inną chwilą ze względu na czas letni.

Informacje dodatkowe: Jest jeszcze jedna różnica, która wpłynie na bardzo stare daty. java.util.Dateużywa kalendarza, który zmienia się 15 października 1582 r., z wcześniejszymi datami, używając kalendarza juliańskiego zamiast kalendarza gregoriańskiego. Dla kontrastu, java.time.*używa systemu kalendarza ISO (odpowiednik gregoriański) przez cały czas. W większości przypadków system kalendarzowy ISO jest tym, czego potrzebujesz, ale możesz zobaczyć dziwne efekty podczas porównywania dat sprzed 1582 roku.


5
Wielkie dzięki za jasne wyjaśnienie. Zwłaszcza dlatego, java.util.Dateże nie zawiera strefy czasowej, ale drukuje ją podczas toString(). Oficjalna dokumentacja wydarzenia nie mówi tego wyraźnie podczas pisania.
Cherry

Ostrzeżenie: LocalDateTime.ofInstant(date.toInstant()... nie zachowuje się tak, jak można by się tego naiwnie spodziewać. Na przykład new Date(1111-1900,11-1,11,0,0,0);stanie się 1111-11-17 23:53:28korzystać z tego podejścia. Spójrz na implementację, java.sql.Timestamp#toLocalDateTime()jeśli potrzebujesz wyniku 1111-11-11 00:00:00z poprzedniego przykładu.
pies,

2
Dodałem sekcję o bardzo starych datach (sprzed 1582 r.). FWIW, sugerowana poprawka może być niepoprawna, ponieważ 1111-11-11 w java.util.Date to ten sam faktyczny dzień w historii, co 1111-11-18 w java.time z powodu różnych systemów kalendarza (różnica 6,5 ​​minuty występuje w wielu strefach czasowych przed 1900 r.)
JodaStephen

2
Warto również zauważyć, że java.sql.Date#toInstantrzuca UnsupportedOperationException. Więc nie używaj toInstantw RowMapper na java.sql.ResultSet#getDate.
LazerBass

132

Oto, co wymyśliłem (i podobnie jak wszystkie zagadki związane z datą i godziną, prawdopodobnie zostanie to obalone na podstawie dziwnej regulacji strefy czasowej-leapyear-światła dziennego: D)

Round-tripping: Date<<->>LocalDateTime

Dany: Date date = [some date]

(1) LocalDateTime<< Instant<<Date

    Instant instant = Instant.ofEpochMilli(date.getTime());
    LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

(2) Date<< Instant<<LocalDateTime

    Instant instant = ldt.toInstant(ZoneOffset.UTC);
    Date date = Date.from(instant);

Przykład:

Dany:

Date date = new Date();
System.out.println(date + " long: " + date.getTime());

(1) LocalDateTime<< Instant<< Date:

Utwórz Instantz Date:

Instant instant = Instant.ofEpochMilli(date.getTime());
System.out.println("Instant from Date:\n" + instant);

Utwórz Datez Instant(nie konieczne, ale do ilustracji):

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

Utwórz LocalDateTimezInstant

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
System.out.println("LocalDateTime from Instant:\n" + ldt);

(2) Date<< Instant<<LocalDateTime

Utwórz Instantz LocalDateTime:

instant = ldt.toInstant(ZoneOffset.UTC);
System.out.println("Instant from LocalDateTime:\n" + instant);

Utwórz Datez Instant:

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

Dane wyjściowe to:

Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

Instant from Date:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

LocalDateTime from Instant:
2013-11-01T14:13:04.574

Instant from LocalDateTime:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

2
@ scottb, czy zwracasz się do mnie lub do osoby, która zadała pytanie? Co do moich myśli, dobrze, że byłoby miło , że konwersja zaznaczono wyraźnie w JDK 8 API - Mogli przynajmniej zrobić. W każdym razie istnieje wiele bibliotek, które zostaną ponownie uwzględnione w celu uwzględnienia nowych funkcji Java-8 i ważne jest, aby wiedzieć, jak to zrobić.
Koordynator

4
@scottb Dlaczego sprawa strony trzeciej byłaby rzadka? To cholernie powszechne. Jeden przykład: JDBC 4 i mniej (mam nadzieję, że nie 5).
Raman

5
Zgodnie z ogólną regułą JSR-310 nie ma potrzeby konwersji między typami za pomocą epoki-milis. Istnieją lepsze alternatywy przy użyciu obiektów, zobacz moją pełną odpowiedź poniżej. Powyższa odpowiedź jest również w pełni poprawna tylko wtedy, gdy używasz przesunięcia strefy, takiego jak UTC - niektóre części odpowiedzi nie będą działać dla pełnej strefy czasowej, jak Ameryka / Nowy_Jork.
JodaStephen

2
Zamiast Instant.ofEpochMilli(date.getTime())zróbdate.toInstant()
kozy

1
@goat toInstant()wygląda ładnie, ale nie udaje się java.sql.Date, arggggh! Jest więc w końcu łatwiejszy w użyciu Instant.ofEpochMilli(date.getTime()).
vadipp

22

Znacznie wygodniejszy sposób, jeśli masz pewność, że potrzebujesz domyślnej strefy czasowej:

Date d = java.sql.Timestamp.valueOf( myLocalDateTime );

25
Jasne, jest łatwiej, ale nie lubię mieszać rzeczy związanych z jdbc z prostą obsługą Data, przynajmniej IMHO.
Enrico Giurin

9
Przepraszam, to okropne rozwiązanie prostego problemu. To tak, jakby zdefiniować 5 jako równą liczbie pierścieni w logo olimpijskim.
Madbreaks

7

podczas konwersji z nowego API LocalDateTime na java.util.date wydaje się, że:

Date.from(ZonedDateTime.of({time as LocalDateTime}, ZoneId.systemDefault()).toInstant());

odwróconą konwersję można (miejmy nadzieję) osiągnąć w podobny sposób ...

mam nadzieję, że to pomoże...


5

Wszystko jest tutaj: http://blog.progs.be/542/date-to-java-time

Odpowiedź z „potknięciem w obie strony” nie jest dokładna: kiedy to zrobisz

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

jeśli strefą czasową systemu nie jest UTC / GMT, zmieniasz godzinę!


Tylko jeśli robisz to przez 1 godzinę w roku, jesienią, kiedy dwa razy z LocalDateTime nakładają się. Sprężynowy ruch do przodu nie powoduje problemów. Większość razy poprawnie konwertuje oba kierunki.
David


3

Nie jestem pewien, czy jest to najprostszy lub najlepszy sposób, czy też są jakieś pułapki, ale działa:

static public LocalDateTime toLdt(Date date) {
    GregorianCalendar cal = new GregorianCalendar();
    cal.setTime(date);
    ZonedDateTime zdt = cal.toZonedDateTime();
    return zdt.toLocalDateTime();
}

static public Date fromLdt(LocalDateTime ldt) {
    ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.systemDefault());
    GregorianCalendar cal = GregorianCalendar.from(zdt);
    return cal.getTime();
}

3
Jest to z pewnością pułapka przechodząc od LocalDateTimedo Date. W przejściach czasu letniego LocalDateTimemoże nie istnieć lub może wystąpić dwukrotnie. Musisz ustalić, co chcesz się wydarzyć w każdym przypadku.
Jon Skeet,

1
BTW, GregorianCalendarnależy do starego niezręcznego API, które nowy java.timeAPI zamierza zastąpić
Vadzim

3

Jeśli korzystasz z Androida i używasz threetenbp , możesz użyć DateTimeUtilszamiast tego.

dawny:

Date date = DateTimeUtils.toDate(localDateTime.atZone(ZoneId.systemDefault()).toInstant());

nie możesz używać, Date.fromponieważ jest obsługiwany tylko w API 26+

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.