How to handle all zone offsets in one Java 8 DateTimeFormater
I need to create DateTimeFormatter
for the following valid dates.
String date1 = "2017-06-20T17:25:28";
String date2 = "2017-06-20T17:25:28.477777";
String date3 = "2017-06-20T17:25:28.477777Z";
String date4 = "2017-06-20T17:25:28.477777UTC";
String date5 = "2017-06-20T17:25:28.477777-05";
String date6 = "2017-06-20T17:25:28.477777+05";
String date7 = "2017-06-20T17:25:28.477777+05:30";
String date8 = "2017-06-20T17:25:28.477777-05:30";
String date9 = "2017-06-20T17:25:28.477777+0530";
String date10 = "2017-06-20T17:25:28.477777-0530";
I have tried the following date time format but it fails for the last two dates ( date9
, date10
).
private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd'T'HH:mm:ss")
.appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true)
.optionalStart().appendZoneId().optionalEnd()
.optionalStart().appendOffset("+HH", "+00").optionalEnd()
.optionalStart().appendOffset("+HH:mm", "+00:00").optionalEnd()
.optionalStart().appendOffset("+HHmm", "+0000").optionalEnd().toFormatter();
All dates from date1
to date8
work fine, but when trying to parse the last two dates, I get DateTimeParseException
:
Exception in thread "main" java.time.format.DateTimeParseException: text '2017-06-20T17: 25: 28.477777 + 0530' could not be parsed, text without text found at index 29
For parsing the date used below.
LocalDateTime.parse(date1, DATE_TIME_FORMATTER);
Valid template for offset From OffsetIdPrinterParser
:
static final class OffsetIdPrinterParser implements DateTimePrinterParser {
static final String[] PATTERNS = new String[] {
"+HH", "+HHmm", "+HH:mm", "+HHMM", "+HH:MM", "+HHMMss", "+HH:MM:ss", "+HHMMSS", "+HH:MM:SS",
}; // order used in pattern builder
I cannot figure out when I am using the correct ZoneOffset templates why my last two dates are failing.
source to share
Another alternative is to use optional sections separated by character []
and appropriate offset patterns ( VV
and x
):
DATE_TIME_FORMATTER = DateTimeFormatter
// pattern with optional sections: fraction of seconds and offsets
.ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSSSSS][VV][x][xx][xxx]");
Each pair is []
equivalent to one section optionalStart
and optionalEnd
. Note that I also had to include the capitalization S
(fraction of a second) as optional to parse the case where this field is missing.
Other templates ( VV
and x
) correspond to different shifts that you need. From the javadoc :
Pattern Count Equivalent builder methods ------- ----- -------------------------- VV 2 appendZoneId() x 1 appendOffset("+HHmm","+00") xx 2 appendOffset("+HHMM","+0000") xxx 3 appendOffset("+HH:MM","+00:00")
This works for all of your entry dates.
The only difference is that it [.SSSSSS]
accepts exactly 6 digits in a fraction of a second field (or zero digits, as this is an optional section), whereas it appendFraction
accepts any number between 0 and 6 digits. To get exactly the same behavior, you should use DateTimeFormatterBuilder
:
DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
// date and time
.appendPattern("yyyy-MM-dd'T'HH:mm:ss")
// fraction of seconds, from 0 to 6 digits
.appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true)
// optional offset patterns
.appendPattern("[VV][x][xx][xxx]")
.toFormatter();
Just change the order of your additional sections:
private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd'T'HH:mm:ss")
.appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true)
.optionalStart().appendZoneId().optionalEnd()
.optionalStart().appendOffset("+HHmm", "+0000").optionalEnd()
.optionalStart().appendOffset("+HH:mm", "+00:00").optionalEnd()
.optionalStart().appendOffset("+HH", "+00").optionalEnd()
.toFormatter();
This analyzes all of your 10 sample time lines.
I'm not really sure why it works. I suppose he is now trying +HHmm
to +HH
, which will ensure he gets four digits if there are four, instead of leaving the last two unpacked.
source to share