军事时区使用JSR 310(DateTime API)

时间:2015-01-21 10:36:22

标签: java datetime timezone java-time

我在我的应用程序中使用JSR 310 DateTime API *,我需要解析和格式化军事日期时间(称为DTG或"日期时间组")。

我解析的格式如下(使用DateTimeFormatter):

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

如上所述,这种格式很容易解析。当日期包含与' Z'不同的时区时,会出现问题。 (祖鲁时区,与UTC / GMT相同),例如' A' (Alpha,UTC + 1:00)或' B' (Bravo,UTC + 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时,不允许使用格式中的单个IllegalArgumentExceptionDateTimeFormatter。)

编辑:如果以下问题不符合,则符号z似乎有效。

我还应该提一下,我创建了一个ZoneRulesProvider,其中包含所有命名区域和正确的偏移量。我已经验证使用SPI机制正确注册了这些,并且按预期调用了我的provideZoneIds()方法。仍然不会解析。作为一个侧面问题(编辑:这现在似乎是主要问题),单个字符时区ID(或"区域")除了' Z' API不允许这样做。

例如:

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

将抛出DateTimeException: Invalid zone: A(甚至不访问我的规则提供程序以查看它是否存在)。

这是API的疏忽吗?或者我做错了什么?


*)实际上,我使用的是Java 7和ThreeTen Backport,但我认为这对于这个问题并不重要。

PS:我目前的解决方法是使用带有文字区域ID的25个不同DateTimeFormatter(即。"ddHHmm'A' MMM yy""ddHHmm'B' MMM yy"等),使用RegExp进行提取区域ID,并根据区域委派给正确的格式化程序。提供商中的区域ID命名为" Alpha"," Bravo"等,以允许ZoneId.of(...)查找区域。有用。但它不是很优雅,我希望有更好的解决方案。

2 个答案:

答案 0 :(得分:8)

java.time中,ZoneId限制为2个字符或更多。具有讽刺意味的是,这是为了保留空间,以便在未来的JDK版本中添加军事ID,如果它被证明需求量很大的话。因此,遗憾的是,您的提供程序无法正常工作,并且无法使用这些名称创建所需的ZoneId实例。

一旦您考虑使用ZoneOffset而不是ZoneId(并且考虑到军事区域是固定偏移,这是查看问题的好方法),解析问题就可以解决了。

关键是方法DateTimeFormatterBuilder.appendText(TemporalField, Map),它允许使用您选择的文本格式化数字字段并将其解析为文本。 ZoneOffset是一个数字字段(值是偏移量中的总秒数)。

我在此示例中,我已为ZAB设置了映射,但您需要将它们全部添加。否则,代码非常简单,设置一个可以打印和解析军事时间的格式化程序(使用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));

答案 1 :(得分:3)

java.time-package(JSR-310)关于区域ID支持的行为如指定 - 请参阅javadoc。相关部分的明确引用(其他ID仅被视为偏移 - ID格式&#34; Z&#34;,&#34; + hh:mm&#34;,&#34; -hh:mm&#34;或&#34; UTC + hh:mm&#34;等等):

  

基于地区的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的范围内很好,它不支持军事时区。因此您将找不到任何格式支持(只需研究课程{{1} } - 格式模式符号的每个处理都被路由到构建器)。我唯一模糊的想法是实现一个代表军事时区偏移的专门DateTimeFormatterBuilder。但实施是(如果可能的话)确定性比你的解决方案更复杂。

另一个更合适的解决方法是字符串预处理。由于您使用固定格式,期望军事信函始终位于输入中的相同位置,您可以简单地执行此操作:

TemporalField

注意:

  • 我用过&#34; Dec&#34;而不是&#34; DEC&#34;否则解析器会抱怨。如果您的输入确实有大写字母,那么您可以使用构建器方法parseCaseInsensitive()

  • 使用字段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 的另一个答案是关于解析问题的更好答案,并且还得到了我的upvote(忽略了此功能)。它并不是更好,因为它给用户带来了负担来定义从军事区域字母到偏移的映射 - 就像我的字符串预处理建议一样。 但它更好,因为它可以使用构建器方法OffsetSecondsoptionalStart(),因此可以处理可选的时区字母A,B,... 另请参阅注释关于可选区域ID的OP。