I'm trying to build a date time format that accepts AM and PM stamps. For example, whenever the LocalTime.parse("12:00 am") in java is called, with the default format, an exception is thrown. So I'm adding my own format like this
String time = "2:00 am"
DateTimeFormatter format = new DateTimeFormatterBuilder()
.appendValue(HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(MINUTE_OF_HOUR, 2)
.optionalStart()
.appendLiteral(':')
.appendValue(SECOND_OF_MINUTE, 2)
.optionalStart()
.appendValue(AMPM_OF_DAY)
.toFormatter();
LocalTime.parse(time, format);
How ever it seems that it doesn't work. I get:
Exception in thread "main" java.time.format.DateTimeParseException: Text '2:00 am' could not be parsed at index 0
How can I build a correct DateTimeFormatterBuilder that parses the following strings: "2:00 am", "12:15 pm"? And yes I'm aware that you can append formats, but in this case it's needed to use appending values.
appendValue
method that accepts a minimum and a maximum field width.ChronoField.CLOCK_HOUR_OF_AMPM
.optionalStart()
… optionalEnd()
as Turing85 said in a comment.am
or AM
or both. In that last case you need to specify case insensitive parsing.So my go is:
String time = "2:00 am";
DateTimeFormatter format = new DateTimeFormatterBuilder()
.appendValue(ChronoField.CLOCK_HOUR_OF_AMPM, 1, 2, SignStyle.NEVER)
.appendLiteral(':')
.appendValue(ChronoField.MINUTE_OF_HOUR, 2)
.optionalStart()
.appendLiteral(':')
.appendValue(ChronoField.SECOND_OF_MINUTE, 2)
.optionalEnd()
.appendLiteral(' ')
.appendText(ChronoField.AMPM_OF_DAY)
.toFormatter(Locale.forLanguageTag("en-AU"));
System.out.println(LocalTime.parse(time, format));
Output is:
02:00
In Australian English am and pm are in lower case (according to my Java 11), so I specified this locale. Which you should only do if your input comes from Australia, or it will just confuse (there are a few more locales where am and pm are in lower case). To accept lower case am and pm from another English-speaking locale, use parseCaseInsensitive()
. For example:
DateTimeFormatter format = new DateTimeFormatterBuilder()
.appendValue(ChronoField.CLOCK_HOUR_OF_AMPM, 1, 2, SignStyle.NEVER)
.appendLiteral(':')
.appendValue(ChronoField.MINUTE_OF_HOUR, 2)
.optionalStart()
.appendLiteral(':')
.appendValue(ChronoField.SECOND_OF_MINUTE, 2)
.optionalEnd()
.appendLiteral(' ')
.parseCaseInsensitive() // Accept AM or am
.appendText(ChronoField.AMPM_OF_DAY)
.toFormatter(Locale.ENGLISH);
This is no recommendation per se. If you do not need case insensitive parsing, it is possible to build your formatter from a format pattern string rather than a builder. On one hand it may be even more error-prone, on the other it’s much shorter.
DateTimeFormatter format = DateTimeFormatter
.ofPattern("h:mm[.ss] a", Locale.forLanguageTag("en-AU"));
Output is the same as before. The square brackets in the format pattern string specify that the seconds are optional.
If you do need case insensitive parsing, you do need the builder for specifying it.
You may also mix the approaches since a builder too accepts a format pattern string:
DateTimeFormatter format = new DateTimeFormatterBuilder()
.parseCaseInsensitive() // Accept AM or am
.appendPattern("h:mm[.ss] a")
.toFormatter(Locale.ENGLISH);