2015-09-19 1 views
2

У меня есть метка времени, которая похожа на POSIX Time, за исключением того, что она не считается в UTC.Как получить время POSIX (UTC) из последовательного значения локального времени-даты с Java 8 DateTime API

Вместо этого это число миллисекунд, прошедших с полуночи, 1 января 1970 г. в определенном локальном часовом поясе. Чтобы сделать это значение в Instant, я должен сначала узнать его смещение (в миллисекундах) по UTC/GMT.

Таким образом, проблема заключается в следующем: знание идентификатора локального часового пояса, например. «Америка/Чикаго» и количество миллисекунд с местной эпохи, как мне сделать Мгновенный (который должен быть построен с миллисекундами с эпохи POSIX)?

Не похоже, что любой из конструкторов java.time API принимает параметр миллисекунды в локальной Epoch.

У меня есть решение, в котором я сначала конвертирую локальную дату в миллисекунду в локальный григорианский календарный период времени (из которого я могу затем построить LocalDateTime и получить смещение по UTC), но это похоже на много из-за того, что кажется, что это должно быть довольно просто.

+2

Кстати, я должен сказать, что эта схема использования подсчета от эпохи в разных часовых поясах, а не в UTC - это идея * очень плохой. Если у вас есть какой-либо контроль над причиной, я настоятельно рекомендую вам исправить эту проблему на своем источнике, а не пытаться улучшить ее. –

+1

Можете ли вы разместить некоторые данные образца? Дайте подсчет-эпоху, ее предполагаемый часовой пояс и ожидаемый результат. –

+1

FYI, нет такой вещи, как «местная эпоха». Эта идея лежит в основе этого ошибочного подхода. –

ответ

2

Неправильный способ отслеживания даты и времени

Во-первых, я должен сказать, что это использование кол-from- epoch целых чисел для значений даты и времени в различных time zones, а не в UTC действительно, очень плохая идея. Я видел некоторые плохие способы обработки дат, включая изобрести один или два плохих пути. Но это самое худшее. Кто бы это ни думал, должен быть приговорен к году ежедневных чтений ответов StackOverflow с пометками «java», «date» и «Jon Skeet».

Использование count-from-epoch для обработки даты в вашем коде приложения похоже на использование бит-массивов для обработки текста. У нас есть классы/интерфейсы, такие как CharSequence, String, StringBuilder, Printer, Reader и т. Д., Чтобы справиться с сложными деталями текста, символов, кодировки символов, сопоставлений и т. Д. Для упрощения написания приложений. Представьте, что вы пытаетесь отлаживать, устранять неполадки и записывать текстовые данные в виде бит-массивов. Сумасшедший, не так ли? Попытка отладки, устранения неполадок и записи данных даты и времени, так как длинные целые числа тоже сумасшедшие.

Ditto for date-time, где у нас было Joda-Time и теперь есть его преемник java.time (Tutorial), встроенный в Java 8 и более поздние версии.

Во-вторых, неявное изменение отсчета от эпохи в часовой пояс, а затем потерять этот факт еще хуже.

Fix

Путь, чтобы исправить это, чтобы получить, что отсчет от-эпохи в произвольной временной зоне переведенного в местную дату и местное время, когда местный означает wall-clock time глазами людей, чем время зона.

С помощью этого локального дате-времени в ручном режиме мы создаем объект с датой, который имеет назначенный часовой пояс, ZonedDateTime. A ZonedDateTime в основном Instant (точка на шкале времени в UTC) плюс ZoneId (часовой пояс).

Поскольку автор вопроса не предоставил никаких выборочных данных, давайте создадим значение в этом мерзком виде. Получите текущий момент в часовом поясе Чикаго.Получите легитимный счет-от-эпохи, настроившись от nanosecond до millisecond. Затем произвольно добавьте/вычтите смещение от UTC для этого часового пояса.

В этом примере мы используем часовой пояс America/Chicago. Это смещение для нашего образца, с летним временем, составляет -05:00. В миллисекундах 5 * 60 * 60 * 1000 = 18 000 000.

// First, create sample data, a count-from-epoch but not in UTC, instead adjusted for the time zone’s offset. 
ZoneId zoneId = ZoneId.of("America/Chicago"); 

// 2015-09-19T12:34:56.000-05:00[America/Chicago] 
ZonedDateTime zdtTemp = ZonedDateTime.of(2015 , 9 , 19 , 12 , 34 , 56 , 0 , zoneId); 
long millisecondsFromEpoch = zdtTemp.toInstant().toEpochMilli(); // Loosing data, goin from nanosecond 
long offsetInMillisecondsForChicagoInDaylightSavingTime = 18_000_000L; // Offset of `-05:00` is in milliseconds, 5 * 60 * 60 * 1,000 = 18,000,000. 
long input = (millisecondsFromEpoch - offsetInMillisecondsForChicagoInDaylightSavingTime); 

Дамп для консоли.

System.out.println("zoneId : " + zoneId); 
System.out.println("zdtTemp : " + zdtTemp); 
System.out.println("millisecondsFromEpoch : " + millisecondsFromEpoch); 
System.out.println("offsetInMillisecondsForChicagoInDaylightSavingTime : " + offsetInMillisecondsForChicagoInDaylightSavingTime); 
System.out.println("input : " + input); 

Теперь выполняйте эту работу. Возьмите этот жуткий номер ввода, притворившись, что он находится в UTC, хотя мы знаем, что это не так, чтобы произвести Мгновенное действие. Из Мгновенно получите LocalDateTime. Теперь нажмите LocalDateTime в часовой пояс, чтобы получить то, что мы, наконец, хотим, ZonedDateTime.

// With example data in hand, proceed to convert to a valid date-time object. 
Instant instantPretendingToBeInUtcButNotReally = Instant.ofEpochMilli(input); 
LocalDateTime localDateTimeOfPretendInstant = LocalDateTime.ofInstant(instantPretendingToBeInUtcButNotReally , ZoneOffset.UTC); 
ZonedDateTime zdt = localDateTimeOfPretendInstant.atZone(zoneId); 

Дамп для консоли.

System.out.println("instantPretendingToBeInUtcButNotReally : " + instantPretendingToBeInUtcButNotReally); 
System.out.println("localDateTimeOfPretendInstant : " + localDateTimeOfPretendInstant); 
System.out.println("zdt : " + zdt); 

При запуске.

zoneId : America/Chicago 
zdtTemp : 2015-09-19T12:34:56-05:00[America/Chicago] 
millisecondsFromEpoch : 1442684096000 
offsetInMillisecondsForChicagoInDaylightSavingTime : 18000000 
input : 1442666096000 
instantPretendingToBeInUtcButNotReally : 2015-09-19T12:34:56Z 
localDateTimeOfPretendInstant : 2015-09-19T12:34:56 
zdt : 2015-09-19T12:34:56-05:00[America/Chicago] 

CAVEAT Я сделал это в спешке. Прокомментируйте или исправьте ошибки.

3

Вычислить момент измененной эпохи:

ZoneId tz = ZoneId.of("America/Chicago"); 
Instant modifiedEpoch = ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, tz).toInstant(); 

Затем добавьте Миллис:

Instant instant = modifiedEpoch.plusMillis(millis); 
+0

В POSIX, я считаю, что день с 00:00 до 00:00:00 до 23: 59: 59.999, где 00:00:00 определяется как полночь и 12:00:00 определяется как полдень. Это справедливо для Java. – scottb

+0

@scottb Ах, хорошо, что плохо. – assylias

0

Потому что хронологические единицы времени являются равноценно, на первый взгляд может показаться, что вы могли бы иметь локальный дата-время в следующем формате двойной точности:

57272.5 

, ...

  • 57272 - это номер дня, отсчитываемый от эпохи измененного юлианского дня (17 ноября 1858 года).
  • 0,5 является местное время, выраженное в виде доли одного дня, например. 0,5 = 12:00 по местному времени.

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

localMillis = (dayNumber - POSIX_EPOCH_AS_MJD)/(86400.0 * 1000.0); 

Это где в этом случае исходит понятие «миллисекунды с местной эпохи». Ошибка здесь, однако, заключается в том, что НЕ является простым взаимно-однозначным соответствием между POSIX Epoch millis и «local» epoch millis (определение POSIX Time требует, чтобы счетчик находился в миллисекундах от Epoch в UTC). Это связано с тем, что локальный номер содержит один или несколько локальных смещений от UTC, которые не гарантируются как исторически согласованные (в зависимости от законодательства и т. Д.).

Эти «местные» миллины могут использоваться как местная метка времени, но их необходимо скорректировать для исторических летних сбережений и смещений часового пояса с той же осторожностью, что и любая другая отметка времени. Так зачем их использовать? Я не могу придумать причину. Ошибка в этом формате была ошибкой.

Решения этой проблемы, которая была использована:

  • Преобразование «локальный Миллиса» к модифицированному числу Julian дня с местным временем выражен в виде доли от одного дня
  • Transform модифицированного Джулиана дневного номера в локальную григорианскую дату и время календаря (алгоритм, адаптированный из «Астрологических алгоритмов», 2-е изд., J. Meeus).
  • Создать LocalDateTime экземпляр из локального календаря даты и времени, полученного выше
  • Объединить LocalDateTime с местным ZoneId к contruct в ZonedDateTime, из которого Instant получают
  • времени POSIX как UTC миллисекунд от POSIX эпохи полученный из Instant

пример кода для этой процедуры следующим образом:

public static long epochMillisFromLocalMillis(ZoneId tz, long millis) { 
    double dayNum = (millis/(86400.0 * 1000.0)) + POSIX_EPOCH_AS_MJD; 
    int[] ymd_hmsm = toVectorFromDayNumber(dayNum); 

    LocalDateTime ldt = LocalDateTime.of (
      ymd_hmsm[MJD.YEAR], 
      ymd_hmsm[MJD.MONTH], 
      ymd_hmsm[MJD.DAY], 
      ymd_hmsm[MJD.HOURS], 
      ymd_hmsm[MJD.MINUTES], 
      ymd_hmsm[MJD.SECONDS], 
      ymd_hmsm[MJD.MILLIS] * 1000000); 
    long utcMillis = ZonedDateTime 
      .of(ldt, tz) 
      .toInstant() 
      .toEpochMilli(); 
    return utcMillis;  
} 

Благодаря Basil Bourque и assylias за их понимание этой специфической проблемы.