Java: установка часового пояса в приложении OSGi
Мое встроенное Java-приложение, работающее в Linux, должно позволять пользователю изменять часовой пояс через графический интерфейс. Мое приложение работает в контейнере OSGI (см. Ниже, почему я считаю, что это актуально), и его не нужно перезагружать перед использованием нового часового пояса.
Каков рекомендуемый способ постоянной установки часового пояса в моем приложении Java/OSGi?
Я могу думать о следующих подходах, для которых я перечисляю некоторые плюсы и минусы. Я что-то пропустил? Что рекомендуется?:
- Из приложения измените часовой пояс ОС, дополнительно используйте
TimeZone.setDefault(...)
для текущей запущенной JVM и обновите всеClock
экземпляры, которые содержат старый TZ (поэтому необходимо какое-то событие). Против: этот метод зависит от ОС и довольно низкий уровень, также я хотел бы сохранить часы ОС UTC. Плюсы: ОС заботится о сохранении TZ, TZ сразу корректируется при следующем запуске приложения. - Из приложения измените
-Duser.timezone=...
параметр, используемый для запуска. Против: очень некрасиво, даже более низкий уровень, но позволяет оставить часы ОС в UTC, при этом приложение запускается с правильной TZ. Также необходимо обновитьClock
случаи на изменение. - Не трогай ОС, а используй только
TimeZone.setDefault(...)
и назовите это рано при запуске. Для этого потребуется отдельное сохранение (предпочтения), чтобы сохранить его. Здесь также, в настоящее время работает JVM, всеClock
экземпляры, которые ссылаются на старый TZ, должны быть обновлены после изменения (необходимо событие). При запуске в контейнере OSGi порядок запуска пакетов не гарантируется, поэтому я не могу быть уверен, что TZ по умолчанию устанавливается перед его использованием. Как я могу гарантировать это? Кроме того, JSR310 явно рекомендует не использовать " TZ по умолчанию" в Clock. - Не используйте TimeZone "по умолчанию" вообще, используйте отдельную глобальную переменную и при каждом преобразовании между
Instant
а такжеLocalXXX
значения, передать часовой пояс явно. Это избавляет от необходимости события для обновленияClock
пример. Но мы должны обратить внимание, чтобы не использоватьLocalDate.now(clock)
, поскольку это использует TZ часов (который тогда больше не корректен). Как иметь эту глобальную переменную в OSGi? с помощьюConfigAdmin
? Как заставить код вести себя правильно, что я не могу контролировать (например, регистрация временных отметок)?
Изменить: чтобы избавиться от необходимости обновления Clock
Я мог бы использовать часы, которые всегда проверяют TimeZone по умолчанию, но это кажется неоптимальным из производительности POV:
public class DefaultZoneClock extends Clock {
private final Clock ref = Clock.systemUTC();
@Override
public ZoneId getZone() {
return ZoneId.systemDefault(); // probed on each request
}
@Override
public Clock withZone(ZoneId zone) {
return ref.withZone(zone);
}
@Override
public Instant instant() {
return ref.instant();
}
}
Это хорошая идея?
Изменить 2:
О моих проблемах производительности выше: они явно не оправданы. Когда вы звоните LocalDate.now()
внутренне новый SytemClock
построен, который устанавливает текущий ZoneID
ища его на карте - это точно так же, как использование моего DefaultZoneClock
выше, разница в том, что используя мой код, я могу ввести любой другой Clock
для тестирования. (весь код клиента будет использовать LocalDate.now(clock)
)
Ответы ниже предлагают не изменять JVM TimeZone, а выполнять преобразование всякий раз, когда это необходимо на основе пользовательской TimeZone, это означает, что я должен позаботиться о том, чтобы не использовать методы java.time, которые вызывают TimeZone из Clock
например если мне нужно LocaTime
использование
// OK, TimeZone is set explicitely from user data
LocalTime t = clock.instant().atZone(myUserZoneID).toLocalTime();
// Not OK, uses the Clock's internal TimeZone which may not have been set or updated
LocalTime t2 = LocalTime.now(clock);
2 ответа
По моему опыту, даты / время всегда должны быть представлены внутри, используя UTC. Только когда-либо конвертировать в местное время, когда показывает его пользователю. На этом этапе у вас также есть требование отформатировать его в соответствии с локалью пользователя.
Возникает вопрос: откуда вы знаете часовой пояс и локаль пользователя? Это зависит от характера приложения. Если это однопользовательское приложение для настольного компьютера, то вам следует либо просмотреть их в ОС, либо использовать конфигурацию, сохраненную в службе Config Admin. Если это многопользовательское веб-приложение, то вы, вероятно, будете иметь некоторую информацию о пользователе в веб-сессии; или используйте заголовок Accept-Language или даже географическое местоположение.
ОБНОВИТЬ
Постер пояснил, что приложение является однопользовательским на встроенном устройстве. В этом случае не проще ли запрашивать текущий системный часовой пояс каждый раз, когда требуется отобразить время? Это позволяет избежать неприятных глобальных переменных, а также позволяет вашему приложению динамически реагировать на изменения в часовом поясе, например, если пользователь переносит устройство из одного места в другое.
Я не верю, что ты должен позвонить TimeZone.setDefault
из вашего Java-приложения, потому что, откуда вы собираетесь получать эту информацию? Возможно, прямой вход пользователя, но не предпочтет ли пользователь установить его в ОС, чтобы все приложения могли его получить?
Вы, кажется, делают гору из кротовой норы, прилагают все усилия, чтобы сделать простую задачу в сложный.
Относитесь к значениям даты и времени как к локализованному тексту
Локализация даты и времени - это проблема локализации. Подходите так, как если бы некоторые пользователи говорили по-французски, по-итальянски и по-немецки.
Выполняйте всю свою внутреннюю работу, такую как поиск и значения ключей, на одном языке, скажем, английском, даже если никто из ваших пользователей не говорит по-английски.
Для даты и времени эквивалент хранит внутренние значения в часовом поясе UTC. Ваша бизнес-логика, хранилище файлов и записи базы данных должны использовать UTC. Если важно знать исходный часовой пояс и / или исходный ввод, сохраните его как дополнительную часть информации в дополнение к скорректированному по UTC значению.
Когда приходит время показывать пользователю или экспортировать данные для пользователя, вы используете эти английские строки и ключи для поиска французского, итальянского или немецкого перевода. На каком языке? Три возможности:
- Язык по умолчанию для вычислительной среды пользователя (по умолчанию JVM, заголовки http, запрос JavaScript и т. Д.)
- Явно выбранный язык пользователя (наше приложение спросило пользователя)
- Язык, соответствующий контексту данных.
Для первых двух где-то вы как-то ведете профиль для каждого пользователя. В веб-приложении мы используем объект сеанса. В однопользовательском "настольном" приложении мы используем Singleton. Для выбранного пользователем языка сохраните его в базе данных или файловом хранилище, чтобы запомнить его для будущих рабочих сеансов.
То же самое касается даты и времени. При представлении или экспорте данных, если контекст не диктует часовой пояс, тогда определите их текущий часовой пояс из их вычислительной среды. Если часовой пояс критичен или пользователи очень мобильны и пересекают часовые пояса, спросите пользователя о выборе часового пояса. Предоставьте список правильных названий часовых поясов. Запомните этот выбор для будущих рабочих сессий этого пользователя.
Укажите часовой пояс для объектов даты и времени
И библиотека Joda-Time, и пакет java.time в Java 8 упрощают настройку часового пояса объекта даты-времени в и из UTC. Вам следует изменить часовой пояс здесь, на объектах значений данных, а не изменять настройки JVM по умолчанию или языковые настройки ОС.
Вход в UTC
Что касается регистрации и других системных вопросов, то это должно быть сделано в UTC. Позже вы всегда можете позже преобразовать это значение UTC в местный часовой пояс при исследовании проблемы. Или включите локализованную дату и время пользователя в сообщение вашего события регистрации, если это уместно.
Не шутите с Clock
Я бы не связывался с java.time Clock
при развертывании в производстве. Основным намерением для этого класса является тестирование. Вы можете подключить фиктивные часы, чтобы показывать текущую дату и время для создания сценариев тестирования. Вы можете подключить часы к работе, но для меня гораздо больше смысла указывать нужный часовой пояс в ваших объектах даты и времени.
пример
Например, скажем, пользователь хочет, чтобы будильник сработал в 7:30 утра. Вы либо спрашиваете пользователя, либо иным образом определяете его желаемый часовой пояс в Монреале. Вот нереалистичный код в Joda-Time (java.time будет похожим).
DateTimeZone zone = DateTimeZone.forID( "America/Montreal" );
DateTime alarmMontréal = DateTime.now( zone ).plusDays( 1 ).withTime( 7 , 30 , 0 , 0 );
DateTime alarmUtc = alarmMontréal.withZone( DateTimeZone.UTC );
Мы показываем alarmMontréal
пользователю, но хранить alarmUtc
в базе данных. Оба представляют один и тот же момент на временной шкале истории Вселенной.
Скажем, пользователь летит в Портленд, штат Орегон, США. Если мы хотим, чтобы сигнализация сработала в тот же момент без изменений, никаких изменений не требуется. Чтобы отобразить, когда этот будильник сработает во время Портленда, мы создадим новый неизменный объект, адаптирующийся к еще одному часовому поясу.
DateTime alarmPortland = alarmUtc.withZone( DateTimeZone.forID( "America/Los_Angeles" ) ); // 3 hours behind Montréal, 04:30:00.000.
Если мы собираемся позвонить нашему дяде в Индии в то время, мы отправим ему электронное письмо с подтверждением назначения, используя результат этого кода:
DateTime alarmKolkata = alarmUtc.withZone( DateTimeZone.forID( "Asia/Kolkata" ) );
У нас есть четыре объекта DateTime, каждый из которых представляет момент на временной шкале Вселенной:
zone America/Montreal
alarmMontréal 2015-01-14T07:30:00.000-05:00
alarmUtc 2015-01-14T12:30:00.000Z
alarmPortland 2015-01-14T04:30:00.000-08:00
alarmKolkata 2015-01-14T18:00:00.000+05:30
Местное время
Если бы в другом сценарии вы хотели 7:30 в любом часовом поясе, в котором находился пользователь, вы бы использовали LocalTime
, что является просто идеей семь тридцать утра в любом часовом поясе. LocalTime
не привязан к временной шкале истории Вселенной.
И Joda-Time, и java.time предлагают класс LocalTime. Поиск Stackru для многих примеров и обсуждений.
Вот чрезмерно упрощенный пример использования LocalTime, если бизнес-правило гласит: "Подайте сигнал тревоги, если текущее время наступает после 07:30:00.000 в любом месте, где пользователь / компьютер / устройство обнаруживает себя сейчас":
Boolean alarmFired = Boolean.FALSE;
LocalTime alarm = new LocalTime( 7 , 30 );
// …
DateTimeZone zoneDefault = DateTimeZone.getDefault(); // Use the computer’s/device’s JVM’s current default time zone. May change at any moment.
if ( LocalTime.now( zoneDefault ).isAfter( alarm ) ) {
DateTime whenAlarmFired = DateTime.now( DateTimeZone.UTC );
alarmFired = Boolean.TRUE;
// fire the alarm.
}
Используйте UTC для истории
Обратите внимание, что если мы отслеживаем историю возникновения тревоги, мы должны сделать это в UTC как DateTime
, Мы также можем узнать, по какому местному времени сработала эта сигнализация. Если мы знаем или записываем часовой пояс, мы всегда можем воссоздать его. Или мы можем также выбрать запись текущей местной даты и времени. Но эта локальная ценность является вторичной информацией. Первичное отслеживание должно быть по UTC.
Явно укажите часовой пояс
Обратите внимание, как мы явно указываем часовой пояс по умолчанию. Было бы короче использовать LocalTime.now()
поскольку это действительно использует текущий часовой пояс JVM по умолчанию.
Мы говорим о разнице между этим...
LocalTime now = LocalTime.now(); // Implicit reliance on JVM’s current default time zone.
…и это…
LocalTime now = LocalTime.now( DateTimeZone.getDefault() ); // Explicitly asking for JVM’s current default time zone.
Неявная зависимость от часового пояса по умолчанию - плохая практика. С одной стороны, такая уверенность побуждает программиста думать в местной системе дат-времени, когда она вместо этого должна выработать привычку думать в UTC. Другая проблема заключается в том, что неявная зависимость от часового пояса по умолчанию делает ваш код неоднозначным в том смысле, что читатель не уверен, что вы хотите использовать значение по умолчанию или просто не знаете лучше (что слишком часто является причиной проблем в настоящее время). время обработки).