[英]How to handle daylight saving time properly in Java 7 without Joda Time?
[英]How To Handle Time Ambiguity Caused by Daylight Saving Time Clock Rollback
我有這個場景,我想看看是否有更好的解決方案:
我從系統 A 收到一個時間戳,格式為“yyyy-MM-dd-HH-mm-ss”,沒有時區,但他們可以將其修復為 UTC。
然后我需要通過 SSH 連接到系統 B 以獲取“上次同步時間”,這可能是過去的時間,並且它不返回任何時區,甚至不返回年份(來自備份產品),這是一個示例:
“同步時間:11 月 3 日星期日 01:13”
系統 B 是一個運行一些專有軟件的備份設備,並且有一個內部調度程序,它每 15 分鍾喚醒一次以檢查是否有任何需要從源同步的數據。 如果前一個同步仍在運行,它將讓當前同步完成而不采取進一步行動。 這個“上次同步時間”將在每次同步后更新,因此在最壞的情況下,它不應落后於當前系統時間超過幾個小時。
我需要比較這兩個時間戳以確保 B 上的“上次同步時間”晚於 A。如果不是,我需要等待並重試查詢,直到 B 晚於 A 才能繼續下一步。
我將 Java 程序放在 Linux 機器上,當請求來自 A 時,它向 B 查詢“上次同步時間”,然后立即向 B 發出“日期”命令以獲取當前系統時間,然后返回格式為“EEE MMM dd kk:mm:ss z yyyy”的當前系統時間,例如,
“2019 年 6 月 14 日星期五 21:07:07 EDT”,
因此程序可以應用該輸出的年份和時區來完成“上次同步時間”的時間戳。 該程序確實處理了“上次同步時間”和“日期”輸出跨越一年邊界的情況。
它工作正常,除非夏季夏令時結束,並且時鍾回滾(凌晨 2 點回到凌晨 1 點),有一個一小時的窗口時間會不明確:
例如,我從系統 A 獲得了一個凌晨 1:20 的時間映射(已經從 UTC 轉換為本地),然后當我從系統 B 獲得一個凌晨 1:30 的時間映射時,我無法判斷 B 是否晚於 A。它可能是 1 :30 在時鍾改變之前。 系統 B 的當前時區並沒有說明“上次同步時間”在哪個時區。
我們要求用戶將所有系統切換到UTC時間,因此我們無需擔心時鍾調整。 但這對環境的影響太大了。
另一種選擇是我專門處理一小時,然后等到“上次同步時間”時間戳超過凌晨 2 點。 為此,我需要在每次進行時間戳比較時檢查代碼是否為特定小時,我認為這不是最佳的。 但我想不出更好的辦法。
任何建議都非常感謝。 如果這是要走的路,是否有一個很好的 Java 庫來確定夏令時切換日期? 太感謝了!
我看不出你能完全確定。 “11 月 3 日”可能是 2002 年、2013 年或 2019 年,甚至更早的歷史。 一種方法是,如果您可以假設上次同步時間不超過您獲得的當前系統 B 系統時間之前的幾年(也不在該時間之后),並且如果您可以檢測到則報告錯誤這不是在這幾年內。
夏令時(夏令時)結束的訣竅是如果有任何歧義,總是假設更早的時間。 這樣,當您檢測到同步時間晚於來自 A 的時間戳時,無論如何解釋不明確的同步時間都是如此。 這也意味着您將按照您的建議等到最后一次同步時間在凌晨 2 點之后。 我不認為這在比較方面會非常昂貴,但如果您需要確定,您需要自己進行測量和判斷。
final DateTimeFormatter timestampFormatter
= DateTimeFormatter.ofPattern("uuuu-MM-dd-HH-mm-ss");
final ZoneId systemBTimeZone = ZoneId.of("America/New_York");
final String syncFormatPattern = "'Sync''ed-as-of time:' EEE MMM d HH:mm";
final DateTimeFormatter currentSystemBTiemFormatter = new DateTimeFormatterBuilder()
.appendPattern("EEE MMM dd HH:mm:ss ")
.appendZoneText(TextStyle.SHORT, Collections.singleton(systemBTimeZone))
.appendPattern(" yyyy")
.toFormatter(Locale.US);
final Period maxSyncAge = Period.ofYears(2);
String systemATimestampString = "2019-11-03-06-05-55";
String lastSyncMsg = "Sync'ed-as-of time: Sun Nov 3 01:13";
String currentSystemBTime = "Sun Nov 03 01:13:07 EDT 2019";
OffsetDateTime systemATimestamp = LocalDateTime.parse(systemATimestampString, timestampFormatter)
.atOffset(ZoneOffset.UTC);
ZonedDateTime maxLastSyncTime
= ZonedDateTime.parse(currentSystemBTime, currentSystemBTiemFormatter);
ZonedDateTime minLatSyncTime = maxLastSyncTime.minus(maxSyncAge);
int candidateYear = maxLastSyncTime.getYear();
ZonedDateTime syncTime;
while (true) {
DateTimeFormatter syncFormatter = new DateTimeFormatterBuilder()
.appendPattern(syncFormatPattern)
.parseDefaulting(ChronoField.YEAR, candidateYear)
.toFormatter(Locale.US);
try {
syncTime = LocalDateTime.parse(lastSyncMsg, syncFormatter)
.atZone(systemBTimeZone)
.withEarlierOffsetAtOverlap();
if (syncTime.isBefore(minLatSyncTime) || syncTime.isAfter(maxLastSyncTime)) {
throw new IllegalStateException("Last sync time is out of range");
}
break;
} catch (DateTimeParseException dtpe) {
// Ignore; try next earlier year
}
candidateYear--;
}
System.out.println("Last sync time: " + syncTime);
System.out.println("Timestamp from system A: " + systemATimestamp);
System.out.println("Is OK? " + syncTime.toOffsetDateTime().isAfter(systemATimestamp));
正如代碼片段所示,它的輸出是:
Last sync time: 2019-11-03T01:13-04:00[America/New_York] Timestamp from system A: 2019-11-03T06:05:55Z Is OK? false
可以看到它選擇將最后一次同步時間01:13解釋為夏令時,偏移量為-04:00,導致校驗失敗。 時間也可能是標准時間,偏移量為 -05:00,在這種情況下,UTC 等效值為 06:13,並且檢查會成功。 但正如所討論的,為了安全起見,我們更願意等到同步時間在 02:00 之后再次變得明確。
while 循環繼續解析字符串,假設不同的年份,直到遇到星期幾匹配的年份。
在 Matt Johnson 的評論中得到了這個,我認為這是最好的解決方案:
現代 Java 代碼應該使用 java.time 包,它從 Java 8 開始就內置了。 ZoneRules.getValidOffsets 將告訴您應用於特定時區中給定 LocalDateTime 的偏移量。 如果返回了多個偏移量,則您處於不明確的時期。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.