3

I need to create a 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 formatter, but this fails for 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 I get a DateTimeParseException when trying to parse last two dates:

Exception in thread "main" java.time.format.DateTimeParseException: Text '2017-06-20T17:25:28.477777+0530' could not be parsed, unparsed text found at index 29

For parsing the date I am using following.

LocalDateTime.parse(date1, DATE_TIME_FORMATTER);

Valid Pattern 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 am not able to understand while I am using valid ZoneOffset patterns, why my last two dates fail.

Amit Garg
  • 838
  • 1
  • 10
  • 21

2 Answers2

5

Simply reverse the order of your optional 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 parses all your 10 sample date-time strings.

I am not quite sure why it works. I suppose that it is now trying +HHmm before +HH, which makes sure it gets alle four digits when there are four, instead of leaving the last two unparsed.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • @Eugene But still we need to find that why Java do this. – Amit Garg Jun 23 '17 at 09:50
  • 1
    There’s a bit of logic in the order making a difference. Regex are not directly comparable, still `"cats".replaceFirst("cat|cats", "bird")` yields `birds` while the opposite order, `"cats".replaceFirst("cats|cat", "bird")`, just yields `bird`. The date-time formatter doesn’t even give us an OR as in the regex, just a sequence of sections that each are optional, so I gather there’s no alternative to trying them in order. – Ole V.V. Jun 23 '17 at 10:28
4

Another alternative is to use optional sections, delimited by [], and the respective 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 of [] is equivalent to one optionalStart and optionalEnd section. Note that I also had to include the uppercase S (fraction of second) as optional, to parse the case where this field is not present.

The other patterns (VV and x) correspond to the various offsets 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 your input dates.


The only difference is that [.SSSSSS] accepts exactly 6 digits in the fraction-of-seconds field (or zero digits, as it's an optional section), while appendFraction accepts any quantity from 0 to 6 digits. To get exactly this same behaviour, you must use the 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();