установить часовой пояс UTC, который уже находится в UTC - PullRequest
0 голосов
/ 05 июня 2018

У меня есть приложение, в котором я установил часовой пояс UTC следующим образом:

// set default time zone to UTC
TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.UTC));

Это подходит для всех полей даты, которые мы храним в базе данных как UTC.Но одна дата уже наступает в UTC, поэтому выше часовой пояс снова установит ее в UTC.

Как не установить ее снова в UTC?

Какой-то код -

public static void main(String[] args) throws DatatypeConfigurationException {
        DatatypeFactory datatypeFactory  = DatatypeFactory.
         newInstance();
        // set default time zone to UTC
        TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.UTC));
        // coming from external program in UTC but assigned it here for reference
        long someTimeToBeInUTC = 1528371000000L;
        GregorianCalendar start = new GregorianCalendar();
        start.setTimeInMillis(someTimeToBeInUTC);
        XMLGregorianCalendar startXmlCalendar  = 
        datatypeFactory.newXMLGregorianCalendar(start);
        System.out.println(startXmlCalendar.
        toGregorianCalendar().getTime());

    }

Подумайте, у меня есть момент времени someTimeToBeInUTC независимо от часового пояса, а затем из-за строки TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.UTC)) он устанавливает его в UTC.

Тогда в hibernate у меня есть свойство в hbm.xml таблицыгде я храню это значение -

<property name="startTime" type="timestamp">
            <column name="start_time" length="29" />
        </property>

Тип startTime - метка времени без часового пояса в postgres.Поэтому, когда мы сохраняем данные, он снова конвертирует их в UTC.

Ответы [ 2 ]

0 голосов
/ 06 июня 2018

tl; dr

Вы, кажется, слишком много работаете.

Вы используете неправильные классы.Вместо этого используйте современные java.time классы.

myPreparedStatement.setObject(
    … , 
    Instant.ofEpochMilli( 1_528_371_000_000L ) 
)

В вашей базе данных используется неверный тип данных .Тип TIMEZONE WITHOUT TIME ZONE нельзя использовать для сохранения определенного момента.

Избегать установки часового пояса по умолчанию

TimeZone.setDefault

Этот вызов немедленно влияет на весь код во всех потоках всех приложений в JVM!Так что делайте этот призыв только в самых отчаянных обстоятельствах.

Вместо этого передайте желаемый / ожидаемый часовой пояс в качестве необязательного аргумента ZoneId многим из java.time методов.

часовой пояс по умолчанию вашей операционной системы и JVM должны быть не связаны с вашим кодом.Укажите явно желаемый / ожидаемый часовой пояс в виде объекта ZoneId (или ZoneOffset).

Избегайте устаревших классов даты и времени.

Старые классы даты и времени, связанные с ранними версиями Java, ужасны.Это включает в себя класс GregorianCalendar, видимый в вашем коде.Вместо этого используйте их замену, современные java.time классы.

В частности, GregorianCalendar заменяется на ZonedDateTime.

Instant

Класс Instant в java.time является основным строительным блоком.Класс Instant представляет момент на временной шкале в UTC с разрешением наносекунд (до девяти (9) цифр десятичной дроби).

Instant instant = Instant.now() ;  // Capture the current moment in UTC.

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

long input = 1_528_371_000_000L ;
Instant instant = Instant.ofEpochMilli( input ) ;

instant.toString (): 2018-06-07T11: 30: 00Z

Часовые пояса

Возможно, вы захотите представить Instant пользователю в определенном часовом поясе.Примените ZoneId, чтобы получить ZonedDateTime.

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

ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;  // Or Europe/Paris, Africa/Tunis, etc.

База данных

Я не знаю, что вы делаете с XMLGregorianCalendar.И я не могу обратиться к Hibernate, так как я не пользователь, хотя я знаю, что Hibernate поддерживает java.time типов .

Но если вы пытаетесь записать и извлечь моментс Postgres вы слишком много работаете.

Сохранение момента в UTC в столбец типа TIMESTAMP WTH TIME ZONE ( не WITHOUT! - см. ниже).

String sql = "INSERT INTO tbl ( event ) VALUES ( ? ) ;" ;       // Writing a moment into a column of type `TIMESTAMP WTH TIME ZONE`.
…
Instant instant = Instant.ofEpochMilli( 1_528_371_000_000L ) ;  // Representing a moment in UTC.
myPreparedStatement.setObject( 1 , instant ) ;                  // As of JDBC 4.2 and later, we can directly exchange java.time objects with our database.

Извлечение.

Instant instant = myResultSet.getObject( … , Instant.class ) ;

Имейте в виду, что стандарт SQL едва затрагивает тему даты и времени.Различные базы данных ведут себя очень по-разному в своей обработке даты и времени.

Способ работы Postgres заключается в том, что TIMESTAMP WITH TIME ZONE является чем-то неправильным.Часовой пояс , а не , хранящийся как часть метки времени.Любая предоставленная временная зона информации о смещении от UTC, представленная с помощью ввода, используется для настройки на UTC.Значение UTC записывается в базу данных.Зона / смещение затем отбрасывается / забывается.Если вам важно запомнить исходную зону / смещение входных данных, вы должны сохранить их отдельно в другом столбце.

Получая значение TIMESTAMP WITH TIME ZONE из Postgres, вы всегда получаете значение в UTC.Это может быть неочевидно для вас, если ваш инструмент промежуточного программного обеспечения, соединяющий вас (ваше приложение) с базой данных, вводит собственное мнение относительно желаемого часового пояса для презентации.

Избежать этой путаницы с часовыми поясами легко с Java: Передавать Instant объекты и извлекать Instant объекты. Instant всегда находится в UTC.Поэтому вызов ResultSet::getObject( … , Instant.class) всегда будет точно представлять значение TIMESTAMP WITH TIME ZONE базы данных.

Один сложный момент - разрешение доли секунды.Классы java.time используют разрешение наносекунд, а Postgres - микросекунды.Таким образом, вы можете явно усечь Instant в своем коде, чтобы подтвердить этот факт.Ваш драйвер JDBC будет урезан для вас, но мне не нравятся такие закулисные манипуляции с данными.

Instant instant = Instant.now().truncatedTo( ChronoUnit.MILLISECONDS ) ;  // Lop off any nanoseconds to match Postgres storing microseconds.

Неправильный тип данных в вашем столбце

Тип startTime:отметка времени без часового пояса в postgres.

TIMESTAMP WITHOUT TIME ZONE - это неверный тип данных для хранения момента.В этом типе отсутствует понятие часового пояса или смещения от UTC.Так что это не может представлять конкретный момент.Он представляет собой набор потенциальных моментов в диапазоне около 26-27 часов (диапазон всех часовых поясов).

Вместо этого вы должны использовать тип TIMESTAMP WITH TIME ZONE для хранения определенного момента на временной шкале.

Поэтому, когда мы сохраняем данные, он снова преобразует их в UTC.

Нет, совсем нет.Как раз наоборот.Любая включенная информация о часовом поясе или смещении от UTC игнорируется.Дата и время дня принимаются как есть, без учета часового пояса, без учета UTC.

Стандартный SQL-тип TIMEZONE WITHOUT TIME ZONE эквивалентен java.time типу LocalDateTime.

Вы должны использовать эти типы без зоны только в следующих трех ситуациях:

  • Зона или смещение неизвестно .Это плохо.Это неверные данные.Аналогично наличию цены / стоимости без знания валюты.Вы должны отвергать такие данные, а не хранить их.
  • Намерение «везде» , как в каждом часовом поясе.Например, корпоративная политика, которая гласит: «Все наши фабрики будут работать на обед в 12:30» означает, что фабрика в Дели сломается за несколько часов до фабрики в Дюссельдорфе, которая ломается за несколько часов до фабрики в Детройте.
  • Момент в будущем намечен, но мы боимся политиков переопределить часовой пояс .Правительства меняют правила своих часовых поясов с удивительной частотой и с удивительно небольшим предупреждением или даже без предупреждения .Поэтому, если вы хотите записаться на прием в 3 часа дня на определенную дату, и вы действительно имеете в виду 3 часа дня, независимо от какого-либо сумасшедшего решения, которое правительство может принять за это время, сохраните LocalDateTime.Чтобы распечатать отчет или отобразить календарь, динамически примените часовой пояс (ZoneId), чтобы сгенерировать определенный момент (ZonedDateTime или Instant).Это нужно делать на лету, а не сохранять значение.

О 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?

  • Java SE 8 , Java SE 9 , Java SE 10, а позже
    • Встроенный.
    • Часть стандартного Java API с встроенной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 и Java SE 7
    • Большая часть функций java.time перенесена на Java 6 & 7 в ThreeTen-Backport .
  • Android
    • Более поздние версии Android связывают реализации классов java.time.
    • Для более ранних версий Android (<26)проект <a href="https://github.com/JakeWharton/ThreeTenABP" rel="nofollow noreferrer"> ThreeTenABP адаптируется ThreeTen-Backport (упомянуто выше).См. Как использовать ThreeTenABP… .

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

0 голосов
/ 05 июня 2018

Время в миллисекундах не имеет ничего о часовом поясе.

Время в миллисекундах - это точный момент времени, независимо от того, где вы используете его в объектах Calendar, Date или Timestamp.

Пожалуйста, внимательно прочитайте документацию по Java.Он всегда показывает миллисекунды, прошедшие с эпохи (01.01.1970 00:00:00 UTC).

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

ОБНОВЛЕНИЕ Допустим, у вас есть время в миллисекундах как 1528371250000L ...

, тогда вы можетеиметь два календаря (они поддерживают TimeZone), один в UTC, один в восточном

см. полный пример кода, чтобы проиллюстрировать, что происходит, когда в обоих из них установлено одинаковое время в миллисекундах:

public static void main(String... args) throws Exception {

    Calendar utcCalendar = Calendar
            .getInstance(TimeZone.getTimeZone("UTC"));

    utcCalendar.setTimeInMillis(1528371250000L);

    System.out.println("UTC Calendar: \t\t" + (utcCalendar.get(Calendar.MONTH) +1)
            + "/" + utcCalendar.get(Calendar.DAY_OF_MONTH) + "/"
            + utcCalendar.get(Calendar.YEAR) + " "
            + utcCalendar.get(Calendar.HOUR_OF_DAY) + ":"
            + utcCalendar.get(Calendar.MINUTE) + ":"
            + utcCalendar.get(Calendar.SECOND) + " " + utcCalendar.getTimeZone().getID());

    Calendar eastCalendar = Calendar.getInstance(TimeZone
            .getTimeZone("EST"));

    eastCalendar.setTimeInMillis(1528371250000L);

    System.out.println("Eastern Calendar: \t"
            + (eastCalendar.get(Calendar.MONTH)+1) + "/"
            + eastCalendar.get(Calendar.DAY_OF_MONTH) + "/"
            + eastCalendar.get(Calendar.YEAR) + " "
            + eastCalendar.get(Calendar.HOUR_OF_DAY) + ":"
            + eastCalendar.get(Calendar.MINUTE) + ":"
            + eastCalendar.get(Calendar.SECOND) + " " + eastCalendar.getTimeZone().getID());

    XMLGregorianCalendar xmlUtcCalendar = DatatypeFactory.newInstance()
            .newXMLGregorianCalendar((GregorianCalendar) utcCalendar);

    System.out.println("XML UTC Calendar: \t" + xmlUtcCalendar.toString());

    XMLGregorianCalendar xmlEastCalendar = DatatypeFactory.newInstance()
            .newXMLGregorianCalendar((GregorianCalendar) eastCalendar);

    System.out.println("XML Eastern Calendar: \t" + xmlEastCalendar.toString());

}

Выход:

 UTC Calendar:          6/7/2018 11:34:10 UTC
 Eastern Calendar:      6/7/2018 6:34:10 EST
 XML UTC Calendar:      2018-06-07T11:34:10.000Z
 XML Eastern Calendar:  2018-06-07T06:34:10.000-05:00
...