JSR 310: преобразование часовых поясов

Попытка использовать JSR 310 для преобразования значений даты и времени в миллисекундах между часовыми поясами. Работа со значениями в миллисекундах необходима для работы с устаревшими API. В моем случае это между локальным и UTC/GMT, но я ожидаю, что это будет точно такой же код, независимо от того, какие именно часовые пояса источника и назначения задействованы. Вот моя маленькая тестовая программа. Он настроен так, что он повторяется в течение нескольких часов прямо перед последним локальным изменением летнего времени. Вывод неверный, и поэтому я предполагаю, что UTCtoLocalMillis неверен, но, возможно, есть и проблема с моей методологией тестирования. Под неправильным выводом я подразумеваю, что для моего часового пояса нужно вычесть часы, чтобы получить UTC, но код фактически добавляет часы. Во-вторых, точка, в которую попадает DST, также отключается на один час.

Сначала я выбираю местный часовой пояс, но затем сбрасываю его в UTC, так что Date(). ToString() не будет выполнять никакого преобразования при создании вывода.

Я хочу создать метод, который занимает время в миллисекундах, исходный ZoneID и целевой ZoneID и возвращает преобразованные миллисекунды с использованием нового API JSR 310.

public class Test {
static int DAYS_OFFSET_TO_BE_BEFORE_DST_CHANGE = -16;

static TimeZone UTC_TZ = TimeZone.getTimeZone("UTC");

static TimeZone LOCAL_TZ = TimeZone.getDefault();

static ZoneId UTC_ID = ZoneId.of(UTC_TZ.getID());

static ZoneId LOCAL_ZONE_ID = ZoneId.of(LOCAL_TZ.getID());

static long UTCtoLocalMillis(final long utcMillis) {
    final Instant instant = Instant.ofEpochMilli(utcMillis);
    final ZonedDateTime before
        = ZonedDateTime.ofInstant(instant, UTC_ID);
    final ZonedDateTime after
        = before.withZoneSameLocal(LOCAL_ZONE_ID);
    return after.toInstant().toEpochMilli();
}

public static void main(final String[] args) {
    TimeZone.setDefault(UTC_TZ);
    System.out.println("LOCAL_TZ: " + LOCAL_TZ.getDisplayName());
    final Calendar cal = Calendar.getInstance(LOCAL_TZ);
    cal.add(Calendar.DATE, DAYS_OFFSET_TO_BE_BEFORE_DST_CHANGE);
    final Date start = cal.getTime();
    // DST Change: Sunday, 31 March 2013 01:59:59 (local time)
    final long oneHour = 3600000L;
    for (int i = 0; i < 12; i++) {
        final Date date = new Date(start.getTime() + i * oneHour);
        System.out.println("UTC: " + date + "   toLocal: "
                + new Date(UTCtoLocalMillis(date.getTime())));
    }
}
}

3 ответа

Решение

Я хочу создать метод, который занимает время в миллисекундах, исходный ZoneID и целевой ZoneID и возвращает преобразованные миллисекунды с использованием нового API JSR 310.

Ваше требование кажется очень запутанным. Значение "миллис", обычно упоминаемое во временных API, - это "эпоха-миллис", число миллисекунд с 1970-01-01T00:00Z. Значение в миллисах всегда относительно UTC. Хотя существует понятие "местный миллис", оно никогда не используется. В частности, конструктор java.util.Date принимает эпохи-миллисы.

Таким образом, код new Date(UTCtoLocalMillis(date.getTime())) в основном бессмысленно.

ТЛ; др

Instant                              // Represent a moment in UTC with a resolution of nanoseconds.
.ofEpochMilli( utcMillis )           // Parse a count of milliseconds since epoch of 1970-01-01T00:00:00Z.
.atZone(                             // Adjust from UTC to some time zone.
    ZoneId.of( "Pacific/Auckland" ) 
)

подробности

Ответ от JodaStephen правильный. Нет такой вещи как "LocalMillis". Кажется, у вас есть концептуальное недоразумение. Это понятно, поскольку работа с датой и временем удивительно сложна.

Совет: учитесь думать и работать в UTC. Как программист или администратор, забудьте о своем часовом поясе. Думайте о UTC как об одном истинном времени. Различные часовые пояса являются всего лишь вариациями. Делайте ваши записи, трассировки, хранения данных и обмена данными все в UTC. Применяйте часовой пояс только для презентации пользователю. Переключение между UTC и вашим собственным приходским часовым поясом приведет вас в замешательство. Поставьте на стол вторые часы с надписью UTC; используйте его во время работы.

Еще один совет: сфокусируйтесь концептуально на графике. Есть только один поток времени. Основным способом представления этой временной шкалы является UTC. Различные часовые пояса представляют собой разные представления одних и тех же моментов на одной временной шкале.

Вот пример кода java.time.

занимает время в миллисекундах, исходный ZoneID и целевой ZoneID и возвращает преобразованные миллисекунды с использованием нового API JSR 310.

Часть, которую вы не понимаете, состоит в том, что "sourceID зоны" всегда является UTC в течение количества миллисекунд с начала эпохи. Любой, кто проходит через миллисекунды, но не в UTC, неопытен и напрашивается на неприятности.

Если у вас есть счет в миллисекундах с начала отсчета эпохи первого момента 1970 года в UTC, 1970-01-01T00:00:00Z, то проанализируйте как Instant, Instant представляет момент в UTC.

Instant instant = Instant.ofEpochMilli( utcMillis ) ;

Создать текст в String представляя момент в этом Instant, По умолчанию классы java.time используют стандартные форматы ISO 8601 при разборе / генерации строк. Z в конце означает UTC и произносится как "зулу".

String output = instant.toString() ;

2018-01-23T01: 23: 45.123456789Z

Идти в другом направлении...

long millisecondsFromEpoch = instant.toEpochMilli() ;

С Instant В свою очередь, вы можете захотеть посмотреть на этот момент через призму часов, используемых людьми определенного региона, часового пояса. Под "временем настенных часов" мы понимаем время суток и дату, которые видит человек, который смотрит на часы и календарь, висящие на их стене.

Укажите правильное название часового пояса в формате continent/region, такие как America/Montreal, Africa/Casablanca, или же Pacific/Auckland, Никогда не используйте 3-4 буквенное сокращение, такое как EST или же IST поскольку они не являются истинными часовыми поясами, не стандартизированы и даже не уникальны (!).

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same moment, same point on the timeline, different wall-clock time.

Мы можем вернуться в UTC, извлекая Instant,

Instant instant = zdt.toInstant() ;  // Same moment, different wall-clock time.

Последний совет: никогда не используйте Date, Calendar, SimpleDateFormat или другие связанные классы даты и времени. Они бесконечно сбивают с толку и расстраивают. Используйте только классы java.time.


О java.time

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

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

Чтобы узнать больше, смотрите Oracle Tutorial. И поиск переполнения стека для многих примеров и объяснений. Спецификация JSR 310.

Вы можете обмениваться объектами java.time напрямую с вашей базой данных. Используйте драйвер JDBC, соответствующий JDBC 4.2 или более поздней версии. Нет необходимости в строках, нет необходимости в java.sql.* классы.

Где взять классы java.time?

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

Документация Java делает это очень ясным.

См. http://docs.oracle.com/javase/7/docs/api/java/lang/System.html

Возвращает:

the difference, measured in milliseconds, between the current time and midnight, January 1, 1970 UTC*.
Другие вопросы по тегам