Reputation: 1081
Following my other question on how to parse date-only strings as LocalDateTime, on attempt to parse string 20120301122133 using pattern yyyyMMdd[HHmmss] I get an error. Strange thing is that parsing 20120301 122133 using pattern yyyyMMdd[ HHmmss] works perfectly.
So this code works fine
LocalDateTime.parse(
"19940513 230000",
new DateTimeFormatterBuilder()
.appendPattern("yyyyMMdd[ HHmmss]")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
.toFormatter()
)
And this one fails
LocalDateTime.parse(
"19940513230000",
new DateTimeFormatterBuilder()
.appendPattern("yyyyMMdd[HHmmss]")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
.toFormatter()
)
How should I parse Strings in format yyyyMMdd[HHmmss], i.e. in format yyyyMMddHHmmss with optional time part using the java 8 time API?
The parse pattern is a configurable option and thus is known only at runtime. So I can't e.g. replace the String pattern with hard-coded DateTimeFormatterBuilder invocations.
Upvotes: 5
Views: 2629
Reputation: 3974
This happens because the year hasn't a fixed value, it is normally limited to 19 digits.
If you create the following formatter (minutes and seconds are not required) and use the toString()
method:
new DateTimeFormatterBuilder()
.appendPattern("yyyyMMdd[ HHmmss]")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter()
.toString();
You could see the following:
"Value(YearOfEra,4,19,EXCEEDS_PAD)Value(MonthOfYear,2)Value(DayOfMonth,2)[' 'Value(HourOfDay,2)Value(MinuteOfHour,2)Value(SecondOfMinute,2)]java.time.format.DateTimeFormatterBuilder$DefaultValueParser@32eff876"
Here you can see that YearOfEra
has a minimun width of 4 and a maximun of 19.
You can use one of the answers of Meno or Ole.
However if you need to receive the format and the date as parameters, and you want to be able to specify the date format in a simpler way (e.g. yyyyMMdd[HHmmSS]
instead [...][...]
), you may preprocess one of the to values (either the format of the date).
You can create 'dinamically' the formatter, so every yyyy
is interpreted as a 4 digits year only.
A custom format builder could be something like (can be improved):
public static DateTimeFormatter createFixed4DigitYearFormatter(String format) {
DateTimeFormatterBuilder formatBuilder = new DateTimeFormatterBuilder();
Arrays.stream(format.split("yyyy", -1))
.flatMap(cur -> Stream.of("yyyy", cur)).skip(1)
.filter(str -> !str.isEmpty())
.forEach(pattern -> {
if ("yyyy".equals(pattern)) formatBuilder
.appendValue(ChronoField.YEAR_OF_ERA, 4);
else formatBuilder.appendPattern(pattern);
});
return formatBuilder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0).toFormatter();
}
This formater split the format by the string "yyyy"
, then every non "yyyy"
is added as a pattern (with appendPattern(..)
) and the "yyyy"
is added as a value of type YEAR_OF_ERA
with fixed 4 digits (with appendValue(..)
).
Finally you can use the formatter with multiple formats:
System.out.println(LocalDateTime.parse("19940513230000",
createFixed4DigitYearFormatter("yyyyMMdd[HHmmss]"))); // 1994-05-13T23:00
System.out.println(LocalDateTime.parse("19940513",
createFixed4DigitYearFormatter("yyyyMMdd[HHmmss]"))); // 1994-05-13T00:00
System.out.println(LocalDateTime.parse("1994-05-13 23:00:00",
createFixed4DigitYearFormatter("yyyy-MM-dd[ HH:mm:ss]"))); // 1994-05-13T23:00
System.out.println(LocalDateTime.parse("1994-05-13",
createFixed4DigitYearFormatter("yyyy-MM-dd[ HH:mm:ss]"))); // 1994-05-13T00:00
Upvotes: 2
Reputation: 86323
System.out.println(LocalDateTime.parse(
"19940513230000",
new DateTimeFormatterBuilder()
.appendPattern("[uuuuMMddHHmmss][uuuuMMdd]")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
.toFormatter()
));
This prints:
1994-05-13T23:00
If instead I try to parse a string with just the date, "19940513"
, I get
1994-05-13T00:00
It works with yyyy
instead of uuuu
too. Assuming all your years are in this era (year 1 or later), it doesn’t make any difference which one you use. Generally uuuu
will accept a negative year too, 0 meaning 1 BCE, -1 meaning 2 BCE and so forth.
Upvotes: 4
Reputation: 44071
The problem is that the pattern expression "yyyy" does not indicate a fixed four-digit-year but at least 4 digits (or more, so the parser is greedy). But you can do following:
LocalDateTime ldt =
LocalDateTime.parse(
"19940513230000",
new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4)
.appendPattern("MMdd[HHmmss]")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter());
System.out.println(ldt); // 1994-05-13T23:00
Upvotes: 2