Как обрабатывать часовые пояса календаря с помощью Java?
Вопрос
У меня есть значение Timestamp, полученное из моего приложения.Пользователь может находиться в любом локальном часовом поясе.
Поскольку эта дата используется для веб-службы, которая предполагает, что указанное время всегда указано в формате GMT, мне необходимо преобразовать пользовательский параметр, скажем, из (EST) в (GMT).Вот кикер:Пользователь не обращает внимания на свое ТЗ.Он вводит дату создания, которую хочет отправить в WS, поэтому мне нужно:
Пользователь вводит: 01.05.2008 18:12 (EST)
Параметр WS должен быть:01.05.2008 18:12 (GMT)
Я знаю, что по умолчанию отметки времени всегда должны быть в формате 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;
}
С предыдущим кодом я получаю вот что (короткий формат для удобства чтения):
[1 мая 2008 г., 23:12]
Решение 2
Спасибо всем за отклик. После дальнейшего расследования я получил правильный ответ. Как упомянул 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()));
Код выводится следующим образом: (Пользователь ввел 01.05.2008 18:12 (EST)
Часовой пояс текущего пользователя: EST
Текущее смещение от времени по Гринвичу (в часах): - 4 (Обычно -5, за исключением случаев, когда установлено летнее время)
TS от ACP: 2008-05-01 14: 12: 00.0
Календарная дата конвертируется из TS с использованием GMT и локали US_EN: 01.05.08 18:12 (GMT)
Другие советы
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]
ОТЛАДКА - смещение -14400000
DEBUG - Создано время по Гринвичу с датой [Чт 23 Октября 08:09:05 ПО ВОСТОЧНОМУ ВРЕМЕНИ 2008]
12:09:05 GMT - 8:09:05 EDT.
Запутанная часть здесь в том, что Calendar.getTime ()
возвращает вам Date
в вашем текущем часовом поясе, а также то, что нет способа изменить часовой пояс календаря и имеют основную дату проката также. В зависимости от того, какой параметр принимает ваш веб-сервис, вы можете просто заключить сделку WS в миллисекундах с начала эпохи.
Вы говорите, что дата используется в связи с веб-сервисами, поэтому я предполагаю, что в какой-то момент она сериализуется в строку.
Если это так, вы должны взглянуть на метод setTimeZone класса 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"));
Похоже, что ваша метка времени настроена на часовой пояс исходной системы.
Это устарело, но должно работать:
cal.setTimeInMillis(ts_.getTime() - ts_.getTimezoneOffset());
Не рекомендуется использовать
Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)
но это необходимо сделать на стороне клиента, так как эта система знает, в каком часовом поясе она находится.
Метод преобразования из одного timeZone в другое (возможно, это работает :)).
/**
* 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;
}
Date и Timestamp не обращают внимания на часовой пояс: они представляют определенное количество секунд с начала эпохи, не принимая особую интерпретацию этого момента как часов и дней , Часовые пояса вводят изображение только в GregorianCalendar (не требуется напрямую для этой задачи) и SimpleDateFormat , которым требуется смещение часового пояса для преобразования между отдельными полями и датой (или длиной ). ) значения.
Проблема ОП заключается в самом начале его обработки: пользователь вводит неоднозначные часы, которые интерпретируются в местном часовом поясе, отличном от GMT; в этот момент значение равно "6: 12 EST" , которое может быть легко напечатано как "11.12 GMT" или в любом другом часовом поясе, но никогда не будет измените на " 6.12 по Гринвичу " .
Нет способа сделать SimpleDateFormat , который анализирует " 06: 12 " как " HH: MM " (по умолчанию равным местный часовой пояс) по умолчанию вместо UTC; SimpleDateFormat слишком умный для своего блага.
Однако вы можете убедить любой экземпляр SimpleDateFormat использовать правильный часовой пояс, если вы явно указали его во входных данных: просто добавьте фиксированную строку к полученному (и надлежащим образом проверенному) " ; 06: 12 " для синтаксического анализа " 06: 12 GMT " как " ЧЧ: ММ z ". Р>
Нет необходимости явно задавать поля GregorianCalendar или извлекать и использовать смещения часового пояса и летнего времени.
Настоящая проблема заключается в разделении входов, которые по умолчанию соответствуют местному часовому поясу, входов, которые по умолчанию имеют значение UTC, и входов, которые действительно требуют явного указания часового пояса.
В прошлом мне удавалось определить смещение (в миллисекундах) между часовым поясом пользователя и GMT. Получив смещение, вы можете просто добавить / вычесть (в зависимости от того, в каком направлении происходит преобразование), чтобы получить подходящее время в любом часовом поясе. Обычно я выполняю это, устанавливая поле в миллисекундах объекта Calendar, но я уверен, что вы легко сможете применить его к объекту временной метки. Вот код, который я использую, чтобы получить смещение
int offset = TimeZone.getTimeZone(timezoneId).getRawOffset();
timezoneId - это идентификатор часового пояса пользователя (например, EST).
java.time
Современный подход использует java.time классы, которые заменили неприятные устаревшие классы даты и времени, включенные в самые ранние версии Java.
А java.sql.Timestamp
class — один из таких устаревших классов.Больше не нужен.Вместо этого используйте Instant
или другие классы java.time непосредственно с вашей базой данных с использованием JDBC 4.2 и более поздних версий.
А Instant
класс представляет момент на временной шкале в универсальное глобальное время с резолюцией наносекунды (до девяти (9) цифр десятичной дроби).
Instant instant = myResultSet.getObject( … , Instant.class ) ;
Если вам необходимо взаимодействовать с существующим Timestamp
, немедленно преобразуйте в java.time с помощью новых методов преобразования, добавленных к старым классам.
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 ) ;
Чтобы сгенерировать строку для отображения пользователю, выполните поиск по запросу «Переполнение стека». DateTimeFormatter
найти множество обсуждений и примеров.
Ваш вопрос на самом деле касается другого направления: от ввода пользовательских данных к объектам даты и времени.Обычно лучше всего разбить ввод данных на две части: дату и время суток.
LocalDate ld = LocalDate.parse( dateInput , DateTimeFormatter.ofPattern( "M/d/uuuu" , Locale.US ) ) ;
LocalTime lt = LocalTime.parse( timeInput , DateTimeFormatter.ofPattern( "H:m a" , 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 Framework встроен в Java 8 и более поздних версий.Эти занятия заменяют надоедливые старые наследие классы даты и времени, такие как java.util.Date
, Calendar
, & SimpleDateFormat
.
А Джода-Время проект, сейчас в Режим обслуживания, советует перейти на java.time занятия.
Чтобы узнать больше, см. Учебное пособие по Oracle.И найдите Stack Overflow для множества примеров и объяснений.Спецификация ДжСР 310.
Где получить классы java.time?
- Ява ЮВ 8, Ява ЮВ 9, и позже
- Встроенный.
- Часть стандартного Java API со встроенной реализацией.
- В Java 9 добавлены некоторые незначительные функции и исправления.
- Ява ЮВ 6 и Ява ЮВ 7
- Большая часть функций java.time перенесена на Java 6 и 7 в ТриДесять-Бэкпорт.
- Андроид
- Более поздние версии реализации пакетов Android классов java.time.
- Для более ранних версий Android ТриTenABP проект адаптируется ТриДесять-Бэкпорт (упомянутое выше).Видеть Как использовать ThreeTenABP….
А ТриДесять-Экстра проект расширяет java.time дополнительными классами.Этот проект является испытательным полигоном для возможных будущих дополнений к java.time.Здесь вы можете найти несколько полезных классов, таких как Interval
, YearWeek
, YearQuarter
, и более.