- Since hour within AM or PM can be 1 digit, specify so. Use the overloaded
appendValue
method that accepts a minimum and a maximum field width.
- As Edwin Daloezo already said, you probably want
ChronoField.CLOCK_HOUR_OF_AMPM
.
- You need am or pm unconditionally, so it should be outside of
optionalStart()
… optionalEnd()
as Turing85 said in a comment.
- Consider whether you need to accept
am
or AM
or both. In that last case you need to specify case insensitive parsing.
- Always specify a locale (a language) for your formatters. While AM and PM are hardly used in other langauges than English, they do have different values (texts) in other languages.
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);
If you want a format pattern string
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);