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.
The DateTimeFormatter class is used to both parse and format dates according to specified Date and Time Patterns. Use parse (...) method to convert from String to Date/Time classes, use format (...) method to convert from Date/Time into String. If you not using Java 8 (and above), you can look at SimpleDateFormat.
The ISO date formatter that formats or parses the ordinal date without an offset, such as '2012-337'. static DateTimeFormatter. The ISO time formatter that formats or parses a time, with the offset if available, such as '10:15', '10:15:30' or '10:15:30+01:00'.
The main date-time classes provide two methods - one for formatting, format (DateTimeFormatter formatter), and one for parsing, parse (CharSequence text, DateTimeFormatter formatter) . For example: In addition to the format, formatters can be created with desired Locale, Chronology, ZoneId, and DecimalStyle.
We can also use FormatStyle to parse a date time String, converting it to ZonedDateTime, for example. We can then use the parsed value to manipulate the date and time variable:
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:
monthNameMap
must be populated with all 12 monthsparseDefaulting(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)You could use a DateTimeFormatterBuilder
:
private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.appendOptional(DateTimeFormatter.ofPattern("d. MMM. HH:ss"))
.appendOptional(DateTimeFormatter.ofPattern("d. MMMM HH:ss"))
.toFormatter(Locale.GERMAN);
Running it on this:
Stream.of(("10. Jan. 18:14\n" +
"8. Feb. 19:02\n" +
"1. Mär. 19:40\n" +
"4. Apr. 18:55\n" +
"2. Mai 21:55\n" +
"5. Juni 08:25\n" +
"5. Juli 20:09\n" +
"1. Aug. 13:42").split("\n"))
.map(formatter::parse)
.forEach(System.out::println);
you get:
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=10, MonthOfYear=1, MilliOfSecond=0, SecondOfMinute=14, HourOfDay=18},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=8, MonthOfYear=2, MilliOfSecond=0, SecondOfMinute=2, HourOfDay=19},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=1, MonthOfYear=3, MilliOfSecond=0, SecondOfMinute=40, HourOfDay=19},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=4, MonthOfYear=4, MilliOfSecond=0, SecondOfMinute=55, HourOfDay=18},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=2, MonthOfYear=5, MilliOfSecond=0, SecondOfMinute=55, HourOfDay=21},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=5, MonthOfYear=6, MilliOfSecond=0, SecondOfMinute=25, HourOfDay=8},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=5, MonthOfYear=7, MilliOfSecond=0, SecondOfMinute=9, HourOfDay=20},ISO
{NanoOfSecond=0, MicroOfSecond=0, DayOfMonth=1, MonthOfYear=8, MilliOfSecond=0, SecondOfMinute=42, HourOfDay=13},ISO
As pointed out it would be easier to use a standard and consistent format - here you are mixing long and short month names.
One option (short of using a DateTimeFormatterBuilder
) is to handle both cases separately:
private static final DateTimeFormatter SHORT_MONTH = DateTimeFormatter.ofPattern("d. MMM. HH:ss", Locale.GERMAN);
private static final DateTimeFormatter LONG_MONTH = DateTimeFormatter.ofPattern("d. MMMM HH:ss", Locale.GERMAN);
private static TemporalAccessor parse(String s) {
try {
return SHORT_MONTH.parse(s);
} catch (DateTimeParseException e) {
return LONG_MONTH.parse(s);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With