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.
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
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
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
This is a tricky one, because the syntax for an optional section of the DateTimeFormatter class specifically wants the space between the optional element and its predecessor included within square brackets.
The documentation of the parseBest() method shows an example of a pattern with an optional timezone ID, uuuu-MM-dd HH.mm[ VV], where the space between the minute and the timezone ID is included within the optional section.
In your snippet, the first optional was written properly hh:mm:ss.SSS[ a... (with the space between SSS and a included within square brackets), but then the optional part for the timezone name was written a [z]zzz instead of a[ z]zzz. In your case, you can either nest the optional sections like so hh:mm:ss.SSS[[ a] zzz] E MMM dd yyyy, or write them as individual optional sections hh:mm:ss.SSS[ a][ z][ zzz] E MMM dd yyyy.
Here is a re-written version of your code covering the 4 cases:
public class Main {
public static void main(String[] args) {
parseDate("09:10:05.584 AM EST Wed Nov 29 2023", TIME_FORMAT);
parseDate("09:10:05.584 AM Wed Nov 29 2023", TIME_FORMAT);
parseDate("09:10:05.584 EST Wed Nov 29 2023", TIME_FORMAT);
parseDate("09:10:05.584 Wed Nov 29 2023", TIME_FORMAT);
}
public static final String TIME_FORMAT = "HH:mm:ss.SSS[ a][ z][zzz] E MMM dd yyyy";
public static String parseDate(String input, String format) {
String retVal = "";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format, Locale.ENGLISH);
TemporalAccessor ta = formatter.parseBest(input, LocalDateTime::from, LocalDate::from);
retVal = ta.toString();
System.out.println("Parsed date is: " + retVal);
return retVal;
}
}
You can quickly check the code here at this link:
https://ideone.com/bpfw8o
specifying the format this way:
public static final String TIME_FORMAT = "hh:mm:ss.SSS[ a][ z][ zzz] E MMM dd yyyy";
worked. Trying to nest the [] was not working.
- 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
You are using the wrong pattern, H is hour-of-day (0-23); you need h.
The error is telling you that H is a 24 hour hour, and 8 is therefore obviously AM. Hence when you tell the parser to use 8 on the 24 hour clock and also tell the parse that it's PM it blows up.
In short, read the documentation.
The pattern symbol H stands for 24-hour-clock. So the parser tells you in case of "PM" that "08"-hour can only be in the scope of AM that is the first half of day.
Solution: Use pattern symbol "h" instead (for 12-hour-clock).
24-hour time not appropriate for some locales
If localizing for the United States, then you should not be aiming for 24-hour time. 24-hour time is generally not used in the US outside of the military. Indeed, Americans often refer to it as "military time".
So your two statements "how to use 24 hours instead of AM/PM?" and "This is for all different locales" are contradictory. You cannot have it both ways. Either you want to control the output such as forcing 24 hour clock, or you want to localize.
If you really need a specific format, use the Answer by lkatiforis. But if your goal is to localize automatically, then you have to "go with the flow" regarding the results. Some locales will localize to 24-hour clock, and some locales will localize to 12-hour clock.
If you want to know where Java gets its rules for how to localize date-time values (translation, and cultural norms), see the Common Locale Data Repository (CLDR) maintained by the Unicode Consortium. The CLDR is used by default in most implementations of Java 9 and later.
If you want to see the various locales in action, here is some example code.
LocalDateTime ldt = LocalDateTime.of( 2021 , 1 , 23 , 18 , 30 , 45 , 0 );
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.SHORT );
for ( Locale locale : Locale.getAvailableLocales() )
{
String output = ldt.format( f.withLocale( locale ) );
System.out.println( output + " | " + locale.getDisplayName( Locale.US ) + " | " + locale );
}
When run.
…
23.01.21 18:30 | Latvian | lv
23/01/2021, 18:30 | English (Niue) | en_NU
23/01/21 下午6:30 | Chinese (Simplified, Singapore) | zh_SG_#Hans
2021-01-23 6:30 𞤇𞤎 | Fulah (Adlam, Liberia) | ff_LR_#Adlm
23/01/2021, 6:30 pm | English (Jamaica) | en_JM
23/01 2021 18:30 | Kako | kkj
2021-01-23 18:30 | Northern Sami (Latin, Norway) | se_NO_#Latn
2021-01-23 6:30 منجهند، شام | Sindhi (Arabic) | sd__#Arab
23/1/21 18:30 | Spanish (Bolivia) | es_BO
2021-01-23 ཆུ་ཚོད་ 6 སྐར་མ་ 30 ཕྱི་ཆ་ | Dzongkha (Bhutan) | dz_BT
23/1/21, 6:30 পি এম | Manipuri | mni
…
You should first check if the date string matches any of the Predefined Formatters.
If not, then you have to make your own Formatter using .ofPattern(String pattern) or .ofPattern(String pattern, Locale locale):
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yy HH:mm:ss", Locale.US);
System.out.println(formatter.format(LocalDateTime.now()));
Output:
11/13/21 19:41:43
I'm having the same problem. I found this: https://bugs.openjdk.org/browse/JDK-8297504
Basically it says in older unicode CLDR (v33) which java 11 is based on, German terms for AM/PM is nachm. and vorm.
But in CLDR v44 which java17 is based on they changed it to AM/PM.
This is the quote from the ticket(https://bugs.openjdk.org/browse/JDK-8297504):
It turned out that the change came from the upstream CLDR. In CLDR v33 which JDK 11 is based on, "pm" string is localized into German "nachm." (look for "·de·" in the right column): https://www.unicode.org/cldr/cldr-aux/charts/33/by_type/date_&_time.gregorian.html#72c7f54616968b69
whereas in CLDR v42 which the latest JDK is based on, "pm" string in German is "PM": https://unicode-org.github.io/cldr-staging/charts/latest/by_type/date_&_time.gregorian.html#72c7f54616968b69
So the current right way in German based on recent unicode is AM/PM
Creating proper datetime format with correct Chronology and Locale will give am/pm in desired locale. For Germany and Chinese, I was able to get correct data, but for Hindi and Italy it’s not giving expected output. May be this is how data is showed in their respective region.
final String pattern = DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.FULL, FormatStyle.FULL, Chronology.ofLocale(Locale.GERMAN),Locale.GERMAN);
final DateTimeFormatter targetFormat = DateTimeFormatter.ofPattern(pattern).withLocale(Locale.GERMAN);
final String value = ZonedDateTime.now().format(targetFormat);
Two separate problems:
Wrong counts
You're using e.g. MM which is an explicit: Always the full amount of digits, zero-padded. Your string is not like that, it's just the number. So, make that M/d/uuuu h:m:s a.
EDIT: Changed yyyy to uuuu, thanks, @deHaar. Reasoning: yyyy or uuuu rarely matters, but note that this mean 4 digits are required. The difference kicks in for years before 0: uuuu goes negative, yyyy does not and expects you to use e.g. GG so that you get 44 BC instead of -44. In that way, uuuu is just more correct, even though usually the difference is not going to come up.
Missing locale
The second problem is that you should pretty much never use this version of ofPattern - it has a bug, which you can't catch with unit tests, which makes that a bug that is thousands of times 'heavier', and thus, a real problem.
You need to specify locale. Without it, 'AM' is not going to parse unless your platform default locale is english.
Putting it together
LocalDateTime date = LocalDateTime.parse("1/1/2020 3:4:7 AM",
DateTimeFormatter.ofPattern("M/d/uuuu h:m:s a", Locale.ENGLISH));
works great.
In your snippet:
LocalDateTime
.parse("1/1/2020 3:4:7 AM", DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss a"));
1- does not matchMM1- does not matchdd3- does not matchhh4- does not matchmm7- does not matchss
i.e. the lengths of the formatter pattern parts (e.g. MM) and their respective parts from the string text (e.g. 1) do not match.
You can match them in a few ways, e.g. you can either change the string text to match the formatter pattern or other way around.
You can try this instead:
LocalDateTime
.parse("01/01/2020 03:04:07 AM", DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss a"));
Additionally, have a look at the Pattern Letters and Symbols.
This works
public class Timeis {
public static void main(String s[]) {
long ts = 1022895271767L;
SimpleDateFormat sdf = new SimpleDateFormat(" MMM d 'at' hh:mm a");
// CREATE DateFormatSymbols WITH ALL SYMBOLS FROM (DEFAULT) Locale
DateFormatSymbols symbols = new DateFormatSymbols(Locale.getDefault());
// OVERRIDE SOME symbols WHILE RETAINING OTHERS
symbols.setAmPmStrings(new String[] { "am", "pm" });
sdf.setDateFormatSymbols(symbols);
String st = sdf.format(ts);
System.out.println("time is " + st);
}
}
Unfortunately the standard formatting methods don't let you do that. Nor does Joda. I think you're going to have to process your formatted date by a simple post-format replace.
String str = oldstr.replace("AM", "am").replace("PM","pm");
You could use the replaceAll() method that uses regepxs, but I think the above is perhaps sufficient. I'm not doing a blanket toLowerCase() since that could screw up formatting if you change the format string in the future to contain (say) month names or similar.
EDIT: James Jithin's solution looks a lot better, and the proper way to do this (as noted in the comments)
DateTimeFormatter is a Locale-sensitive type i.e. its parsing and formatting depend on the Locale. Check Never use SimpleDateFormat or DateTimeFormatter without a Locale to learn more about it.
If you have an English Locale, and you want the output to be always in a single case (i.e. upper case), you can chain the string operation with the formatted string e.g.
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
System.out.println(
LocalTime.now(ZoneOffset.UTC)
.format(DateTimeFormatter.ofPattern("ha", Locale.ENGLISH))
.toUpperCase()
);
}
}
Output from a sample run:
6AM
ONLINE DEMO
Learn more about the modern Date-Time API* from Trail: Date Time.
* If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring. Note that Android 8.0 Oreo already provides support for java.time.
-Duser.timezone=EDT
-Duser.country=US
-Duser.language=en-US
solved issue