Sunday, 22 April 2018

java - Military time zones using JSR 310 (DateTime API)



I'm using the JSR 310 DateTime API* in my application, and I need to parse and format military date times (known as DTG or "date time group").



The format I'm parsing looks like this (using DateTimeFormatter):




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


This format is fairly easy to parse as described above. The problem arises when the dates contains a different time zone than 'Z' (Zulu time zone, same as UTC/GMT), for example 'A' (Alpha, UTC+1:00) or 'B' (Bravo, UTC+2:00). See Military time zones for the full list.



How can I parse these time zones? Or in other words, what can I put in the format above other than the literal 'Z' to have it parse all zones correctly? I have tried using "ddHHmmX MMM yy", "ddHHmmZ MMM yy" and "ddHHmmVV MMM yy", but none of them work (all will throw DateTimeParseException: Text '312359A DEC 14' could not be parsed at index 6 for the example above, when parsing). Using a single V in the format is not allowed (IllegalArgumentException when trying to instantiate DateTimeFormatter).



Edit: It seems that the symbol z could have worked, if it wasn't for the issue below.




I should also mention that I have created a ZoneRulesProvider with the all the named zones and correct offset. I have verified that these are registered correctly using the SPI mechanism, and my provideZoneIds() method is invoked as expected. Still won't parse. As a side issue (Edit: this now seems to be the main issue), single character time zone ids (or "regions") other than 'Z' are not allowed by the API.



For example:



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


Will throw DateTimeException: Invalid zone: A (without even accessing my rules provider to see if it exists).



Is this an oversight in the API? Or am I doing something wrong?







*) Actually, I'm using Java 7 and ThreeTen Backport, but I don't think that matters for this question.



PS: My current workaround is to use 25 different DateTimeFormatters with literal zone id (ie. "ddHHmm'A' MMM yy", "ddHHmm'B' MMM yy", etc), use a RegExp for extracting the zone id, and delegating to the correct formatter based on the zone. Zone ids in the provider are named "Alpha", "Bravo", etc to allow ZoneId.of(...) to find the zones. It works. But it's not very elegant, and I'm hoping there's a better solution.


Answer



In java.time, the ZoneId is limited to be 2 characters or more. Ironically, this was to reserve space to allow the military IDs to be added in a future JDK release if it proved to be heavily in demand. As such, sadly your provider will not work, and there is no way to create the ZoneId instances you desire with those names.



The parsing problem is soluble once you consider working with ZoneOffset instead of ZoneId (and given that military zones are fixed offsets, that is a good way to look at the problem).




The key is the method DateTimeFormatterBuilder.appendText(TemporalField, Map) which allows a numeric field to be formatted and parsed as text, using text of your choice. And ZoneOffset is a numeric field (the value being the total number of seconds in the offset).



I this example, I've setup the mapping for Z, A and B, but you'd need to add them all. Otherwise, the code is pretty simple, setting up a formatter that can print and parse the military time (use OffsetDateTime for date and time).



Map 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));

No comments:

Post a Comment

casting - Why wasn't Tobey Maguire in The Amazing Spider-Man? - Movies & TV

In the Spider-Man franchise, Tobey Maguire is an outstanding performer as a Spider-Man and also reprised his role in the sequels Spider-Man...