Ответ 1
Вы можете конвертировать ZonedDateTime в мгновение, которое вы можете использовать непосредственно с датой.
Date.from(java.time.ZonedDateTime.now().toInstant());
Я пытаюсь установить время агностической даты сервера в своей базе данных, и я считаю, что наилучшей практикой является установка UTC DateTime. Мой сервер db - Cassandra, а драйвер db для Java понимает только тип даты.
Итак, полагая, что в моем коде я использую новый Java 8 ZonedDateTime, чтобы получить UTC сейчас (ZonedDateTime.now(ZoneOffset.UTC)
), как я могу преобразовать этот экземпляр ZonedDateTime в класс "legacy" Date?
Вы можете конвертировать ZonedDateTime в мгновение, которое вы можете использовать непосредственно с датой.
Date.from(java.time.ZonedDateTime.now().toInstant());
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. JVM выбирает этот параметр хоста в качестве часового пояса по умолчанию в тех реализациях Java, о которых я знаю.
Но вы никогда не должны полагаться на текущий часовой пояс JVM по умолчанию. Вместо того, чтобы выбрать настройку хоста, флаг, переданный при запуске JVM, может установить другой часовой пояс. Еще хуже: любой код в любом потоке любого приложения в любой момент может вызвать java.util.TimeZone::setDefault
, чтобы изменить это значение по умолчанию во время выполнения!
Timestamp
ТипЛюбая достойная база данных и драйвер должны автоматически обрабатывать настройку переданного времени и даты в формате UTC для хранения. Я не использую Cassandra, но, похоже, она имеет некоторую элементарную поддержку для даты и времени. В документации говорится, что его тип Timestamp
представляет собой счет в миллисекундах той же эпохи (первый момент 1970 года в UTC).
Кроме того, 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, для получения более подробной информации прочитайте документацию класса).
Среда 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?
Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является полигоном для возможных будущих дополнений к java.time. Здесь вы можете найти несколько полезных классов, таких как Interval
, YearWeek
, YearQuarter
и еще.
Вот пример преобразования текущего системного времени в 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();
}
Если вы используете 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());
Пожалуйста, прочитайте совет в ответе Василия Бурка
Вы можете сделать это, используя классы 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);
Если вас интересует только сейчас, просто используйте:
Date d = new Date();
Я использую это.
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);
}
}
Для докер-приложения, подобного комментарию 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
Принятый ответ не работал для меня. Возвращаемая дата всегда является локальной датой, а не датой исходного часового пояса. Я живу в 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);