簡體   English   中英

Java:在OSGi應用程序中設置時區

[英]Java: Setting the timezone from within an OSGi application

我在Linux上運行的嵌入式Java應用程序應該允許用戶通過GUI更改時區。 我的應用程序在OSGI容器中運行(請參閱下面我認為這是相關的原因),並且在使用新時區之前不需要重新啟動。

從我的Java / OSGi應用程序持久設置時區的推薦方法是什么?

我可以想到以下方法,我列出了一些優點和缺點。 我錯過了什么嗎? 什么推薦?:

  1. 從應用程序,更改底層操作系統時區,另外對當前運行的JVM使用TimeZone.setDefault(...)並更新包含舊TZ的所有Clock實例(因此需要某種事件)。 Con:這種方法取決於操作系統,而且級別很低,我也希望保持OS時鍾UTC。 優點:操作系統負責存儲TZ,TZ在下次啟動應用程序時立即正確。
  2. 從應用程序中,更改用於啟動的-Duser.timezone=...參數。 Con:非常難看,甚至更低級別,但允許以UTC為單位留下OS時鍾,同時讓應用程序以正確的TZ開始。 還需要在更改時更新Clock實例。
  3. 不要觸摸操作系統,只使用TimeZone.setDefault(...)並在啟動時盡早調用它。 這將需要一個單獨的持久性(首選項)來保存它。 此外,在當前運行的JVM中,所有引用舊TZ的Clock實例都需要在更改時更新(需要事件)。 在OSGi容器中運行時,無法保證bundle的啟動順序,因此我無法確定默認TZ在使用之前是否已設置。 我怎么能保證這個? 此外,JSR310明確建議不要在Clock中使用“默認TZ”。
  4. 根本不使用“默認”TimeZone,使用單獨的全局變量,並在InstantLocalXXX值之間的每次轉換時,顯式傳遞時區。 這擺脫了需要更新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調整后的值之外,還要將其存儲為附加信息。

當需要向用戶顯示或導出用戶數據時,您可以使用這些英語字符串和鍵來查找法語或意大利語或德語翻譯。 哪種語言? 三種可能性:

  • 用戶的計算環境的默認語言(JVM默認,http標頭,JavaScript查詢,等等)
  • 用戶明確選擇的語言(我們的應用程序詢問用戶)
  • 適合數據上下文的語言。

對於前兩個,某處你以某種方式為每個用戶保留一個配置文件。 在Web應用程序中,我們使用會話對象。 在單用戶“桌面”應用中,我們使用Singleton。 對於用戶選擇的語言,請持久保存到數據庫或文件存儲,以便記住此用戶將來的工作會話。

日期時間也一樣。 在呈現或導出數據時,如果上下文沒有指定時區,則從其計算環境中檢測其當前時區。 如果時區很關鍵,或者用戶具有高度移動性和穿越時區,請詢問用戶他們選擇的時區。 提供正確的時區名稱列表。 請記住此用戶未來工作會話的此選擇。

在日期 - 時間對象上指定時區

Joda-Time庫和Java 8中的java.time包都可以輕松調整日期時間對象與UTC之間的指定時區。 您應該在此處更改數據值對象上的時區,而不是更改JVM的默認設置或操作系統區域設置。

登錄UTC

至於日志記錄和其他系統問題,應該用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歷史記錄

請注意,如果我們正在跟蹤警報觸發的歷史記錄,我們應該在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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM