Як перетворити ZonedDateTime на дату?


103

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

Отже, припускаючи, що в моєму коді я використовую новий Java 8 ZonedDateTime для отримання UTC зараз ( ZonedDateTime.now(ZoneOffset.UTC)), як я можу перетворити цей екземпляр ZonedDateTime у "застарілий" клас дати?


Існує два класи "Дата", вбудовані в Java, java.util.Dateі java.sql.Date.
Василь Бурк

Відповіді:


163

Ви можете перетворити ZonedDateTime на миттєвий, який ви можете використовувати безпосередньо з датою.

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

27
Ні, це буде поточна дата у вашій зоні за замовчуванням.
Slim Soltani Dridi

6
@MilenKovachev Ваше запитання не має сенсу - Дата не має часового поясу - вона представляє лише мить у часі.
assylias

1
@assylias Насправді ваша заява не має сенсу. Формат, у якому зберігається час, передбачає часовий пояс. Дата базується на UTC, на жаль, Java робить якісь дурниці і не поводиться з нею як з такою, і крім того, вважає час місцевим TZ замість UTC. Спосіб зберігання даних Data, LocalDateTime, ZonedDateTime передбачає часовий пояс. Вони звикають так, ніби ТЗ не існують, що просто невірно. java.util.Date має неявний TZ JVM за замовчуванням. Той факт, що люди ставляться до цього як до чогось іншого (включаючи java-документи для цього!) - це просто погані люди.
Девід

5
@David uh uh - a Date- це кількість мілісекунд з епохи - тому це пов'язано з UTC. Якщо його роздрукувати , буде використаний часовий пояс за замовчуванням, але клас Date не знає часового поясу користувача ... Дивіться, наприклад, розділ "відображення" в docs.oracle.com/javase/tutorial/datetime/iso/legacy .html І LocalDateTime явно не посилається на часовий пояс - що можна розглядати як заплутане ...
assylias

1
Ваша відповідь не потребує ZonedDateTime. java.time.InstantКлас є безпосередньо заміною java.util.Date, як представляє момент в UTC , хоча Instantвикористовує більш високу роздільну здатність наносекунд замість мілісекунд. Date.from( Instant.now() )повинно було бути вашим рішенням. Або в тому, що саме new Date()має такий самий ефект, фіксуючи поточний момент в UTC.
Василь Бурк,

66

tl; д-р

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. Якщо у вас вже є a ZonedDateTime, налаштуйте UTC, витягнувши a 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 для зберігання. Я не використовую Кассандру, але, схоже, вона має певну елементарну підтримку дати та часу. У документації сказано, що його Timestampтип - це кількість мілісекунд від тієї ж епохи (перший момент 1970 року за UTC).

ISO 8601

Крім того, Кассандра приймає введення рядків у стандартних форматах ISO 8601 . На щастя, java.time використовує формати ISO 8601 за замовчуванням для синтаксичного аналізу / генерації рядків. Реалізація Instantкласу toStringбуде працювати добре.

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

Але спочатку нам потрібно зменшити наносекундну точність ZonedDateTime до мілісекунд. Один із способів - створити свіжий миттєвий пошук, використовуючи мілісекунди. На щастя, 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

Ми просто витягуємо миттєву частину, яка вже є в UTC (в основному в UTC, прочитайте документ класу для точних деталей).


Таблиця типів дати та часу в Java, як сучасних, так і застарілих


Про java.time

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

Проект 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. Ви можете знайти деякі корисні класи тут , такі як Interval, YearWeek, YearQuarter, і більш .


Чудове пояснення, добре детальне. Дуже дякую!
Джанмарко Ф.

4

Ось приклад перетворення поточного системного часу в UTC. Він включає форматування ZonedDateTime як рядка, а потім об’єкт 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 для 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
Backport ThreeTen (і ThreeTenABP) включають DateTimeUtilsклас із методами перетворення, тому я б використовував Date date = DateTimeUtils.toDate (zdt.toInstant ()); `. Це не так низько.
Оле В. В.

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 відстежувати секунди та наносекунди. Dates відстеження мілісекунд. Це перетворення з одного в інше є правильним.
scottb

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, це створить проблему. Оскільки комп’ютер - це не ваш часовий пояс на Cloud. Ви повинні правильно встановити свій часовий пояс у Коді. Наприклад, Азія / Тайбей. Тоді це буде виправлено в 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);
    }
}

1
Здається, це один із найскладніших способів у будь-якій із 7 відповідей. Чи це має якісь переваги? Думаю, ні. Насправді немає необхідності проходити форматування та синтаксичний аналіз.
Оле В. В.

Дякую, запитаєте. Тому що Date wrongAns = Date.from (java.time.ZonedDateTime.ofInstant (зараз, zoneId) .toInstant ()) ;; Це не робота !!!!!
beehuang

Я запустив вам секундний фрагмент трохи раніше 17:08 у своєму часовому поясі і отримав ans=Mon Jun 18 01:07:56 CEST 2018, що неправильно, а потім wrongAns=Sun Jun 17 17:07:56 CEST 2018, що правильно.
Оле В. В.

У мене не виникає проблем із запуском програми на моєму комп’ютері. Але при використанні докера виникають проблеми. Оскільки часовий пояс у докері - це не ваш часовий пояс у комп’ютері. Ви повинні правильно встановити свій часовий пояс у Коді. Наприклад, Азія / Тайбей. Тоді це буде виправлено в 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);

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


-1

Якщо вас цікавить лише зараз, просто використовуйте:

Date d = new Date();

Мене цікавить зараз, але зараз UTC.
Мілен Ковачев

2
Так, це буде UTC зараз, Дата не знає нічого кращого.
Jacob Eckel

3
Ні, не буде. Зараз це буде в часовому поясі локальної системи. Дата не зберігає жодної інформації про часовий пояс, але вона використовує поточний системний час і ігнорує поточний системний часовий пояс, тому для ніколи не перетворює її назад у UTC
Девід

2
@David Date зберігає час відносно епохи UTC, тому, якщо ви берете нову дату () із системи в США та іншого об'єкта з комп'ютера в Японії, вони будуть ідентичними (подивіться, як довго вони обидва зберігають внутрішньо).
Jacob Eckel

1
@JacobEckel - Так, але якщо ви введете 9 ранку в дату, тоді як ваш тз - це американський тз, а потім зміните на JP тз і створите нову дату з 9 ранку, внутрішнє значення в Даті буде іншим. Як тільки ви вкинете літній час у суміш, ви не зможете надійно використовувати дату, якщо ваш додаток ЗАВЖДИ не працює в UTC, що може змінитися з будь-якої кількості причин, більшість з яких обертаються навколо поганого коду.
Девід
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.