Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

Date.from(java.time.ZonedDateTime.now().toInstant());

Ответ 2

ТЛ; др

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 отслеживает миллисекунды с начала эпохи.

diagram comparing resolutions of millisecond, microsecond, and nanosecond

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

Хранить серверы в 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 class toString подойдет.

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

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

Пример кода

Вот пример кода в 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

Или, согласно этому документу по драйверу Java Cassandra, вы можете передать 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 = Instant + ZoneId

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


Table of date-time types in Java, both modern and legacy


О java.time

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

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

Чтобы узнать больше, см. Учебное пособие по Oracle. И поищите в Кару множество примеров и объяснений. Спецификация: 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

Table of which java.time library to use with which version of Java or Android

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

Ответ 3

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

Ответ 4

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

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

или:

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

Пожалуйста, прочитайте совет в ответе Василия Бурка

Ответ 5

Вы можете сделать это, используя классы 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);

Ответ 6

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

Date d = new Date();

Ответ 7

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

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, это вызовет проблемы. Потому что компьютер не ваш часовой пояс в облаке. Вы должны правильно установить часовой пояс в коде. Например, Азия/Тайбэй. Тогда это будет исправлено в 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);
    }
}

Ответ 8

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

Ответ 9

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