2015-01-21 83 views
11

我在我的應用程序中使用JSR 310 DateTime API *,我需要解析和格式化軍用日期時間(稱爲DTG或「日期時間組」)。軍事時區使用JSR 310(DateTime API)

格式我解析看起來像這樣(使用DateTimeFormatter):

"ddHHmm'Z' MMM yy" // (ie. "312359Z DEC 14", for new years eve 2014) 

這種格式是很容易如上所述解析。例如'A'(阿爾法,UTC + 1:00)或'B'(布拉沃,UTC +格林威治標準時間),日期包含與'Z'不同的時區時(Zulu時區,與UTC/GMT相同) 2:00)。完整列表請參見Military time zones

我該如何解析這些時區?或者換句話說,我可以用上面的格式'Z'以外的格式來解析所有區域嗎?我曾嘗試使用"ddHHmmX MMM yy","ddHHmmZ MMM yy""ddHHmmVV MMM yy",但它們都不起作用(在解析時,上述示例均會產生DateTimeParseException: Text '312359A DEC 14' could not be parsed at index 6)。不允許在格式中使用單個V(嘗試實例化DateTimeFormatter時爲IllegalArgumentException)。

編輯:看起來符號z可以工作,如果它不是爲了下面的問題。

我還應該提到,我已經創建了一個ZoneRulesProvider與所有命名的區域和正確的偏移量。我已經驗證了這些使用SPI機制正確註冊,並且我的provideZoneIds()方法按預期調用。仍然不會解析。作爲一個側面問題(編輯:現在似乎是主要問題),API以外的單字符時區id(或「區域」)不允許使用。

例如:

ZoneId alpha = ZoneId.of("A"); // boom 

將拋出DateTimeException: Invalid zone: A(甚至沒有訪問我的規則,供應商,看看它的存在)。

這是API中的疏忽嗎?或者我做錯了什麼?


*)其實,我使用的Java 7和ThreeTen Backport,但我不認爲事項這個問題。

PS:我目前的解決辦法是使用25個不同的DateTimeFormatter s的文字區域ID(即"ddHHmm'A' MMM yy""ddHHmm'B' MMM yy"等),使用RegExp提取區域ID,並委託給基於區域的正確格式。提供商的區域ID被命名爲「Alpha」,「Bravo」等,以允許ZoneId.of(...)找到區域。有用。但它不是很優雅,我希望有更好的解決方案。

+1

您是否嘗試過在格式字符串(即「ddHHmmV MMM yy」'')中爲zone-id使用'V'? AFAIK''Z''只是'Z'字符。 https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html – 2015-01-21 11:10:36

+1

是的,我明白'Z''是一個文字,不會被解析器解釋。 'Z'(無引號)雖然是一個符號(但不起作用)。是的,我嘗試了'V'。儘管...嘗試創建格式化程序時出現'IllegalArgumentException:模式字母計數必須爲2:V',它會給出不同的錯誤。使用'VV',我可以回到解析時的「...無法在索引6解析」異常。不管怎麼說,還是要謝謝你! :-) – haraldK 2015-01-21 11:18:26

+0

@PavelHoral實際上,它似乎是「正確的」符號是'z'(小寫)...這似乎正確解析區域,但當然它爆炸,因爲單字母區域是不允許的..: -P「'...無法解析:無效區域:A'」。我會更新這個問題...... – haraldK 2015-01-21 11:24:02

回答

8

java.time中,ZoneId限制爲2個字符或更多。具有諷刺意味的是,這是爲了保留空間,以便在未來的JDK版本中添加軍事ID,如果證明其需求量很大的話。因此,可悲的是你的提供者將無法工作,並且沒有辦法用這些名字創建你想要的ZoneId實例。

一旦您考慮使用ZoneOffset而不是ZoneId(並且考慮到軍事區域是固定偏移量,這是查看問題的好方法),解析問題就可以解決。

關鍵是方法DateTimeFormatterBuilder.appendText(TemporalField, Map)它允許使用您選擇的文本將數字字段格式化並解析爲文本。而ZoneOffset是一個數字字段(該值是偏移量中的總秒數)。

我這個例子中,我已經設置了Z,AB的映射,但是您需要將它們全部添加。否則,代碼非常簡單,設置一個格式化程序可以打印和解析軍事時間(日期和時間使用OffsetDateTime)。

Map<Long, String> map = ImmutableMap.of(0L, "Z", 3600L, "A", 7200L, "B"); 
DateTimeFormatter f = new DateTimeFormatterBuilder() 
    .appendPattern("HH:mm") 
    .appendText(ChronoField.OFFSET_SECONDS, map) 
    .toFormatter(); 
System.out.println(OffsetTime.now().format(f)); 
System.out.println(OffsetTime.parse("11:30A", f)); 
+0

謝謝!肯定是一個更清潔和更優雅的解決方案!經過測試,並與我的格式非常吻合。 – haraldK 2015-01-22 12:13:40

+0

PS:我目前正在將所有日期/時間內部轉換爲祖魯語,並使用'ZonedDateTime'而不是'OffsetDateTime'。我習慣了喬達,但這是我第一個使用JSR 310的項目。是否有一個很好的理由使用ODT而不是ZDT(我意識到你想在某些時候合併這些類......--)? – haraldK 2015-01-22 12:17:58

+2

ODT更簡單,可以沒有DST問題。它也是標準網絡ISO08601格式。否則,這些課程非常相似。使用最適合你的方法。 – JodaStephen 2015-01-22 15:14:56

3

有關支持區域ID的java.time-package(JSR-310)的行爲如指定 - 請參閱javadoc。相關部分的明確引用(其他ID僅以格式「Z」,「+ hh:mm」,「-hh:mm」或「UTC + hh:mm」等形式考慮爲偏移ID。):

基於區域的ID必須是兩個或多個字符

具有至少兩個字符的要求也在ZoneRegion類的源代碼時區數據的任何負載之前實現開始:

/** 
* Checks that the given string is a legal ZondId name. 
* 
* @param zoneId the time-zone ID, not null 
* @throws DateTimeException if the ID format is invalid 
*/ 
private static void checkName(String zoneId) { 
    int n = zoneId.length(); 
    if (n < 2) { 
     throw new DateTimeException("Invalid ID for region-based ZoneId, invalid format: " + zoneId); 
    } 
    for (int i = 0; i < n; i++) { 
     char c = zoneId.charAt(i); 
     if (c >= 'a' && c <= 'z') continue; 
     if (c >= 'A' && c <= 'Z') continue; 
     if (c == '/' && i != 0) continue; 
     if (c >= '0' && c <= '9' && i != 0) continue; 
     if (c == '~' && i != 0) continue; 
     if (c == '.' && i != 0) continue; 
     if (c == '_' && i != 0) continue; 
     if (c == '+' && i != 0) continue; 
     if (c == '-' && i != 0) continue; 
     throw new DateTimeException("Invalid ID for region-based ZoneId, invalid format: " + zoneId); 
    } 
} 

這就解釋了爲什麼它是不可能的JSR-310/Threeten寫的語句,比如ZoneId.of("A")。字母Z的工作原理是因爲它在ISO-8601中也是如JSR-310中所指定的那樣來表示零點偏移。

您找到的解決方法在JSR-310不支持軍事時區的範圍內很好。因此,您不會找到任何格式支持(只需研究類DateTimeFormatterBuilder - 格式模式符號的每個處理都會路由到構建器)。我得到的唯一含糊的想法是實現代表軍事時區偏移的專用TemporalField。但是實施(如果可能的話)的確定性比您的解決方法更爲複雜。

另一個更合適的解決方法是隻進行字符串預處理。既然你有固定的格式總是期待軍事字母在輸入相同位置的工作,你可以簡單地這樣做:

String input = "312359A Dec 14"; 
String offset = ""; 

switch (input.charAt(6)) { 
    case 'A': 
    offset = "+01:00"; 
    break; 
    case 'B': 
    offset = "+02:00"; 
    break; 
    //... 
    case 'Z': 
    offset = "Z"; 
    break; 
    default: 
    throw new ParseException("Wrong military timezone: " + input, 6); 
} 
input = input.substring(0, 6) + offset + input.substring(7); 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ddHHmmVV MMM yy", Locale.ENGLISH); 
ZonedDateTime odt = ZonedDateTime.parse(input, formatter); 
System.out.println(odt); 
// output: 2014-12-31T23:59+01:00 

注:

  • 我曾用「月」,而不是「DEC」,否則解析器會報錯。如果你的輸入真的有大寫字母,那麼你可以使用構建器方法parseCaseInsensitive()

  • 使用領域OffsetSeconds對方回答是更好的答案至於解析問題,也得到了我給予好評(忽略了此功能)。這並不是更好的,因爲它將用戶的負擔定義爲從軍事區域字母到偏移量的映射 - 就像我對字符串預處理的建議一樣。 但它更好,因爲它可以使用構建器方法optionalStart()optionalEnd(),因此可以處理可選的時區字母A,B ...。另請參閱OP關於可選區域ID的評論。

+0

感謝您的確認,我認爲我會將其標記爲正確的答案,因爲根據當前的規範/實現(我已經在threetenbp項目中提出問題,等待反饋),我想要做的事似乎是不可能的。 – haraldK 2015-01-21 18:11:48

+0

Re。字符串預處理和區分大小寫,是的,我知道這一點。我用於解析的實際格式比我所示的更爲複雜。區域id是可選的(如果省略,則假定爲Z),並且空格也是可選的(這意味着'charAt(6)'可能是4月份的A ...)。我會堅持我的基於正則表達式的解決方案。我會考慮使用正則表達式替換,但它可能工作!而我所做的'ZoneRulesProvider'可能是多餘的,我可以直接使用'ZoneOffset'。無論如何,謝謝你一個好的和徹底的答案! :-) – haraldK 2015-01-21 18:16:32