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

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

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


person Milen Kovachev    schedule 28.08.2015    source источник
comment
В Java встроены два класса Date: java.util.Date и java.sql.Date.   -  person Basil Bourque    schedule 28.08.2015


Ответы (9)


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

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

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) в объект старой школы _ 10_. Извлеките Instant из ZonedDateTime и перейдите к _ 12_.

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

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

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

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

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

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

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

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

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

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

ISO 8601

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

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

Но сначала нам нужно уменьшить наносекундную точность 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 driver doc, вы можете передать java.util.Date экземпляр (не путать с java.sqlDate) . Таким образом, вы можете создать j.u.Date из этого 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 framework встроена в Java 8 и новее. Эти классы заменяют неудобные старые устаревшие классы даты и времени, такие как _ 27_, _ 28_, & _ 29_.

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

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

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

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

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

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

person Basil Bourque    schedule 28.08.2015
comment
Замечательное объяснение, хорошо детализированное. Большое спасибо! - person Gianmarco F.; 26.03.2019

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

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

or:

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

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

person David Rawson    schedule 28.10.2017
comment
ThreeTen Backport (и ThreeTenABP) включают DateTimeUtils класс с методами преобразования, поэтому я бы использовал Date date = DateTimeUtils.toDate (zdt.toInstant ()); `. Это не так уж и плохо. - person Ole V.V.; 17.06.2018

Вот пример преобразования текущего системного времени в 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();
    }
person Asanka Siriwardena    schedule 23.11.2015

Принятый ответ у меня не сработал. Возвращаемая дата всегда является местной датой, а не датой исходного часового пояса. Я живу в 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);
person Avec    schedule 25.09.2019

Для приложения-докера, такого как 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

person dwe    schedule 11.07.2018

Это можно сделать с помощью 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);
person Peter Lawrey    schedule 28.08.2015
comment
Извините, а откуда берутся временные и все эти константы? - person Milen Kovachev; 28.08.2015
comment
@MilenKovachev ZonedDateTime - это экземпляр Temporal - person Peter Lawrey; 28.08.2015
comment
Спасибо, но вы должны были упомянуть, что отредактировали свой ответ, чтобы мой комментарий не казался глупым. - person Milen Kovachev; 28.08.2015
comment
@MilenKovachev вы можете удалить комментарий, но это не глупый вопрос. - person Peter Lawrey; 28.08.2015
comment
Я не могу понять, почему этот ответ был отклонен. Instants отслеживает секунды и наносекунды. Dates отслеживают миллисекунды. Это преобразование из одного в другое правильно. - person scottb; 29.08.2015

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

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);
    }
}
person beehuang    schedule 16.06.2018
comment
Кажется, это один из самых сложных способов ответа на любой из семи ответов. Есть ли преимущества? Я бы не подумал. На самом деле нет необходимости проходить форматирование и синтаксический анализ. - person Ole V.V.; 16.06.2018
comment
Спасибо, спросите вы. Потому что Date errorAns = Date.from (java.time.ZonedDateTime.ofInstant (now, zoneId) .toInstant ()) ;; Это не работает !!!!! - person beehuang; 17.06.2018
comment
Я запустил вам фрагмент секунд незадолго до 17:08 в моем часовом поясе и получил ans=Mon Jun 18 01:07:56 CEST 2018, что неверно, а затем wrongAns=Sun Jun 17 17:07:56 CEST 2018, что правильно. - person Ole V.V.; 17.06.2018
comment
У меня нет проблем с запуском приложения на моем компьютере. Но при использовании докера возникает проблема. Потому что часовой пояс в докере - это не ваш часовой пояс на компьютере. Вы должны правильно установить часовой пояс в Code. Например, Азия / Тайбэй. Потом исправят в AWS, Docker, GCP или на любом другом компьютере. - person beehuang; 18.06.2018
comment
@ OleV.V. Вы можете понять ?? или есть вопросы? - person beehuang; 20.06.2018
comment
Нет, позвольте мне быть честным, я отказался от понимания, почему вы считаете, что ваш неправильный код правильный, и наоборот. Предполагается, что это может быть связано с игнорированием аббревиатуры часового пояса, которую печатает Date.toString() и которая имеет решающее значение для интерпретации строки. Но это только предположение. - person Ole V.V.; 20.06.2018
comment
Хорошо, не проблема. Поскольку я встречаю проблему, я ее решаю. Возможно, вы сможете позже попробовать в другом часовом поясе компьютера. Вы можете найти один комментарий No, it will be the current Date on your zone system default. – Slim Soltani Dridi Aug 28 '15 at 15:53 к первому ответу. - person beehuang; 20.06.2018
comment
Я видел этот комментарий. Но у Date нет часового пояса. Таким образом, если Date содержит текущее время, тогда оно будет содержать текущее время независимо от часового пояса. - person Ole V.V.; 20.06.2018
comment
Может ты прав. Но я просто хочу получить дату из часового пояса. (Это вопрос ZonedDateTime to Date?). И первый ответ неверен. Потому что это просто по умолчанию для вашей системы зоны. - person beehuang; 20.06.2018

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

Date d = new Date();
person Jacob Eckel    schedule 28.08.2015
comment
Меня интересует сейчас, но сейчас UTC. - person Milen Kovachev; 28.08.2015
comment
Да, сейчас это будет всемирное координированное время, дата не знает ничего лучше. - person Jacob Eckel; 28.08.2015
comment
Нет, не будет. Это будет Сейчас в часовом поясе локальной системы. Date не хранит информацию о часовом поясе, но использует текущее системное время и игнорирует текущий системный часовой пояс, поэтому никогда не преобразует его обратно в UTC. - person David; 23.10.2015
comment
@David Date хранит время относительно эпохи UTC, поэтому, если вы возьмете новый Date () из системы в США и другой объект из компьютера в Японии, они будут идентичны (посмотрите, сколько времени они оба хранят внутри). - person Jacob Eckel; 24.10.2015
comment
@JacobEckel - Да, но если вы поместите 9 утра в дату, в то время как ваша tz является американской tz, а затем измените на JP tz и создадите новую дату с 9 утра, внутреннее значение в Date будет другим. Как только вы добавляете DST в микс, вы не можете надежно использовать Date, если ваше приложение ВСЕГДА не запускается специально в UTC, что может измениться по любому количеству причин, большинство из которых связано с плохим кодом. - person David; 18.11.2015
comment
Я думаю, что Милен хочет UTC + 0, или GMT, или зулусское время. Я добавил ответ, который даст правильный объект Date, если я правильно это понял. new Date () будет соответствовать UTC +? для любого часового пояса / локальных настроек Java работает. Если вы живете в Лондоне, функция new Date () будет работать нормально, но не для других часовых поясов. - person Avec; 25.09.2019