Как обрабатывать календарные часовые пояса с помощью Java?
У меня есть значение Timestamp, которое приходит из моего приложения. Пользователь может находиться в любом локальном часовом поясе.
Поскольку эта дата используется для WebService, которая предполагает, что указанное время всегда находится в GMT, мне нужно преобразовать параметр пользователя из say (EST) в (GMT). Здесь кикер: Пользователь не обращает внимания на свою TZ. Он вводит дату создания, которую хочет отправить на WS, поэтому мне нужно:
Пользователь вводит: 5/1/2008 6: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 для легкого чтения):
[1 мая 2008 г. 11:12]
Ответы
Ответ 1
Спасибо всем за ответ. После дальнейшего расследования я получил правильный ответ. Как упоминалось 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 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 6:12 PM (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)
Ответ 2
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-сделку в миллисекундах с эпохи.
Ответ 3
Вы говорите, что дата используется в связи с веб-службами, поэтому я предполагаю, что в какой-то момент она сериализуется в строку.
Если это так, вы должны взглянуть на метод 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());
Ответ 4
Вы можете решить его с помощью 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"));
Ответ 5
Похоже, ваш TimeStamp устанавливается в часовой пояс исходной системы.
Это устарело, но оно должно работать:
cal.setTimeInMillis(ts_.getTime() - ts_.getTimezoneOffset());
Не устаревший способ заключается в использовании
Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)
но это нужно сделать на стороне клиента, так как эта система знает, в какой временной зоне он находится.
Ответ 6
Метод преобразования из одного времени в другой (возможно, он работает:)).
/**
* 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;
}
Ответ 7
Объекты Дата и Временная метка не зависят от часового пояса: они представляют собой определенное количество секунд с эпохи, не переходя к определенной интерпретации этого момента в виде часов и дней,
Часовые пояса вводят изображение только в 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 и входам, которые действительно требуют явного указания часового пояса.
Ответ 8
Что-то, что работало для меня в прошлом, заключалось в определении смещения (в миллисекундах) между часовым поясом пользователя и GMT. После того, как у вас есть смещение, вы можете просто добавить/вычесть (в зависимости от того, каким образом происходит преобразование), чтобы получить подходящее время в любом часовом поясе. Обычно я выполнял это, установив поле миллисекунд объекта Calendar, но я уверен, что вы можете легко применить его к объекту timestamp. Здесь код, который я использую для получения смещения
int offset = TimeZone.getTimeZone(timezoneId).getRawOffset();
timezoneId - это идентификатор пользовательского часового пояса (например, EST).
Ответ 9
java.time
В современном подходе используются классы java.time, которые вытеснили неприятные предыдущие классы времени, связанные с самыми ранними версиями Java.
Класс java.sql.Timestamp
является одним из тех устаревших классов. Больше не нужен. Вместо этого используйте Instant
или другие классы java.time непосредственно с вашей базой данных, используя JDBC 4.2 и более поздние версии.
Instant
класс представляет момент на временной шкале в UTC с разрешением наносекунд (до девяти (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 ) ;
Чтобы создать строку для отображения пользователю, выполните поиск Qaru для 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 встроена в Java 8 и более поздние версии. Эти классы вытесняют неприятные старые устаревшие классы времени, такие как java.util.Date
, Calendar
и SimpleDateFormat
.
Проект Joda-Time, теперь в режиме обслуживания, советует перейти на классы java.time.
Чтобы узнать больше, ознакомьтесь с учебным пособием Oracle. И поиск Qaru для многих примеров и объяснений. Спецификация - JSR 310.
Где можно получить классы 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
Проект ThreeTen-Extra расширяет java.time с дополнительными классами. Этот проект является доказательством возможных будущих дополнений к java.time. Здесь вы можете найти полезные классы, такие как Interval
, YearWeek
, YearQuarter
и другие.