Перетворення між java.time.LocalDateTime і java.util.Date


484

У Java 8 є абсолютно новий API для дати та часу. Одним з найкорисніших класів цього API є LocalDateTimeпроведення незалежних від часу часу значення часу.

Напевно, є мільйони рядків коду, що використовує для цього спадковий клас java.util.Date. Таким чином, при взаємодії старого і нового коду виникне потреба у перетворенні між цими двома. Оскільки, здається, немає прямих методів для цього, як це можна зробити?




Відповіді:


706

Коротка відповідь:

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

Пояснення: (виходячи з цього питання про LocalDate)

Незважаючи на свою назву, java.util.Dateявляє собою момент на часовій лінії, а не "дату". Фактичні дані, що зберігаються в об'єкті, - цеlong кількість мілісекунд з 1970-01-01T00: 00Z (опівночі на початку 1970 GMT / UTC).

Клас, еквівалентний java.util.DateJSR-310, є Instant, таким чином, є зручні методи забезпечення перетворення сюди:

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

java.util.DateПримірник не має поняття про час зони. Це може здатися дивним, якщо ви зателефонуєте toString()на "a" java.util.Date, оскільки значення toStringвідносно часового поясу. Однак цей метод фактично використовує часовий пояс Java за замовчуванням на ходу для надання рядка. Часовий пояс не є частиною фактичного стану Росії java.util.Date.

А Instantтакож не містить жодної інформації про часовий пояс. Таким чином, для перетворення з Instantлокального на дату-час необхідно вказати часовий пояс. Це може бути зона за замовчуванням - ZoneId.systemDefault()- або це часовий пояс, яким керує ваша програма, наприклад часовий пояс із налаштувань користувача. LocalDateTimeмає зручний заводський метод, який займає як миттєвий, так і часовий пояс:

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

Зворотним LocalDateTimeчином часовий пояс задається за допомогою виклику atZone(ZoneId)методу. ZonedDateTimeПотім можуть бути перетворені безпосередньо до Instant:

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

Зауважте, що перехід від LocalDateTimeдо ZonedDateTimeможе потенційно внести несподівану поведінку. Це пояснюється тим, що не кожен місцевий час існує через літній час. Восени / восени спостерігається перекриття локальної часової лінії, де однаковий місцевий час зустрічається двічі. Навесні є розрив, де зникає година. Докладніше atZone(ZoneId)про те, що буде робити перетворення, див. У Javadoc програми .

Підсумок: якщо ви об'їжджаєте дорогу java.util.Dateна a LocalDateTimeі назад до java.util.Dateвас, ви можете закінчити іншу мить через літній час.

Додаткова інформація: Є ще одна різниця, яка вплине на дуже давні дати. java.util.Dateвикористовує календар, який змінюється 15 жовтня 1582 року, а дати до цього використовують юліанський календар замість григоріанського. Навпаки, java.time.*використовує календарну систему ISO (еквівалентну грегоріанській) за весь час. У більшості випадків використання система календарів ISO - це те, що ви хочете, але ви можете побачити дивні ефекти при порівнянні дат до 1582 року.


5
Дякую велике за чітке пояснення. Особливо тому, java.util.Dateщо не містить часовий пояс, але друкуйте його протягом toString(). Офіційна документація події не говорить про це чітко під час публікації.
Вишня

Попередження: LocalDateTime.ofInstant(date.toInstant()... не поводиться так, як можна було б наївно очікувати. Наприклад new Date(1111-1900,11-1,11,0,0,0);, стане 1111-11-17 23:53:28використовувати цей підхід. Погляньте на реалізацію, java.sql.Timestamp#toLocalDateTime()якщо вам потрібен результат був 1111-11-11 00:00:00у попередньому прикладі.
собака

2
Я додав розділ про дуже давні дати (до 1582 року). FWIW, запропоноване виправлення може бути помилковим, оскільки 1111-11-11 у java.util.Date - такий самий фактичний день в історії, як 1111-11-18 у java.time через різні календарні системи (різниця в 6,5 хвилин трапляється у багатьох часових поясах до 1900 р.)
JodaStephen

2
Також варто зазначити, що java.sql.Date#toInstantкидає UnsupportedOperationException. Тому не використовуйте toInstantв RowMapper на java.sql.ResultSet#getDate.
LazerBass

132

Ось що я придумав (і, як і всі загадки дати, це, мабуть, буде спростовано на основі деякої дивної коригування часового поясу-ліпий-день: D)

Круглий відключення: Date<<->>LocalDateTime

Подано: 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);

Приклад:

Подано:

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

(1) LocalDateTime<< Instant<< Date:

Створити Instantз Date:

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

Створіть Dateіз Instant(не потрібно, але для ілюстрації):

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

Створити LocalDateTimeзInstant

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

(2) Date<< Instant<<LocalDateTime

Створити Instantз LocalDateTime:

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

Створити Dateз Instant:

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

Вихід:

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 Ви це звертаєтесь до мене або до людини, яка поставила це питання? Щодо моїх думок, добре, було б непогано, щоб перетворення було чітко зазначено в API jdk 8 - Вони могли б принаймні це зробити. У будь-якому випадку, існує безліч бібліотек, які будуть переобладнані для включення нових функцій Java-8 і важливо знати, як це зробити.
Координатор

4
@scottb Чому випадки третьої сторони нечасті? Його чорт звичайний. Один приклад: JDBC 4 і менше (сподіваємось, не 5).
Раман

5
Як правило, правило JSR-310, не потрібно перетворювати між типами за допомогою епох-мільйонів. Кращі альтернативи існують за допомогою об’єктів, дивіться мою повну відповідь нижче. Відповідь, наведена вище, також є повною мірою лише в тому випадку, коли використовується зсув зони на зразок UTC - деякі частини відповіді не працюватимуть для повного часового поясу, як America / New_York.
JodaStephen

2
Замість того , Instant.ofEpochMilli(date.getTime())зробиdate.toInstant()
коза

1
@goat toInstant()виглядає приємно, за винятком цього java.sql.Date, arggggh! Тож нарешті простіше у використанні Instant.ofEpochMilli(date.getTime()).
vadipp

22

Набагато зручніший спосіб, якщо ви впевнені, що вам потрібен часовий пояс за замовчуванням:

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

25
Звичайно, це простіше, але мені не подобається змішувати речі, пов'язані з jdbc, з простою обробкою датами, принаймні IMHO.
Енріко Джурін

9
Вибачте, це жахливе рішення простої проблеми. Це як би визначити 5, щоб бути рівним кількості кілець в олімпійському логотипі.
Madbreaks

7

наступне, здається, працює при перетворенні нового API LocalDateTime в java.util.date:

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

зворотне перетворення можна (сподіваємось) досягти аналогічним чином ...

сподіваюся, що це допоможе ...


5

Тут все: http://blog.progs.be/542/date-to-java-time

Відповідь із "круглим відключенням" не точна: коли це робити

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

якщо часовий пояс вашої системи не UTC / GMT, ви змінюєте час!


Тільки якщо ви робите це протягом 1 години на рік, восени, коли два рази з LocalDateTime перекриваються. Хід весни вперед не викликає проблем. Більшість разів перетворить обидва напрямки належним чином.
Девід


3

Я не впевнений, чи це найпростіший чи найкращий спосіб, чи є якісь підводні камені, але це працює:

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
Там, безумовно, пастка переходу від LocalDateTimeдо Date. При переході на літній режим переходи LocalDateTimeможуть бути неіснуючими або виникати двічі. Вам потрібно розібратися, що ви хочете, щоб сталося у кожному конкретному випадку.
Джон Скіт

1
BTW, GregorianCalendarналежить старому незручному API, який java.timeмає на меті замінити новий API
Вадим

3

Якщо ви перебуваєте на android і використовуєте threetenbp ви можете використовуватиDateTimeUtils замість.

колишній:

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

ви не можете використовувати, Date.fromоскільки він підтримується лише на api 26+

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.