Как преобразовать ZonedDateTime в Date?

105

Я пытаюсь установить независимую от сервера дату и время в своей базе данных, и я считаю, что лучший способ сделать это - установить дату и время в формате UTC. Мой сервер db - это Cassandra, а драйвер db для Java понимает только тип Date.

Итак, предполагая, что в моем коде я использую новую Java 8 ZonedDateTime для получения UTC сейчас ( ZonedDateTime.now(ZoneOffset.UTC)), как я могу преобразовать этот экземпляр ZonedDateTime в «устаревший» класс Date?

Милен Ковачева
источник
В Java встроены два класса «Date» java.util.Dateи java.sql.Date.
Basil Bourque

Ответы:

170

Вы можете преобразовать ZonedDateTime в момент времени, который можно использовать непосредственно с Date.

Date.from(java.time.ZonedDateTime.now().toInstant());
Slim Soltani Dridi
источник
29
Нет, это будет текущая дата в системе вашей зоны по умолчанию.
Slim Soltani Dridi
6
@MilenKovachev Ваш вопрос не имеет смысла - Date не имеет часового пояса - он представляет только момент времени.
assylias
1
@assylias На самом деле, ваше утверждение не имеет смысла. Формат, в котором хранится время, подразумевает часовой пояс. Дата основана на UTC, к сожалению, Java делает некоторые глупые вещи и не рассматривает это как таковое, и, кроме того, считает время локальным TZ вместо UTC. Способ хранения данных Data, LocalDateTime, ZonedDateTime подразумевает часовой пояс. Их используют так, как будто ТЗ не существует, что совершенно неверно. java.util.Date неявно имеет TZ JVM по умолчанию. Тот факт, что люди относятся к нему как к чему-то другому (включая java-документацию для него!), Просто плохие люди.
Дэвид
5
@ Дэвид эээ, нет - это Dateколичество миллисекунд с начала эпохи - значит, это связано с UTC. Если вы распечатаете его, будет использоваться часовой пояс по умолчанию, но класс Date не знает часовой пояс пользователя ... См., Например, раздел «сопоставление» в docs.oracle.com/javase/tutorial/datetime/iso/legacy .html И LocalDateTime явно без ссылки на часовой пояс, что может сбивать с толку ...
assylias
2
Ваш ответ напрасно вовлекает ZonedDateTime. java.time.InstantКласс является непосредственно заменой java.util.Date, как представляющий момент в UTC , хотя Instantиспользует более высокое разрешение наносекунд вместо миллисекунд. Date.from( Instant.now() )должно было быть твоим решением. Или, если на то пошло, это new Date()имеет тот же эффект, фиксируя текущий момент в UTC.
Basil Bourque
70

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.
)

Хотя в приведенном выше коде не было смысла. Оба java.util.Dateи Instantпредставляют момент в формате UTC, всегда в формате UTC. Код выше имеет такой же эффект, как:

new java.util.Date()  // Capture current moment in UTC.

Здесь нет пользы от использования ZonedDateTime. Если у вас уже есть ZonedDateTime, настройтесь на UTC, извлекая 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.
)

Другой ответ правильный

Ответ на ssoltanid правильно обращается ваш конкретный вопрос, как преобразовать новую школу java.time объект ( ZonedDateTime) для старой школы java.util.Dateобъекта. Извлеките Instantиз ZonedDateTime и перейдите к java.util.Date.from().

Потеря данных

Обратите внимание, что вы столкнетесь с потерей данных , поскольку Instantотслеживает наносекунды с эпохи, а java.util.Dateотслеживает миллисекунды с эпохи.

диаграмма сравнения разрешений миллисекунды, микросекунды и наносекунды

Ваш вопрос и комментарии поднимают другие вопросы.

Хранить серверы в UTC

На ваших серверах обычно должна быть установлена ​​ОС хоста в формате UTC. JVM выбирает этот параметр ОС хоста в качестве часового пояса по умолчанию в реализациях Java, о которых я знаю.

Укажите часовой пояс

Но никогда не следует полагаться на текущий часовой пояс JVM по умолчанию. Вместо того, чтобы выбирать настройку хоста, флаг, переданный при запуске JVM, может установить другой часовой пояс. Еще хуже: любой код в любом потоке любого приложения в любой момент может позвонить, java.util.TimeZone::setDefaultчтобы изменить это значение по умолчанию во время выполнения!

TimestampТип Кассандры

Любая достойная база данных и драйвер должны автоматически обрабатывать настройку прошедшей даты и времени на UTC для хранения. Я не использую Cassandra, но, похоже, у него есть элементарная поддержка даты и времени. В документации указано, что его Timestampтип - это количество миллисекунд той же эпохи (первый момент 1970 года по UTC).

ISO 8601

Кроме того, Cassandra принимает строковые входные данные в стандартных форматах ISO 8601 . К счастью, java.time использует форматы ISO 8601 по умолчанию для синтаксического анализа / генерации строк. Реализация Instantкласса toStringподойдет.

Точность: миллисекунда против наношнура

Но сначала нам нужно уменьшить наносекундную точность ZonedDateTime до миллисекунд. Один из способов - создать новый Instant за миллисекунды. К счастью, в java.time есть несколько удобных методов для преобразования в миллисекунды и обратно.

Пример кода

Вот пример кода в Java 8 (обновление 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

Или, согласно этому документу драйвера Cassandra Java , вы можете передать java.util.Dateэкземпляр (не путать с java.sqlDate). Таким образом, вы можете сделать juDate из этого instantTruncatedToMillisecondsв приведенном выше коде.

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

Если делать это часто, можно сделать однострочник.

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

Но было бы аккуратнее создать небольшой служебный метод.

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;
}

Обратите внимание на разницу во всем этом коде, чем в вопросе. Код вопроса пытался изменить часовой пояс экземпляра ZonedDateTime на UTC. Но в этом нет необходимости. Концептуально:

ZonedDateTime = Мгновенно + ZoneId

Мы просто извлекаем часть Instant, которая уже находится в UTC (в основном в UTC, подробности читайте в документации класса).


Таблица типов даты и времени в Java, как современных, так и устаревших


О java.time

Java.time каркас встроен в Java 8 и более поздних версий. Эти классы вытеснять неприятные старые устаревшие классы даты и времени , такие как java.util.Date, Calendar, и SimpleDateFormat.

Проект Joda-Time , находящийся сейчас в режиме обслуживания , рекомендует перейти на классы java.time .

Чтобы узнать больше, см. Oracle Tutorial . И поищите в Stack Overflow множество примеров и объяснений. Спецификация - JSR 310 .

Вы можете обмениваться объектами java.time напрямую с вашей базой данных. Используйте драйвер JDBC, совместимый с JDBC 4.2 или новее. Нет необходимости в строках, нет необходимости в java.sql.*занятиях.

Где взять классы java.time?

  • Java SE 8 , Java SE 9 , Java SE 10 и более поздние версии
    • Встроенный.
    • Часть стандартного Java API со встроенной реализацией.
    • В Java 9 добавлены некоторые незначительные функции и исправления.
  • Java SE 6 и Java SE 7
    • Большая часть функциональности java.time перенесена на Java 6 и 7 в ThreeTen-Backport .
  • Android

Таблица того, какая библиотека java.time использовать с какой версией Java или Android

Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является испытательной площадкой для возможных будущих дополнений к java.time. Вы можете найти некоторые полезные классы здесь , такие как Interval, YearWeek, YearQuarter, и более .

Василий Бурк
источник
Замечательное объяснение, хорошо детализированное. Спасибо огромное!
Джанмарко Ф.
5

Если вы используете Backport ThreeTen для Android и не можете использовать более новую версиюDate.from(Instant instant) (для которой требуется минимум API 26), вы можете использовать:

ZonedDateTime zdt = ZonedDateTime.now();
Date date = new Date(zdt.toInstant().toEpochMilli());

или:

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

Также прочитайте совет в ответе Базиля Бурка

Дэвид Роусон
источник
1
ThreeTen Backport (и ThreeTenABP) включают DateTimeUtilsкласс с методами преобразования, поэтому я бы использовал Date date = DateTimeUtils.toDate (zdt.toInstant ()); `. Это не так уж и низко.
Ole VV
4

Вот пример преобразования текущего системного времени в UTC. Он включает форматирование ZonedDateTime как String, а затем объект String будет преобразован в объект даты с использованием 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();
    }
Асанка Сиривардена
источник
1

Вы можете сделать это с помощью классов java.time, встроенных в Java 8 и новее.

ZonedDateTime temporal = ...
long epochSecond = temporal.getLong(INSTANT_SECONDS);
int nanoOfSecond = temporal.get(NANO_OF_SECOND);
Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);
Питер Лоури
источник
Извините, откуда берутся временные и все эти константы?
Милен Ковачева
@MilenKovachev A ZonedDateTime - это экземпляр Temporal
Питер Лоури
Спасибо, но вы должны были упомянуть, что отредактировали свой ответ, чтобы мой комментарий не казался глупым.
Милен Ковачева
2
@MilenKovachev вы можете удалить комментарий, но это не глупый вопрос.
Питер Лоури
1
Я не могу понять, почему этот ответ был отклонен. Instants отслеживать секунды и наносекунды. Dateотслеживать миллисекунды. Это преобразование из одного в другое правильно.
Скотт
1

Я использую это.

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;
    }
}

Потому что Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); это не работает !!! Если вы запустите приложение на своем компьютере, это не проблема. Но если вы запустите AWS, Docker или GCP в любом регионе, это вызовет проблемы. Потому что компьютер не является вашим часовым поясом в облаке. Вы должны правильно установить часовой пояс в Code. Например, Азия / Тайбэй. Потом исправят в AWS, Docker или 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
источник
1
Кажется, это один из самых сложных способов ответа на любой из семи ответов. Есть ли преимущества? Думаю, нет. На самом деле нет необходимости проходить форматирование и синтаксический анализ.
Ole VV
Спасибо, спросите вы. Потому что Date errorAns = Date.from (java.time.ZonedDateTime.ofInstant (now, zoneId) .toInstant ()) ;; Это не работает !!!!!
beehuang
Я запустил вам фрагмент секунд незадолго до 17:08 в моем часовом поясе и получил ans=Mon Jun 18 01:07:56 CEST 2018, что неверно, а затем wrongAns=Sun Jun 17 17:07:56 CEST 2018, что правильно.
Ole VV
У меня нет проблем с запуском приложения на моем компьютере. Но при использовании докера возникает проблема. Потому что часовой пояс в докере - это не ваш часовой пояс на компьютере. Вы должны правильно установить часовой пояс в Code. Например, Азия / Тайбэй. Потом исправят в AWS, Docker, GCP или на любом компьютере.
beehuang
@ OleV.V. Вы можете понять ?? или есть вопросы?
beehuang
1

Принятый ответ у меня не сработал. Возвращаемая дата всегда является местной датой, а не датой исходного часового пояса. Я живу в UTC + 2.

//This did not work for me
Date.from(java.time.ZonedDateTime.now().toInstant()); 

Я придумал два альтернативных способа получить правильную дату из ZonedDateTime.

Допустим, у вас есть ZonedDateTime для Гавайев

LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10

или для UTC, как было изначально задано

Instant zulu = Instant.now(); // GMT, UTC+0
ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));

Альтернатива 1

Мы можем использовать java.sql.Timestamp. Это просто, но, вероятно, также повредит вашу целостность программирования.

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

Альтернатива 2

Мы создаем дату из миллисекунд (ответ здесь ранее). Обратите внимание, что локальный ZoneOffset является обязательным.

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
источник
0

Для приложения-докера, такого как beehuang, вы должны установить свой часовой пояс.

В качестве альтернативы вы можете использовать withZoneSameLocal . Например:

2014-07-01T00: 00 + 02: 00 [GMT + 02: 00] преобразуется в

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

по Вт 01 июл 00:00:00 CEST 2014 и до

Date.from(zonedDateTime.toInstant())

по Пн 30 июн 22:00:00 UTC 2014

dwe
источник
-1

Если вас интересует только сейчас, просто используйте:

Date d = new Date();
Джейкоб Экель
источник
Меня интересует сейчас, но сейчас UTC.
Милен Ковачева
2
Да, сейчас это будет UTC, дата не знает ничего лучше.
Джейкоб Экель
3
Нет, не будет. Это будет сейчас в часовом поясе локальной системы. Date не хранит информацию о часовом поясе, но использует текущее системное время и игнорирует текущий системный часовой пояс, поэтому никогда не преобразует его обратно в UTC
Дэвид
2
@David Date хранит время относительно эпохи UTC, поэтому, если вы возьмете новый Date () из системы в США и другой объект из компьютера в Японии, они будут идентичны (посмотрите, сколько времени они оба хранят внутри).
Джейкоб Экель,
1
@JacobEckel - Да, но если вы поместите 9 утра в дату, в то время как ваш tz - это американский tz, а затем измените на JP tz и создадите новую дату с 9 утра, внутреннее значение в Date будет другим. Как только вы добавите DST в микс, вы не сможете надежно использовать Date, если ваше приложение ВСЕГДА не работает в формате UTC, что может измениться по любому количеству причин, большинство из которых связано с плохим кодом.
Дэвид