3

This question is similar to How to parse ZonedDateTime with default zone? but addinitional condition.

I have a string param that represent a date in UK format: "3/6/09". It doesn't contain time, only date. But may contain it and even time zone. And I want to parse it to ZonedDateTime.

public static ZonedDateTime parse(String value) {
    DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(SHORT).withLocale(Locale.UK).withZone(ZoneId.systemDefault());
    TemporalAccessor temporalAccessor = formatter.parseBest(value, ZonedDateTime::from, LocalDateTime::from, LocalDate::from);
    if (temporalAccessor instanceof ZonedDateTime) {
        return ((ZonedDateTime) temporalAccessor);
    }
    if (temporalAccessor instanceof LocalDateTime) {
        return ((LocalDateTime) temporalAccessor).atZone(ZoneId.systemDefault());
    }
    return ((LocalDate) temporalAccessor).atStartOfDay(ZoneId.systemDefault());
}

But, it fails with exception:

java.time.format.DateTimeParseException: Text '3/6/2009' could not be parsed at index 6

It's a bug for me, or isn't?

Community
  • 1
  • 1
Sergey Ponomarev
  • 2,947
  • 1
  • 33
  • 43
  • 2
    Main objection is: You use `DateTimeFormatter.ofLocalizedDateTime(...)` so there must be present at least the hour or any time component in your input. Another observation: With old pre8-code `Date d = DateFormat.getDateInstance(DateFormat.SHORT, Locale.UK).parse(input);` all is fine while `DateTimeFormatter.ofLocalizedDate(...)` fails! Maybe Oracle has changed the SHORT-format for Threeten-library. You should probably prefer an explicit pattern and then try again. By the way, such code with instanceof-expressions rather hurt my eyes (sorry). – Meno Hochschild Dec 15 '14 at 17:57
  • 1
    You can also experiment with `DateTimeFormatterBuilder.parseDefaulting()` and/or optional sections in order to supplement optional parts of your input although I have not made only positive experiences with it in the past. – Meno Hochschild Dec 15 '14 at 18:03

2 Answers2

2

In my opinion is not a bug. Your approach is flawed.

First of all you are returning a ZonedDateTime so it is expected that the String contains full date, time and zone information. The string "3/6/09" should be parsed to a LocalDate.

Second, you are delegating a runtime detection of format to the library. Again, you should be parsing/formatting an expected format. Your application should know wether is expecting a full date & time or a partial (only date or only time).

Anyway you will have more luck detecting the format and then using different parsing methods.

Only local date:

DateTimeFormatter
  .ofLocalizedDate(FormatStyle.SHORT)
  .parse(value, LocalDate::from)`

Zoned date and time:

DateTimeFormatter
  .ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT)
  .parse(value, ZonedDateTime::from)`
sargue
  • 5,695
  • 3
  • 28
  • 43
1

The format used can be seen using the getLocalizedDateTimePattern() method:

String fmt = DateTimeFormatterBuilder.getLocalizedDateTimePattern(
    FormatStyle.SHORT, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.UK);

The result is "dd/MM/yy HH:mm".

As such, the format is expecting both a date and a time with a space separator, so that is what must be provided.

In addition, the format/parse expects there to be two digits for the day-of-month and two digits for the month-of-year. Thus, you would need to pass in "03/06/09 00:00" in order to get the result you expect, in which case you can parse directly to a LocalDateTime.

Alternatively, use ofLocalizedDate():

DateTimeFormatter formatter =
    DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(Locale.UK);
LocalDate date = LocalDate.parse("03/06/99", formatter);

Note that the input must still have two digits for the day and month.

Alternatively, parse using a specific pattern that can handle the missing leading zeroes:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/M/yy");
LocalDate date = LocalDate.parse("3/6/99", formatter);
LocalDate date = LocalDate.parse("03/06/99", formatter);
// handles both "3/6/99" and "03/06/99"

Update: Lenient parsing also handles this case:

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
        .parseLenient().appendPattern("dd/MM/yy").toFormatter();
LocalDate date = LocalDate.parse("3/6/99", formatter);
LocalDate date = LocalDate.parse("03/06/99", formatter);
// handles both "3/6/99" and "03/06/99"
JodaStephen
  • 60,927
  • 15
  • 95
  • 117