Failed to parse single digit hour and lowercase am-pm of day into Java 8 LocalTime
Asked Answered
T

6

10

When I try to run the following code:

LocalTime test = LocalTime.parse("8:00am", DateTimeFormatter.ofPattern("hh:mma"));

I get this:

Exception in thread "main" java.time.format.DateTimeParseException: Text '8:00am' could not be parsed at index 0

Any idea why this might be happening?

Taboo answered 29/9, 2017 at 1:34 Comment(1)
What happens if you use just a single h, like "h:mma"?Volunteer
C
8

The AM/PM tokens have* to be uppercase:

LocalTime test = LocalTime.parse("8:00AM", DateTimeFormatter.ofPattern("hh:mma"));

Patterns definitions for hours:

Symbol  Meaning                     Presentation      Examples
------  ------------                ------------      --------
a       am-pm-of-day                text              PM
h       clock-hour-of-am-pm (1-12)  number            12
K       hour-of-am-pm (0-11)        number            0
k       clock-hour-of-am-pm (1-24)  number            0
H       hour-of-day (0-23)          number            0

Refer to the DateTimeFormatter javadocs for all available patterns: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html

-- EDIT

*As per @Hugo's comment, this is related to the Locale your formatter is setup on. If you are not specifying any, then the JVMs default is used. It just happens that the vast majority of Locales enforces AM/PM tokens to be upper case:

It so happens that, for most locales, the uppercase AM is used, so it'll work for most people. But it can also be a.m. in ga_IE and es_US locales, 午前 in Japanese (ja_JP), fm in Swedish (sv_SE), and many others. I've tested in JDK 1.8.0_144 and it has 160 locales - 108 of them use AM - anyway, it's not an issue if your JVM uses one of those 108 locales (and nobody changes this config)

Cointreau answered 29/9, 2017 at 2:9 Comment(3)
Actually, it doesn't have to be uppercase (the javadoc shows just an example), it all depends on the locale (when you create a formatter without specifying a locale, it uses the JVM's default). It so happens that, for most locales, the uppercase AM is used, so it'll work for most people. But it can also be a.m. in ga_IE and es_US locales, 午前 in Japanese (ja_JP), fm in Swedish (sv_SE), and many others. I've tested in JDK 1.8.0_144 and it has 160 locales - 108 of them use AM - anyway, it's not an issue if your JVM uses one of those 108 locales (and nobody changes this config)Unbelievable
@Hugo Wonderful addition, I honestly didn't know that. I will include your info on the answer. Thanks!Cointreau
Never use SimpleDateFormat or DateTimeFormatter without a Locale.Kernel
U
8

AM/PM pattern is locale sensitive. If you create a formatter and don't set a java.util.Locale, it'll use the JVM's default. Anyway, I've checked in JDK 1.8.0_144 and there's no locale that uses lowercase am as the text for AM/PM field (I've found locales that use a.m. and AM, but no am).

So, one alternative is to set a locale that uses AM (example: Locale.ENGLISH) and use a java.time.format.DateTimeFormatterBuilder to build a case insensitive formatter. Another detail is that the hour in the input has only 1 digit, so you must change the pattern to h (which accepts 1 or 2 digits, while hh accepts only 2):

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // case insensitive
    .parseCaseInsensitive()
    // pattern
    .appendPattern("h:mma")
    // set Locale that uses "AM" and "PM"
    .toFormatter(Locale.ENGLISH);
// now it works
LocalTime test = LocalTime.parse("8:00am", fmt);

The problem is that the locale can also affect other fields (if you use month or day of week names, or week based fields, for example).

Another detail is that the formatter is case insensitive only for parsing. When formatting, it'll use the locale specific symbols, which in this case is uppercase. So this:

System.out.println(fmt.format(test)); // 8:00AM

Prints:

8:00AM


To not depend on the locale, you can use a map of custom texts for this field, using a java.time.temporal.ChronoField:

// map of custom text for AM/PM field
Map<Long, String> map = new HashMap<>();
// AM's value is 0
map.put(0L, "am");
// PM's value is 1
map.put(1L, "pm");
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // pattern (hour:minute)
    .appendPattern("h:mm")
    // use custom text for AM/PM
    .appendText(ChronoField.AMPM_OF_DAY, map)
    // create formatter, no need to set locale
    .toFormatter();
// it also works
LocalTime test = LocalTime.parse("8:00am", fmt);

The difference from the previous formatter is that it uses the custom text (lowercase am and pm) for both parsing and formatting. So this code:

System.out.println(fmt.format(test)); // 8:00am

Will print:

8:00am

Unbelievable answered 29/9, 2017 at 11:38 Comment(0)
A
2

AM/PM are uppercase, and you need to use h to specify one digit for hours (with hh two digits are required). Like,

LocalTime test = LocalTime.parse("8:00am".toUpperCase(), 
        DateTimeFormatter.ofPattern("h:mma"));
Abominable answered 29/9, 2017 at 1:39 Comment(1)
I believe this is the key point of the question. If you use ONE h you need to specify ONE digit, otherwise, use TWO hh for TWO digits.Constructive
D
1

You need to capitalize to AM and add a leading zero in front of the 8.

LocalTime lt=LocalTime.parse("08:00AM",DateTimeFormatter.ofPattern("hh:mma"));
Dakotadal answered 29/9, 2017 at 1:46 Comment(0)
C
0

Just a heads up, In Adelaide the requirement is for it to use am insteam of AM, so I am having to .toLowerCase() my dates prior to parsing from text. So "AM" fails, "am" works.

Corin answered 24/6, 2019 at 8:40 Comment(0)
D
0
LocalTime lt=LocalTime.parse("08:00p.m.",DateTimeFormatter.ofPattern("hh:mma"));

if you put a.m. or p.m. it works but AM or PM not working. i am using openjdk 11 and openjdk 14, i think it is a bug.

Drugget answered 14/6, 2020 at 17:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.