JSR 310: преобразование между часовыми поясами

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

Сначала я получаю местный часовой пояс, но затем сбрасываю его на 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())));
    }
}
}

person Sebastien Diot    schedule 15.04.2013    source источник


Ответы (2)


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

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

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

person JodaStephen    schedule 16.04.2013
comment
Благодарю вас! Так что, похоже, я не «получаю» API времени. Должен признаться, что до сих пор мне приходилось много иметь дело с датами, но ни со временем, ни с часовыми поясами. Но я думаю, что один-единственный вопрос может все прояснить: если все (правильно настроенные) компьютеры по всему миру вызовут System.currentTimeMillis() одновременно, получат ли они все одинаковое (приблизительное) значение? В javadoc говорится, что Возвращает текущее время в миллисекундах, что не говорит нам ни о том, является ли оно локальным или UTC, потому что текущий конкретно не подразумевает ни того, ни другого, IMO. - person Sebastien Diot; 16.04.2013
comment
ХОРОШО. Я сам ответил на него, попросив кого-нибудь с другой стороны планеты запустить этот однострочник одновременно со мной: .currentTimeMillis()); } } И в то же время вышло. По сути, System.currentTimeMillis() (и дата) всегда указаны в формате UTC. Я хотел бы, чтобы это было более ясно в Javadoc. :( - person Sebastien Diot; 16.04.2013

tl;dr

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" ) 
)

Подробности

Ответ ДжодаСтефена правильный. Не существует такой вещи, как «LocalMillis». У вас видимо какое-то концептуальное непонимание. Это понятно, поскольку работа с датой и временем на удивление сложна.

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

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

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

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

Часть, которую вы не понимаете, заключается в том, что "source ZoneID" всегда 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 и произносится как "Zulu".

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. И поищите множество примеров и пояснений в Stack Overflow. Спецификация: JSR 310.

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

Где получить классы java.time?

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

person Basil Bourque    schedule 30.09.2018
comment
Привет. Хотя я ценю длинные и подробные ответы, мне кажется странным, что вы предполагаете, что я до сих пор не понимаю, как это работает 5 лет после того, как задал вопрос: у вас, кажется, какое-то концептуальное непонимание.. . - person Sebastien Diot; 02.10.2018
comment
@SebastienDiot Вы также, кажется, неправильно понимаете, что Переполнение стека — это не электронная почта. Когда я пишу здесь, я не разговариваю с вами индивидуально. Я обращаюсь к тысячам других людей, которые читают эту страницу сегодня. Хотя я оформлен как ответ вам, я на самом деле обращаюсь к замешательству, непониманию или любопытству тех, кто искал эту страницу. Если бы я помогал только вам индивидуально, я бы брал плату за консультацию, а не работал бесплатно. Удовольствие от служения потомству делает эту работу бесплатной. - person Basil Bourque; 02.10.2018