Search code examples
javadatetimelocalizationjava-8java-time

How to parse non-standard month names with DateTimeFormatter


I need to parse (German) dates that come in the following form:

10. Jan. 18:14
8. Feb. 19:02
1. Mär. 19:40
4. Apr. 18:55
2. Mai 21:55
5. Juni 08:25
5. Juli 20:09
1. Aug. 13:42
[...]

As you can see, the month names are cut if the month has more than 4 characters. Even weirder, don't aks me why, the month of March is shortened to Mär. although the whole name is März. How can I parse this with java.time? (The dates are formatted based on the localization of the android device that creates the list of dates. However, I'm not parsing it on Android)

My approach was to create a DateTimeFormatter like this:

DateTimeFormatter.ofPattern("d. MMMM HH:mm").withLocale(Locale.GERMAN);
// or
DateTimeFormatter.ofPattern("d. MMMMM HH:mm").withLocale(Locale.GERMAN);

But neither the MMMM nor the MMMMM pattern fit the dates that are shortened. I can, of course, have the following pattern d. MMM. HH:mm to match the shortened months, but then I can't match the 3 and 4 characters months. I am aware that I can have two formatters (MMM. and MMMMM) but I would rather have a solution where I have only one formatter and possibly a custom locale or something like this.


Solution

  • The answer to the problem is the DateTimeFormatterBuilder class and the appendText(TemporalField, Map) method. It allows any text to be associated with a value when formatting or parsing, which solves the problem effectively and elegantly:

    Map<Long, String> monthNameMap = new HashMap<>();
    monthNameMap.put(1L, "Jan.");
    monthNameMap.put(2L, "Feb.");
    monthNameMap.put(3L, "Mar.");
    DateTimeFormatter fmt = new DateTimeFormatterBuilder()
        .appendPattern("d. ")
        .appendText(ChronoField.MONTH_OF_YEAR, monthNameMap)
        .appendPattern(" HH:mm")
        .parseDefaulting(ChronoField.YEAR, 2016)
        .toFormatter();
    
    System.out.println(LocalDateTime.parse("10. Jan. 18:14", fmt));
    System.out.println(LocalDateTime.parse("8. Feb. 19:02", fmt));
    

    Some notes:

    • The monthNameMap must be populated with all 12 months
    • The formatter should normally be assigned to a static final constant, rather than being created all the time
    • The parseDefaulting(YEAR, 2016) has been added so that LocalDateTime.parse(String, DateTimeFormatter) can be used directly. Without it, there would be no year, and thus nothing more than a TemporalAccessor could be parsed (the year must be a leap year, in case 29th Feb is being parsed)