Как добавить/вычесть смещение TimeZone к отметке времени в Java?

Я использую JDK 8 и много играл с ZonedDateTime и Timestamp. Но все же я не могу найти решение проблемы, с которой столкнулся.

Допустим, я получаю отформатированное Timestamp по Гринвичу (UTC), и мой сервер где-то находится. Допустим, он установлен на Asia/Calcutta TimeZone (чей ZoneOffset равен +05:30).

Как в основном добавить/вычесть смещение моего часового пояса к метке времени?

Input : 2017-09-13 13:10:30.333
Output: 2017-09-13 18:40:30.333

Объяснение: к входным данным добавлено смещение часового пояса, равное 5 часам 30 минутам. Кроме того, ввод имеет тип Timestamp, как и вывод.

Я использую функцию ZoneID.systemDefault() для получения часового пояса, известного JVM. В моем случае TimeZone оказался Asia/Calcutta.


person Prasath Govind    schedule 11.09.2017    source источник
comment
Ввод и вывод - это типы даты или строки?   -  person    schedule 11.09.2017
comment
Ввод не является строкой. Это временная метка.   -  person Prasath Govind    schedule 11.09.2017
comment
добавить/вычесть - неправильный способ думать об этом. См. Часовой пояс!= Смещение в вики тега часового пояса   -  person Matt Johnson-Pint    schedule 11.09.2017
comment
Я никогда не говорил добавить/вычесть часовой пояс. Я сказал добавить/вычесть смещение часового пояса.   -  person Prasath Govind    schedule 11.09.2017
comment
Тем не менее, добавление или вычитание значения Timestamp не является правильным способом преобразования локальной даты/времени в другой часовой пояс (как я объясняю в своем ответе ниже)   -  person    schedule 12.09.2017


Ответы (1)


java.sql.Timestamp не имеет информации о часовом поясе. Он имеет только одно значение*: количество наносекунд с эпохи Unix (1970-01-01T00:00Z или "1st 1 января 1970 года, полночь по Гринвичу"). Вы не конвертируете это значение в часовой пояс, поскольку это число не привязано ни к какому конкретному часовому поясу.

При печати Timestamp он вызывает метод toString(), и он печатает соответствующую дату и время в часовом поясе JVM по умолчанию. Но к самому Timestamp не привязан часовой пояс.

Взгляните на эту статью. больше информации. В нем говорится о java.util.Date, но концепция та же: эти объекты не несут никакой информации о формате или часовом поясе, поэтому вы не можете преобразовать их между часовыми поясами. Вы можете изменить представление этих значений в разных зонах.

Пример: если я возьму 1505308230333000000 за количество наносекунд с начала эпохи. Это же число представляет 13:10 в UTC, 10:10 в Сан-Паулу, 14:10 в Лондоне, 22:10 в Токио, 18:40 в Калькутте и так далее.

Класс Timestamp просто сохраняет значение большого числа*. Одно и то же значение соответствует разным дате/времени в каждом часовом поясе, но его значение всегда одинаково для всех.

Вот почему преобразование Timestamp между разными зонами не имеет смысла: ваш объект Timestamp уже представляет оба 13:10 по UTC и 18:40 по Калькутте (он содержит большое числовое значение, соответствующее этим дате/времени). в соответствующих часовых поясах — изменение этого значения изменит соответствующую местную дату/время для всех часовых поясов).

Что вы можете изменить, так это String представление этого большого числового значения (соответствующая локальная дата/время в указанном часовом поясе).


Если вы хотите получить String с соответствующей датой и временем в другом часовом поясе, вы можете использовать классы java.time.

Сначала вам нужно преобразовать java.sql.Timestamp в java.time.Instant, а затем преобразовать его в часовой пояс, в результате чего получится java.time.ZonedDateTime. Наконец, я форматирую его в String, используя java.time.format.DateTimeFormatter:

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
// convert the timestamp to a zoneddatetime
ZonedDateTime z = timestamp.toInstant().atZone(ZoneId.of("Asia/Calcutta"));
// format it
System.out.println(z.format(fmt)); // 2017-09-13 18:40:30.333

Результат:

2017-09-13 18:40:30.333


Если вы уже знаете, какой часовой пояс использовать (в данном случае Asia/Calcutta), не используйте часовой пояс по умолчанию (ZoneID.systemDefault()) — конечно, вы можете использовать его, если хотите, просто имейте в виду, что он может быть изменен без предварительного уведомления, даже во время выполнения, поэтому лучше всегда указывать явно, какой из них вы используете.


*На самом деле, Timestamp хранит значение большого числа в двух полях: одно для секунд, а другое для значения в наносекундах. Но это деталь реализации, которая не меняет концепции того, что это значение не привязано ни к какому часовому поясу, и поэтому нет смысла преобразовывать Timestamp между поясами

person Community    schedule 11.09.2017
comment
Хьюго, я использую ZoneID.systemDefault() с известным риском. Конечный результат, который мне нужен, — это снова время в формате UTC. Я использую аннотации Джексона для десериализации моего вывода в UTC. Итак, мне не нужно беспокоиться о часовом поясе. В какой бы часовой пояс я ни конвертировал свои входные данные, окончательный результат снова будет преобразован обратно в UTC. ;) - person Prasath Govind; 11.09.2017
comment
Если вам нужен вывод в формате UTC, просто используйте java.util.Timestamp напрямую. Если бы вы могли отредактировать вопрос и прояснить, какой ожидаемый результат (потому что я понял, что вы хотели 2017-09-13 18:40:30.333) - person ; 11.09.2017
comment
2017-09-13 18:40:30.333 - это то, что я хотел. ;) Ваше здоровье. В моем проекте есть еще один слой, о котором я говорил в своем предыдущем комментарии. Неважно.. - person Prasath Govind; 11.09.2017
comment
@PrasathGovind Означает ли это, что это сработало и решило вашу проблему? Если это так, вы можете принять ответ и/или проголосовать за него: stackoverflow.com/help/someone-answers (конечно, вы не обязаны это делать, но если это решило вашу проблему, это считается хорошей практикой). Если это не так, просто дайте мне знать, чтобы я мог скорректировать ответ. Ваше здоровье! - person ; 11.09.2017