a expects either PM or AM in upper case. To get a case insensitive formatter you need to build it manually:
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendPattern("EEE MMM dd, yyyy h:mma z")
.toFormatter(Locale.US);
Note that you will get a new error because the 16th of July is not a Wednesday.
Answer from assylias on Stack Overflowa expects either PM or AM in upper case. To get a case insensitive formatter you need to build it manually:
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendPattern("EEE MMM dd, yyyy h:mma z")
.toFormatter(Locale.US);
Note that you will get a new error because the 16th of July is not a Wednesday.
Note that the case of AM and PM depends on your locale!
So if your locale is US it's expected to be upper case, but if it's UK it's expected to be lower case.
See: Localize the period (AM/PM) in a time stamp to another language for more details.
Easiest way to get it by using date pattern - h:mm a, where
- h - Hour in am/pm (1-12)
- m - Minute in hour
- a - Am/pm marker
Code snippet :
DateFormat dateFormat = new SimpleDateFormat("hh:mm a");
Read more on documentation - SimpleDateFormat java 7
Use this SimpleDateFormat formatDate = new SimpleDateFormat("hh:mm a");

Java docs for SimpleDateFormat
You cannot parse the Strings due to two h in your format pattern instead of just one, and due to am-pm of day being in lower case letters. The a in the pattern String expects (standard/default) upper case, like AM.
You can either manipulate the Strings as shown in the other answer(s), but you can also use a DateTimeFormatter that can handle lower-case things. All you need is to build one with a DateTimeFormatterBuilder and make it parseCaseInsensitive() and apply a Locale:
public static void main(String[] args) {
String dtStr = "1:23am-1:08am";
String t1 = dtStr.split("-")[0];
String t2 = dtStr.split("-")[1];
// build a formatter that parses lower case am-pm of day
DateTimeFormatter format = new DateTimeFormatterBuilder()
.parseCaseInsensitive() // handles lower- and upper-case
.appendPattern("h:mma")
.toFormatter(Locale.ENGLISH); // doesn't reliably work without a Locale
LocalTime time1 = LocalTime.parse(t1, format);
LocalTime time2 = LocalTime.parse(t2, format);
Duration dur = Duration.between(time1, time2);
System.out.println(dur.toMinutes() + " minutes " + dur.toSecondsPart() + " seconds");
}
Output:
-15 minutes 0 seconds
This is a little more flexible than manipulating the input before parsing, because this one can also parse upper-case am-pm of day.
You need to use the following pattern h:mma because of one-digit hour, also a parses AM/PM in uppercase
String dtStr = "1:23am-1:08am";
String t1 = dtStr.split("-")[0].toUpperCase();
String t2 = dtStr.split("-")[1].toUpperCase();
DateTimeFormatter format = DateTimeFormatter.ofPattern("h:mma");
LocalTime time1 = LocalTime.parse(t1, format);
LocalTime time2 = LocalTime.parse(t2, format);
Duration dur = Duration.between(time1, time2);
System.out.println(dur.toMinutes() + " minutes " + dur.toSecondsPart() + " seconds");
-15 minutes 0 seconds
java.time format specifications
My laptop can parse `AM` but not `am`. On the server, it parses `am` but not `AM`.
Here's a POC: https://onecompiler.com/java/42ymjw6mp
Can anyone shed some light what is going on?
Use proper date-time objects for your dates and times
For the vast majority of purposes you should not keep your date and time in a string and should not convert your date and time from a string in one format to a string in another format. Keep your date and time in a ZonedDateTime or LocalDateTime object.
When you are required to accept string input, parse that input into a date-time object immediately. I am using and recommending java.time, the modern Java date and time API:
DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm:ss");
String input = "11/06/2020 04:14:20";
LocalDateTime dateTime = LocalDateTime.parse(input, inputFormatter);
System.out.println(dateTime);
Output so far is:
2020-06-11T04:14:20
Since there is no AM or PM in your string, I have assumed that 04:14:20 was the time of day from 00:00:00 through 23:59:59. If you intended otherwise, you need to explain how.
Only when you need to give string output, format your date and time back into a string of appropriate format:
DateTimeFormatter outputFormatter = DateTimeFormatter
.ofPattern("MMMM dd, yyyy hh:mm:ss a", Locale.ENGLISH);
String output = dateTime.format(outputFormatter);
System.out.println(output);
June 11, 2020 04:14:20 AM
Do provide a locale for the formatter so Java knows which language to use for the month name and the AM/PM indicator.
What went wrong in your code?
Your string has no AM nor PM: 11/06/2020 04:14:20. Yet your format pattern string requires an AM/PM marker in the end. This is what format pattern letter a signifies. So your string wasn’t in the format that you required. This was the reason for the exception that you observed.
Link
Oracle tutorial: Date Time explaining how to use java.time.
You can check this code you have to pass the am/pm part too with the date string value as your format is expecting that.
//String date = "11/06/2020 04:14:20";
String date = "11/06/2020 04:14:20 am";
DateFormat df = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss a");
https://ideone.com/3nibwJ
- Since hour within AM or PM can be 1 digit, specify so. Use the overloaded
appendValuemethod 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
amorAMor 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);
I believe you need to change HOUR_OF_DAY which goes from (0-23) to CLOCK_HOUR_OF_AMPM which goes from (1-12). I also added a space literal before AM/PM and used appendText instead of appendValue for the AM/PM field.
The following works just fine.
var formatter = new DateTimeFormatterBuilder()
.appendValue(CLOCK_HOUR_OF_AMPM, 2)
.appendLiteral(':')
.appendValue(MINUTE_OF_HOUR, 2)
.optionalStart()
.appendLiteral(":")
.appendValue(SECOND_OF_MINUTE, 2)
.optionalStart()
.appendLiteral(" ")
.appendText(AMPM_OF_DAY)
.toFormatter();
//formatter = new DateTimeFormatterBuilder().appendPattern("hh:mm:ss a").toFormatter();
var time = LocalTime.parse("07:20:54 AM", formatted);
System.out.println(time); // 19:20:54