Преобразование эпохи в ZonedDateTime в Java

Как конвертировать эпоху, как 1413225446.92000 в ZonedDateTime в яве?

Данный код ожидает длинное значение, поэтому это будет NumberFormatException для значения, указанного выше.

ZonedDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(dateInMillis)), ZoneId.of(TIME_ZONE_PST));

2 ответа

Ответ Василия Бурка хороший. Извлечение наносекунд из дробной части в целое число для наносекунд может повлечь ловушку или две. Я предлагаю:

    String dateInMillis = "1413225446.92000";
    String[] secondsAndFraction = dateInMillis.split("\\.");
    int nanos = 0;
    if (secondsAndFraction.length > 1) { // there’s a fractional part
        // extend fractional part to 9 digits to obtain nanoseconds
        String nanosecondsString
                = (secondsAndFraction[1] + "000000000").substring(0, 9);
        nanos = Integer.parseInt(nanosecondsString);
        // if the double number was negative, the nanos must be too
        if (dateInMillis.startsWith("-")) {
            nanos = -nanos;
        } 
    }
    ZonedDateTime zdt = Instant
            .ofEpochSecond(Long.parseLong(secondsAndFraction[0]), nanos)
            .atZone(ZoneId.of("Asia/Manila"));
    System.out.println(zdt);

Это печатает

2014-10-14T02:37:26.920+08:00[Asia/Manila]

Нам не нужно 64 бит для наносекунд, поэтому я просто использую int,

Если однажды ваше время наступит в научной нотации (1.41322544692E9), вышеуказанное не сработает.

Пожалуйста, укажите желаемый часовой пояс в формате региона / города, если это не Азия / Манила, например, Америка / Ванкувер, Америка /Los_Angeles или Pacific/Pitcairn. Избегайте трехбуквенных сокращений, таких как PST, они неоднозначны и часто не являются истинными часовыми поясами.

Расширяя ответы Бэзила и Оле здесь, для особого случая отрицательной отметки времени, то есть до эпохи. Это вообще возможно? Вот что пишет Джон Скит в "Все о java.util.Date":

Класс Date использует "миллисекунды с начала эпохи Unix" - это значение, возвращаемое getTime() и устанавливаемое либо конструктором Date(long), либо методом setTime(). Поскольку лунная прогулка произошла до эпохи Unix, значение отрицательное: на самом деле это -14159020000.

Единственная реальная разница между ответом Оле (помимо нескольких дополнительных утверждений) заключается в том, что здесь мы не меняем знак наноса, если строка даты начинается с отрицательного знака. Причина этого в том, что при передаче nanos в конструктор Instant это корректировка, поэтому, если мы отправим nanos как отрицательное значение, он фактически отрегулирует секунды назад, и, таким образом, все значение ZonedDateTime будет отключено nanos.

Это из JavaDoc, обратите внимание на интересное поведение:

Этот метод позволяет передавать произвольное число наносекунд. Заводская установка изменит значения секунд и наносекунд, чтобы гарантировать, что сохраненная наносекунда находится в диапазоне от 0 до 999,999,999. Например, следующее приведет к тому же моменту:

Instant.ofEpochSecond (3, 1);
Instant.ofEpochSecond (4, -999_999_999);
Instant.ofEpochSecond (2, 1000_000_001);

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

Взяв код Оле за основу и добавив вышеупомянутые изменения:

    String strDateZoned = "Jul 20 1969 21:56:20.599 CDT"; // yes, should use America/Chicago here as Ole points out
    DateTimeFormatter dtfFormatter  = DateTimeFormatter.ofPattern("MMM dd yyyy HH:mm:ss.SSS zzz");
    ZonedDateTime     originalZoned  = ZonedDateTime.parse(strDateZoned, dtfFormatter);

    long epochSeconds = originalZoned.toInstant().getEpochSecond();
    int nanoSeconds = originalZoned.toInstant().getNano();
    String dateInMillis = epochSeconds + "." + nanoSeconds;
    String[] secondsAndFraction = dateInMillis.split("\\.");
    int nanos = 0;
    if (secondsAndFraction.length > 1) { // there’s a fractional part
        // extend fractional part to 9 digits to obtain nanoseconds
        String nanosecondsString
                = (secondsAndFraction[1] + "000000000").substring(0, 9);
        nanos = Integer.parseInt(nanosecondsString);
    }

    ZonedDateTime zdt = Instant
            .ofEpochSecond(Long.parseLong(secondsAndFraction[0]), nanos)
            .atZone(ZoneId.of("America/Chicago"));

    String formattedZdt = dtfFormatter.format(zdt);

    System.out.println("zoneDateTime expected    = " + strDateZoned);
    System.out.println("zoneDateTime from millis = " + formattedZdt);

    assertEquals("date in millis is wrong",  "-14159020.599000000", dateInMillis);
    assertEquals("date doesn't match expected",strDateZoned, dtfFormatter.format(zdt));

Вывод из кода:

zoneDateTime expected    = Jul 20 1969 21:56:20.599 CDT
zoneDateTime from millis = Jul 20 1969 21:56:20.599 CDT

Если мы поменяем знак на nanos для случая, когда часть секунд отрицательна, мы увидим разницу в отформатированном ZonedDateTime:

org.junit.ComparisonFailure: date doesn't match expected 
Expected :Jul 20 1969 21:56:20.599 CDT
Actual   :Jul 20 1969 21:56:19.401 CDT

PS Еще несколько мыслей из поста "Все о датах" о том, что Джон Скит называет "снисходительностью", и в другом месте, которое я видел, называется "нормализация", что, возможно, связано с влиянием POSIX:

Это снисходительно без очевидной причины: "Во всех случаях аргументы, данные методам для этих целей, не должны попадать в указанные диапазоны; например, дата может быть указана как 32 января и интерпретируется как означающая 1 февраля ". Как часто это полезно?

Разбейте число на пару длинных 64-битных чисел:

  • Количество целых секунд с даты отсчета эпохи первого момента 1970 года в UTC
  • Количество наносекунд за доли секунды

Передать эти цифры заводскому методу Instant.ofEpochSecond​(long epochSecond, long nanoAdjustment)

С Instant в руке, перейдите к назначению часового пояса, чтобы получить ZonedDateTime,

ZoneId z = ZoneId.of( "America/Los_Angeles" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
Другие вопросы по тегам