Java 使用JSR 310(日期时间API)的军事时区

Java 使用JSR 310(日期时间API)的军事时区,java,datetime,timezone,java-time,Java,Datetime,Timezone,Java Time,我在应用程序中使用JSR 310 DateTime API*,我需要解析和格式化军用日期时间(称为DTG或“日期时间组”) 我正在解析的格式如下所示(使用DateTimeFormatter): 如上所述,这种格式很容易解析。当日期包含与“Z”(祖鲁时区,与UTC/GMT相同)不同的时区时,会出现问题,例如“a”(阿尔法,UTC+1:00)或“B”(布拉沃,UTC+2:00)。有关完整列表,请参阅 如何解析这些时区?或者换句话说,除了文字“Z”之外,我可以在上面的格式中添加什么来正确解析所有区域?

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

我正在解析的格式如下所示(使用
DateTimeFormatter
):

如上所述,这种格式很容易解析。当日期包含与“Z”(祖鲁时区,与UTC/GMT相同)不同的时区时,会出现问题,例如“a”(阿尔法,UTC+1:00)或“B”(布拉沃,UTC+2:00)。有关完整列表,请参阅

如何解析这些时区?或者换句话说,除了文字“Z”之外,我可以在上面的格式中添加什么来正确解析所有区域?我试过使用
“ddHHmmX MMM yy”
“ddHHmmZ MMM yy”
“ddHHmmVV MMM yy”
,但都不起作用(解析时,所有这些都会抛出
DateTimeParseException:Text'312359A DEC 14'无法在索引6处解析(对于上面的示例,解析时)。不允许在格式中使用单个
V
IllegalArgumentException
尝试实例化
DateTimeFormatter
时)

编辑:如果不是因为下面的问题,符号
z
似乎可以工作

我还应该提到,我已经创建了一个带有所有命名区域和正确偏移量的
zonerulessprovider
。我已经使用SPI机制验证了这些已正确注册,并且按照预期调用了我的
provideZoneIds()
方法。仍然无法解析。作为一个次要问题(编辑:这现在似乎是主要问题),API不允许使用“Z”以外的单字符时区ID(或“区域”)

例如:

ZoneId alpha = ZoneId.of("A"); // boom
将抛出
DateTimeException:Invalid zone:A
(甚至不访问我的规则提供程序查看它是否存在)

这是API中的疏忽吗?还是我做错了什么


*)事实上,我正在使用Java7和,但我认为这对这个问题并不重要


PS:我目前的解决方法是使用25个不同的
DateTimeFormatter
s和文字区域id(即
“ddHHmm'A'MMM yy”
“ddHHmm'B'MMM yy”
,等等),使用
RegExp
提取区域id,并根据区域委托给正确的格式化程序。提供程序中的区域ID被命名为“Alpha”、“Bravo”等,以允许(…)的分区ID查找区域。它起作用了。但是它不是很优雅,我希望有一个更好的解决方案。

java.time-package(JSR-310)在支持区域ID方面的行为是指定的-请参阅。明确引用相关章节(其他ID仅被视为格式为“Z”、“+hh:mm”、“-hh:mm”或“UTC+hh:mm”等的偏移ID):

基于区域的ID必须包含两个或多个字符

在任何时区数据加载开始之前,类的源代码中还实现了至少有两个字符的要求:

/**
 * 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);
    }
}
注:

  • 我使用了“Dec”而不是“Dec”,否则解析器会抱怨。如果您的输入真的有大写字母,那么您可以使用builder方法

  • 关于解析问题,使用字段
    OffsetSeconds
    的另一个答案是更好的答案
    ,并且也得到了我的支持(忽略了此功能)。这并不是更好,因为它给用户带来了定义从军事区字母到偏移量的映射的负担,就像我建议的字符串预处理一样但它更好,因为它支持使用生成器方法
    optionalStart()
    optionalsend()
    ,因此可以选择时区字母A、B、。。。可以处理。
    另请参阅OP关于可选区域ID的评论


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

解析问题是可取的,一旦你考虑使用<代码> ZoneOffset <代码>而不是<代码> ZONIDID<代码>(并且考虑到军事区域是固定的偏移,这是一个很好的方法来看待这个问题)。 关键是方法

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

在本例中,我已经为
Z
A
B
设置了映射,但您需要将它们全部添加。否则,代码非常简单,设置一个可以打印和解析军事时间的格式化程序(日期和时间使用
OffsetDateTime

Map Map=ImmutableMap.of(0L,“Z”,3600L,“A”,7200L,“B”);
DateTimeFormatter f=新的DateTimeFormatterBuilder()
.appendPattern(“HH:mm”)
.appendText(ChronoField.OFFSET_秒,地图)
.toFormatter();
System.out.println(OffsetTime.now().format(f));
System.out.println(OffsetTime.parse(“11:30A”,f));

您是否尝试了
V
格式字符串中的区域id(即
“ddHHmmV MMM yy”
)?AFAIK
'Z'
仅表示
Z
字符。是的,我知道
'Z'
是一个文本,解析器不会解释它
Z(无引号)是一个符号(但不起作用)。是的,我试过
V
。但它给出了一个不同的错误<代码>IllegalArgumentException:尝试创建格式化程序时,模式字母计数必须为2:V
。使用
VV
,我在解析时返回到“…无法在索引6处解析”异常。无论如何,谢谢!:-)@实际上,“正确”的符号似乎是
z
(小写)。。。
/**
 * 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);
    }
}
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
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));