[英]Java: Setting the timezone from within an OSGi application
我在Linux上運行的嵌入式Java應用程序應該允許用戶通過GUI更改時區。 我的應用程序在OSGI容器中運行(請參閱下面我認為這是相關的原因),並且在使用新時區之前不需要重新啟動。
從我的Java / OSGi應用程序持久設置時區的推薦方法是什么?
我可以想到以下方法,我列出了一些優點和缺點。 我錯過了什么嗎? 什么推薦?:
TimeZone.setDefault(...)
並更新包含舊TZ的所有Clock
實例(因此需要某種事件)。 Con:這種方法取決於操作系統,而且級別很低,我也希望保持OS時鍾UTC。 優點:操作系統負責存儲TZ,TZ在下次啟動應用程序時立即正確。 -Duser.timezone=...
參數。 Con:非常難看,甚至更低級別,但允許以UTC為單位留下OS時鍾,同時讓應用程序以正確的TZ開始。 還需要在更改時更新Clock
實例。 TimeZone.setDefault(...)
並在啟動時盡早調用它。 這將需要一個單獨的持久性(首選項)來保存它。 此外,在當前運行的JVM中,所有引用舊TZ的Clock
實例都需要在更改時更新(需要事件)。 在OSGi容器中運行時,無法保證bundle的啟動順序,因此我無法確定默認TZ在使用之前是否已設置。 我怎么能保證這個? 此外,JSR310明確建議不要在Clock中使用“默認TZ”。 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
,它通過在Map中搜索來設置當前的ZoneID
- 這與上面使用我的DefaultZoneClock
,不同之處在於使用我的代碼我可以注入任何其他Clock
測試。 (所有客戶端代碼都將使用LocalDate.now(clock)
)
下面的答案建議不要更改JVM TimeZone,而是根據用戶定義的TimeZone進行轉換,這意味着我必須注意不要使用從Clock
調用TimeZone的java.time方法,例如。 如果我需要使用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);
根據我的經驗,日期/時間應始終使用UTC在內部表示。 只有在向用戶顯示時才轉換為本地時間。 此時,您還需要根據用戶的語言環境對其進行格式化。
所以問題就變成了,您如何知道用戶的時區和區域設置? 這取決於應用程序的性質。 如果它是單用戶桌面應用程序,那么您應該在操作系統中查看這些應用程序,或者使用通過Config Admin服務存儲的配置。 如果它是一個多用戶Web應用程序,那么您可能會在Web會話中獲得一些與用戶相關的信息; 或使用Accept-Language標頭甚至地理位置。
UPDATE
海報澄清該應用程序是嵌入式設備上的單用戶。 在這種情況下,每次需要顯示時間時,查詢當前系統時區是不是更容易? 這可以避免令人討厭的全局變量,並且它還允許您的應用程序動態響應時區中的更改,例如,如果用戶將設備從一個地方運送到另一個地方。
我不相信你應該從你的Java應用程序調用TimeZone.setDefault
,因為你將從哪里獲取這些信息? 也許直接用戶輸入,但用戶不願意在操作系統中設置它,以便所有應用程序都可以獲得它?
你似乎是在一個小山丘上建造一座山,努力將一個簡單的問題變成一個復雜的問題。
本地化日期時間是一個本地化問題。 如果某些用戶說法語,一些意大利語和一些德語,就會采用類似於您的方式進行處理。
即使您的所有用戶都不會說英語,也要用一種語言(例如英語)完成所有內部工作,例如查找和鍵值。
對於日期時間,等效值是將內部值保留在UTC時區中。 您的業務邏輯,文件存儲和數據庫記錄都應使用UTC。 如果知道原始時區和/或原始輸入是至關重要的,那么除了UTC調整后的值之外,還要將其存儲為附加信息。
當需要向用戶顯示或導出用戶數據時,您可以使用這些英語字符串和鍵來查找法語或意大利語或德語翻譯。 哪種語言? 三種可能性:
對於前兩個,某處你以某種方式為每個用戶保留一個配置文件。 在Web應用程序中,我們使用會話對象。 在單用戶“桌面”應用中,我們使用Singleton。 對於用戶選擇的語言,請持久保存到數據庫或文件存儲,以便記住此用戶將來的工作會話。
日期時間也一樣。 在呈現或導出數據時,如果上下文沒有指定時區,則從其計算環境中檢測其當前時區。 如果時區很關鍵,或者用戶具有高度移動性和穿越時區,請詢問用戶他們選擇的時區。 提供正確的時區名稱列表。 請記住此用戶未來工作會話的此選擇。
Joda-Time庫和Java 8中的java.time包都可以輕松調整日期時間對象與UTC之間的指定時區。 您應該在此處更改數據值對象上的時區,而不是更改JVM的默認設置或操作系統區域設置。
至於日志記錄和其他系統問題,應該用UTC完成。 稍后您可以在調查問題時將該UTC值轉換為本地時區。 或者,如果相關,請在日志記錄事件的消息中包含用戶的本地化日期時間。
Clock
混淆 在生產中部署時,我不會弄亂java.time Clock
。 本課程的主要目的是測試。 你可以插入一個虛假的時鍾來說謊當前的日期時間來創建測試場景。 你可以在生產中插入一個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
與Universe歷史的時間軸無關。
Joda-Time和java.time都提供了LocalTime類。 搜索StackOverflow以獲取許多示例和討論。
如果業務規則是“如果當前時間是在07:30:00000之后,在用戶/計算機/設備恰好發現自己的任何地方,則觸發警報”,這是使用LocalTime的過於簡單的示例:
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中將其作為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思考的習慣。 另一個問題是隱含地依賴於默認時區會使您的代碼模糊不清,因為讀者不確定您是否打算使用默認值或者只是不知道更好(這通常是導致日期出現問題的原因) - 時間處理)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.