Как обрабатывать календарные часовые пояса с помощью Java?

У меня есть значение Timestamp, которое исходит из моего приложения. Пользователь может находиться в любом локальном часовом поясе.

Поскольку эта дата используется для WebService, которая предполагает, что указанное время всегда находится в GMT, мне нужно преобразовать параметр пользователя из say (EST) в (GMT). Вот кикер: Пользователь не обращает внимания на свою TZ. Он вводит дату создания, которую хочет отправить на WS, поэтому мне нужно:

Пользователь вводит: 5/1/2008 18:12 (EST)
Параметр для WS должен быть : 5/1/2008 6:12 PM (GMT)

Я знаю, что TimeStamps всегда должны быть в GMT по умолчанию, но при отправке параметра, хотя я создал свой календарь из TS (который должен быть в GMT), часы всегда выключены, если пользователь не находится в GMT. Что мне не хватает?

Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate"); Calendar issueDate = convertTimestampToJavaCalendar(issuedDate); ... private static java.util.Calendar convertTimestampToJavaCalendar(Timestamp ts_) { java.util.Calendar cal = java.util.Calendar.getInstance( GMT_TIMEZONE, EN_US_LOCALE); cal.setTimeInMillis(ts_.getTime()); return cal; } 

С предыдущим кодом это то, что я получаю в результате (Short Format for easy reading):

[1 мая 2008 г. 11:12]

 public static Calendar convertToGmt(Calendar cal) { Date date = cal.getTime(); TimeZone tz = cal.getTimeZone(); log.debug("input calendar has date [" + date + "]"); //Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT long msFromEpochGmt = date.getTime(); //gives you the current offset in ms from GMT at the current date int offsetFromUTC = tz.getOffset(msFromEpochGmt); log.debug("offset is " + offsetFromUTC); //create a new calendar in GMT timezone, set to this date and add the offset Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); gmtCal.setTime(date); gmtCal.add(Calendar.MILLISECOND, offsetFromUTC); log.debug("Created GMT cal with date [" + gmtCal.getTime() + "]"); return gmtCal; } 

Вот результат, если я передаю текущее время («12:09:05 EDT» из Calendar.getInstance() ) в:

DEBUG – входной календарь имеет дату [Чт Окт 23 12:09:05 EDT 2008]
DEBUG – офсет -14400000
DEBUG – Создано GMT cal с датой [Чт Окт 23 08:09:05 EDT 2008]

12:09:05 GMT – 8:09:05 EDT.

Сложная часть здесь заключается в том, что Calendar.getTime() возвращает вам Date в вашем текущем часовом поясе, а также не существует способа изменить часовой пояс для календаря и иметь также базовую дату. В зависимости от того, какой тип параметра используется вашим веб-сервисом, вы можете просто захотеть заключить сделку WS в миллисекундах с эпохи.

Спасибо всем за ответ. После дальнейшего расследования я получил правильный ответ. Как упоминалось Skip Head, TimeStamped, который я получал от моего приложения, настраивался на TimeZone пользователя. Поэтому, если пользователь вошел в 6:12 вечера (EST), я бы получил 2:12 вечера (GMT). Мне нужен был способ отменить преобразование, так что время, введенное пользователем, – это время, которое я отправил на запрос WebServer. Вот как я это сделал:

 // Get TimeZone of user TimeZone currentTimeZone = sc_.getTimeZone(); Calendar currentDt = new GregorianCalendar(currentTimeZone, EN_US_LOCALE); // Get the Offset from GMT taking DST into account int gmtOffset = currentTimeZone.getOffset( currentDt.get(Calendar.ERA), currentDt.get(Calendar.YEAR), currentDt.get(Calendar.MONTH), currentDt.get(Calendar.DAY_OF_MONTH), currentDt.get(Calendar.DAY_OF_WEEK), currentDt.get(Calendar.MILLISECOND)); // convert to hours gmtOffset = gmtOffset / (60*60*1000); System.out.println("Current User's TimeZone: " + currentTimeZone.getID()); System.out.println("Current Offset from GMT (in hrs):" + gmtOffset); // Get TS from User Input Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate"); System.out.println("TS from ACP: " + issuedDate); // Set TS into Calendar Calendar issueDate = convertTimestampToJavaCalendar(issuedDate); // Adjust for GMT (note the offset negation) issueDate.add(Calendar.HOUR_OF_DAY, -gmtOffset); System.out.println("Calendar Date converted from TS using GMT and US_EN Locale: " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT) .format(issueDate.getTime())); 

Вывод кода: (введен пользователем 5/1/2008 18:12 (EST)

Текущий часовой пояс текущего пользователя: EST
Текущее смещение от GMT (в часах): – 4 (обычно -5, за исключением корректировки DST)
TS от ACP: 2008-05-01 14: 12: 00.0
Дата календаря, преобразованная из TS с использованием GMT и US_EN Язык: 5/1/08 6:12 PM (GMT)

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

Если это так, вы должны взглянуть на метод setTimeZone classа DateFormat. Это определяет, какой часовой пояс будет использоваться при печати метки времени.

Простой пример:

 SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); formatter.setTimeZone(TimeZone.getTimeZone("UTC")); Calendar cal = Calendar.getInstance(); String timestamp = formatter.format(cal.getTime()); 

Вы можете решить это с помощью Joda Time :

 Date utcDate = new Date(timezoneFrom.convertLocalToUTC(date.getTime(), false)); Date localDate = new Date(timezoneTo.convertUTCToLocal(utcDate.getTime())); 

Java 8:

 LocalDateTime localDateTime = LocalDateTime.parse("2007-12-03T10:15:30"); ZonedDateTime fromDateTime = localDateTime.atZone( ZoneId.of("America/Toronto")); ZonedDateTime toDateTime = fromDateTime.withZoneSameInstant( ZoneId.of("Canada/Newfoundland")); 

Метод преобразования из одного времени в другой (возможно, он работает :)).

 /** * Adapt calendar to client time zone. * @param calendar - adapting calendar * @param timeZone - client time zone * @return adapt calendar to client time zone */ public static Calendar convertCalendar(final Calendar calendar, final TimeZone timeZone) { Calendar ret = new GregorianCalendar(timeZone); ret.setTimeInMillis(calendar.getTimeInMillis() + timeZone.getOffset(calendar.getTimeInMillis()) - TimeZone.getDefault().getOffset(calendar.getTimeInMillis())); ret.getTime(); return ret; } 

Похоже, ваш TimeStamp настроен на часовой пояс исходной системы.

Это устарело, но оно должно работать:

 cal.setTimeInMillis(ts_.getTime() - ts_.getTimezoneOffset()); 

Не устаревший способ заключается в использовании

 Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000) 

но это нужно делать на стороне клиента, поскольку эта система знает, в какой временной зоне она находится.

Объекты « Дата» и « Временная метка» не учитывают часовой пояс: они представляют собой определенное количество секунд с эпохи, не допуская конкретной интерпретации этого момента в виде часов и дней. Временные метки вводят изображение только в GregorianCalendar (это не требуется непосредственно для этой задачи) и SimpleDateFormat , для чего требуется смещение часового пояса для преобразования между отдельными полями и значениями даты (или длинными ).

Проблема OP находится прямо в начале его обработки: пользователь вводит часы, которые являются неоднозначными, и они интерпретируются в локальном часовом поясе, отличном от GMT; на данный момент значение равно «6:12 EST» , которое можно легко распечатать как «11.12 GMT» или любой другой часовой пояс, но никогда не изменится на «6.12 GMT» .

Невозможно сделать SimpleDateFormat, который анализирует «06:12» как «HH: MM» (по умолчанию для местного часового пояса) по умолчанию вместо UTC; SimpleDateFormat слишком умен для собственного блага.

Тем не менее, вы можете убедить любой экземпляр SimpleDateFormat использовать правильный часовой пояс, если явно указать его на вход: просто добавьте фиксированную строку к полученному (и адекватно подтвержденному) «06:12», чтобы проанализировать «06:12 GMT» как “HH: MM z” .

Нет необходимости в явной настройке полей GregorianCalendar или для извлечения и использования временных зон и смещений на летнее время.

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

Что-то, что работало для меня в прошлом, заключалось в определении смещения (в миллисекундах) между часовым поясом пользователя и GMT. После того, как у вас есть смещение, вы можете просто добавить / вычесть (в зависимости от того, каким образом происходит преобразование), чтобы получить подходящее время в любом часовом поясе. Обычно я выполнял это, установив поле миллисекунд объекта Calendar, но я уверен, что вы можете легко применить его к объекту timestamp. Вот код, который я использую для получения смещения

 int offset = TimeZone.getTimeZone(timezoneId).getRawOffset(); 

timezoneId – это идентификатор часового пояса пользователя (например, EST).

java.time

В современном подходе используются classы java.time, которые вытеснили неприятные предыдущие classы времени, связанные с самыми ранними версиями Java.

Класс java.sql.Timestamp является одним из тех устаревших classов. Больше не нужен. Вместо этого используйте Instant или другие classы java.time непосредственно с вашей базой данных, используя JDBC 4.2 и более поздние версии.

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

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

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

 Instant instant = myTimestamp.toInstant() ; 

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

 ZoneId z = ZoneId.of( "America/Montreal" ) ; 

Примените к Instant для создания объекта ZonedDateTime .

 ZonedDateTime zdt = instant.atZone( z ) ; 

Чтобы создать строку для отображения пользователю, выполните поиск Stack Overflow для DateTimeFormatter чтобы найти много обсуждений и примеров.

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

 LocalDate ld = LocalDate.parse( dateInput , DateTimeFormatter.ofPattern( "M/d/uuuu" , Locale.US ) ) ; LocalTime lt = LocalTime.parse( timeInput , DateTimeFormatter.ofPattern( "H:ma" , Locale.US ) ) ; 

Ваш вопрос не ясен. Вы хотите интерпретировать дату и время, введенные пользователем в UTC? Или в другом часовом поясе?

Если вы имели в виду UTC, создайте OffsetDateTime со смещением, используя константу для UTC, ZoneOffset.UTC .

 OffsetDateTime odt = OffsetDateTime.of( ld , lt , ZoneOffset.UTC ) ; 

Если вы имели в виду другой часовой пояс, объедините вместе с объектом часового пояса ZoneId . Но какой часовой пояс? Вы можете определить часовой пояс по умолчанию. Или, если это необходимо, вы должны подтвердить, чтобы пользователь был уверен в своих намерениях.

 ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ; 

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

 Instant instant = odt.toInstant() ; 

…или…

 Instant instant = zdt.toInstant() ; 

Отправьте в базу данных.

 myPreparedStatement.setObject( … , instant ) ; 

О java.time

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

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

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

Где можно получить classы java.time?

  • Java SE 8 , Java SE 9 и более поздние версии
    • Встроенный.
    • Часть стандартного Java API с интегрированной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 и Java SE 7
    • Большая часть функциональных возможностей java.time включена обратно в Java 6 и 7 в ThreeTen-Backport .
  • Android
    • Более поздние версии реализаций пакетов Android classов java.time.
    • Для более ранних Android проект ThreeTenABP адаптирует ThreeTen-Backport (упомянутый выше). См. Раздел Как использовать ThreeTenABP ….

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

  • Разница между UTC и стандартным временем GMT в .NET.
  • Всегда ли полезно хранить время в UTC или это тот случай, когда лучше хранить в местное время?
  • Преобразование дат UTC в другие часовые пояса
  • Как установить часовой пояс по умолчанию в node.js?
  • Как изменить часовой пояс mysql в java-соединении
  • linux конвертировать время (для разных часовых поясов) в UTC
  • Игнорирование часовых поясов в Rails и PostgreSQL
  • Как хранить дату и время и временные метки в часовом поясе UTC с JPA и Hibernate
  • Установите системный часовой пояс из .NET.
  • Заставить часовой пояс Java как GMT / UTC
  • Параметр DateTime с часовым поясом формы PST / CEST / UTC / etc.
  • Interesting Posts
    Давайте будем гением компьютера.