0

This looks like a very simple thing to achieve, but I am failing to do so.

I have a string pattern which is yyyyMMddHH and I am trying to parse 2021061104 into an instance of LocalDateTime

Here is the code:

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;


class Main {
    public static void main(String[] args) {
        String pattern = "yyyyMMddHH";
        String date = "2021061104";
        DateTimeFormatter formatter =
            new DateTimeFormatterBuilder()
                .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
                .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
                .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
                .parseLenient()
                .appendPattern(pattern)
                .toFormatter();
        LocalDateTime ldt = LocalDateTime.parse(date, formatter);
    }
}

It throws this exception:

Exception in thread "main" java.time.format.DateTimeParseException: Text '2021061104' could not be parsed at index 8
        at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
        at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
        at java.time.LocalDateTime.parse(LocalDateTime.java:492)
        at Main.main(Main.java:22)

It is failing to parse the HH field from the input.
I have looked at the javadoc here But that does not help.

Why do I have this exception? How to solve this?

EDIT:

I can not remove .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)

The following are the constraints:

  • The user will give the pattern and date ( might be as args[0] and args[1] )
  • The pattern must always have date ( Year Month and Date )
  • The time in the pattern is optional and it would be up to hour only.
  • Examples of a couple of valid patterns are: yyyy-MM-dd HH, yyyy MM dd

With these constraints, I can not remove .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) because if I do so, I would not be able to parse yyyy-MM-dd into an instance of LocalDateTime here

Olivier Grégoire
  • 33,839
  • 23
  • 96
  • 137
Anand Undavia
  • 3,493
  • 5
  • 19
  • 33
  • 1
    Why not just `DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); `? – g00se Jul 21 '21 at 16:02
  • Does this answer your question? [Parse date-only as LocalDateTime in Java 8](https://stackoverflow.com/questions/49323017/parse-date-only-as-localdatetime-in-java-8) – Eklavya Jul 21 '21 at 16:15
  • You can use optional part in formatted `new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd[HH]").parseDefaulting(ChronoField.HOUR_OF_DAY, 0).toFormatter();` using `[]` – Eklavya Jul 21 '21 at 16:17

1 Answers1

5

Well, I think this is caused by the line with .parseDefaulting(ChronoField.HOUR_OF_DAY, 0). The builder immediately inserts a default value, before any parsing has taken place. At the time of parsing, the hour component already has a value, so the parsing of HH fails.

This behavior is actually mentioned in the JavaDocs:

During parsing, the current state of the parse is inspected. If the specified field has no associated value, because it has not been parsed successfully at that point, then the specified value is injected into the parse result. Injection is immediate, thus the field-value pair will be visible to any subsequent elements in the formatter. As such, this method is normally called at the end of the builder.

Emphasis mine.

So a possible fix would be to move the parseDefaulting lines to the end of the formatter builder:

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    .parseLenient()
    .appendPattern(pattern)
    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
    .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
    .toFormatter();

Here is a working example with those lines moved to the end of the builder.

MC Emperor
  • 22,334
  • 15
  • 80
  • 130
  • Thank you for the answer! I can not actually remove the `.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)` Let me update the questions with the constraints that I have. – Anand Undavia Jul 21 '21 at 16:04
  • 1
    Hey! Moving `parseDefaulting()` lines at the end of the formatter actually did the trick! Which is weird, because I thought it followed the Builder pattern -- But it does not look like so! Thank you, Marking this answer as accepted. – Anand Undavia Jul 21 '21 at 16:17
  • And `.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0).parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)` is not needed `.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)` do the job for them. – Eklavya Jul 21 '21 at 16:19