In a DateTimeFormatter pattern, the letters hh print the hour on a 12-hour clock, from 01 to 12, and the letter a prints the AM/PM marker, so the pattern hh:mm a gives the 12-hour format with AM/PM and turns 19:35 into 07:35 PM. The same formatter also parses 12-hour text back into a LocalTime.
We use the Java 12-hour format with AM/PM for times that people read, such as a table reservation in a restaurant booking app or the start time in a meeting reminder, mostly for readers in the US, Canada, India and Australia, where the 12-hour clock is common.
The following example formats an evening time, midnight and noon, and parses a 12-hour string.
DateTimeFormatter twelveHour = DateTimeFormatter.ofPattern("hh:mm a", Locale.US);
String evening = LocalTime.of(19, 35).format(twelveHour); // "07:35 PM"
String midnight = LocalTime.MIDNIGHT.format(twelveHour); // "12:00 AM"
String noon = LocalTime.NOON.format(twelveHour); // "12:00 PM"
LocalTime parsed = LocalTime.parse("07:35 PM", twelveHour); // 19:35
Notice the Locale.US argument, because the AM/PM text comes from the locale. The rest of the post compares the hour letters, formats dates and zoned times, explains the locale differences and a Java 20 change in localized formats, and converts between 12-hour and 24-hour text.
1. Hour Letters for 12-Hour and 24-Hour Clocks
A DateTimeFormatter pattern has four letters for the hour, and they differ in two ways. The letters h and K count on a 12-hour clock, while H and k count on a 24-hour clock, and the pairs differ in whether the count starts at 0 or 1.

| Letter | Clock | 00:05 | 07:05 | 12:05 | 19:35 |
|---|---|---|---|---|---|
| h / hh | 12-hour, 1 to 12 | 12:05 AM | 7:05 AM | 12:05 PM | 7:35 PM |
| K / KK | 12-hour, 0 to 11 | 0:05 AM | 7:05 AM | 0:05 PM | 7:35 PM |
| H / HH | 24-hour, 0 to 23 | 0:05 | 7:05 | 12:05 | 19:35 |
| k / kk | 24-hour, 1 to 24 | 24:05 | 7:05 | 12:05 | 19:35 |
The pattern for the common 12-hour format is h or hh together with a; hh adds a leading zero, so 7 o’clock prints as 07. The letters K and k are rare; we use them only when a system expects “0:05 PM” or “24:05”.
Two mistakes compile and run without an error. The pattern HH:mm a prints 19:35 PM, a 24-hour value with a marker, and hh:mm without a prints 07:35 for both 7:35 in the morning and in the evening.
String mixed = LocalTime.of(19, 35).format(DateTimeFormatter.ofPattern("HH:mm a", Locale.US)); // "19:35 PM"
String noMarker = LocalTime.of(19, 35).format(DateTimeFormatter.ofPattern("hh:mm")); // "07:35"
String twoA = LocalTime.of(19, 35).format(DateTimeFormatter.ofPattern("hh:mm aa")); // IllegalArgumentException: Too many pattern letters: a
The third line throws because DateTimeFormatter accepts only one a. Patterns copied from SimpleDateFormat code, where aa was allowed, need that fix during migration.
2. Formatting Dates and Zoned Times With AM/PM
The pattern works the same way for every class that has a time of day, such as LocalTime, LocalDateTime and ZonedDateTime. We add the date letters and, for a zoned value, a zone letter.
LocalDateTime reservation = LocalDateTime.of(2026, 3, 14, 19, 35);
String confirmation = reservation.format(DateTimeFormatter.ofPattern("MMM d, uuuu h:mm a", Locale.US)); // "Mar 14, 2026 7:35 PM"
ZonedDateTime newYork = reservation.atZone(ZoneId.of("America/New_York"));
String zoned = newYork.format(DateTimeFormatter.ofPattern("MMM d, h:mm a z", Locale.US)); // "Mar 14, 7:35 PM EDT"
ZonedDateTime tokyo = newYork.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
String tokyoText = tokyo.format(DateTimeFormatter.ofPattern("MMM d, h:mm a z", Locale.US)); // "Mar 15, 8:35 AM JST"
A restaurant booking app in New York confirms a table reservation to a guest whose phone is set to Tokyo time. The booking stores the moment, and the app formats it in the restaurant’s zone, because 8:35 AM on the next day would confuse the guest. The ZonedDateTime format article covers the zone letters.
For the current time, we call LocalTime.now() with an explicit zone, because the default zone of a server is often UTC.
LocalTime nowInChicago = LocalTime.now(ZoneId.of("America/Chicago"));
String clock = nowInChicago.format(DateTimeFormatter.ofPattern("h:mm a", Locale.US)); // current Chicago time, such as "9:41 AM"
3. AM/PM Text in Other Locales
The letter a prints the day-period text of the formatter’s locale, and that text differs even between English-speaking countries. A formatter without a locale uses the JVM default locale, so the same code prints PM on a laptop in the US and pm on a server in London.
LocalTime evening = LocalTime.of(19, 35);
String us = evening.format(DateTimeFormatter.ofPattern("hh:mm a", Locale.US)); // "07:35 PM"
String uk = evening.format(DateTimeFormatter.ofPattern("hh:mm a", Locale.UK)); // "07:35 pm"
String canada = evening.format(DateTimeFormatter.ofPattern("hh:mm a", Locale.CANADA)); // "07:35 p.m."
String india = evening.format(DateTimeFormatter.ofPattern("hh:mm a", Locale.of("en", "IN"))); // "07:35 pm"
String germany = evening.format(DateTimeFormatter.ofPattern("hh:mm a", Locale.GERMANY)); // "07:35 PM"
The marker texts come from the Unicode CLDR locale data, which the JDK uses by default since Java 9, so they can change with a JDK upgrade. When a log file or an API needs a fixed AM and PM whatever the locale, we map the values ourselves with appendText().
DateTimeFormatter fixedMarker = new DateTimeFormatterBuilder()
.appendPattern("hh:mm ")
.appendText(ChronoField.AMPM_OF_DAY, Map.of(0L, "AM", 1L, "PM"))
.toFormatter(Locale.UK);
String alwaysUpper = LocalTime.of(19, 35).format(fixedMarker); // "07:35 PM"
4. Localized Time Styles and the Narrow Space Before PM
Instead of a pattern, we can ask the locale for its own short time style with DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT). For Locale.US, that style is a 12-hour time, and for Locale.GERMANY it is a 24-hour time, so one formatter fits each user.
DateTimeFormatter shortUs = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(Locale.US);
String usText = LocalTime.of(19, 35).format(shortUs); // 7:35 PM, with U+202F before PM
boolean plainSpace = usText.equals("7:35 PM"); // false
String german = LocalTime.of(19, 35).format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(Locale.GERMANY)); // "19:35"
Since Java 20, the JDK uses CLDR version 42, which puts a narrow no-break space (U+202F) between the time and AM or PM instead of a normal space (JDK-8284840). The text looks the same on screen, but string comparisons in tests fail, and parsing a time typed with a normal space fails too.
DateTimeFormatter shortUs = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(Locale.US);
LocalTime typed = LocalTime.parse("7:35 PM", shortUs); // DateTimeParseException at index 4
DateTimeFormatter loose = new DateTimeFormatterBuilder()
.parseLenient()
.appendLocalized(null, FormatStyle.SHORT)
.toFormatter(Locale.US);
LocalTime accepted = LocalTime.parse("7:35 PM", loose); // 19:35
Since Java 23, lenient parsing matches any space character against a space separator, which is why the second formatter accepts the normal space. For text that a program reads, such as CSV files or JSON, we use an explicit pattern like h:mm a instead of a localized style.
5. Day Periods Such as “in the evening”
Since Java 16, the pattern letter B prints a day period, which is a friendlier text than AM or PM for messages and chat apps. The periods and their limits also come from the locale.
DateTimeFormatter period = DateTimeFormatter.ofPattern("h:mm B", Locale.US);
String morning = LocalTime.of(9, 0).format(period); // "9:00 in the morning"
String afternoon = LocalTime.of(15, 0).format(period); // "3:00 in the afternoon"
String night = LocalTime.of(22, 0).format(period); // "10:00 at night"
String midday = LocalTime.NOON.format(period); // "12:00 noon"
The day period article lists the periods for other locales.
6. Converting Between 24-Hour and 12-Hour Text
Converting a time string from one clock to the other is a parse with one formatter and a format with another. The LocalTime in the middle holds the time without any format, so no manual arithmetic on hours is needed.
DateTimeFormatter h24 = DateTimeFormatter.ofPattern("HH:mm");
DateTimeFormatter h12 = DateTimeFormatter.ofPattern("hh:mm a", Locale.US);
String to12 = LocalTime.parse("19:35", h24).format(h12); // "07:35 PM"
String to24 = LocalTime.parse("07:35 PM", h12).format(h24); // "19:35"
String midnight24 = LocalTime.parse("12:15 AM", h12).format(h24); // "00:15"
Parsing 12-hour text has its own rules. The input must contain the marker in the case the locale uses, the hour must be between 1 and 12, and hh requires two digits, so we use h for input such as 7:35 PM.
DateTimeFormatter h12 = DateTimeFormatter.ofPattern("hh:mm a", Locale.US);
LocalTime lowercase = LocalTime.parse("07:35 pm", h12); // DateTimeParseException at index 6
LocalTime hour13 = LocalTime.parse("13:35 PM", h12); // DateTimeParseException: Invalid value for ClockHourOfAmPm
LocalTime oneDigit = LocalTime.parse("7:35 PM", DateTimeFormatter.ofPattern("h:mm a", Locale.US)); // 19:35
DateTimeFormatter anyCase = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendPattern("h:mm a")
.toFormatter(Locale.US);
LocalTime relaxed = LocalTime.parse("07:35 pm", anyCase); // 19:35
For more parsing examples with AM/PM in a full date-time string, the String to LocalDateTime article has a section on 12-hour input.
7. 12-Hour Format FAQs
Most AM/PM surprises come from the locale and from mixing the 12-hour and 24-hour letters.
7.1. What is the difference between hh and HH in Java?
The letters hh print the hour on a 12-hour clock (01 to 12) and need a next to them, whereas HH prints the hour on a 24-hour clock (00 to 23). The time 19:35 prints as 07 with hh and as 19 with HH.
7.2. Why does the output show pm or p.m. instead of PM?
Because the formatter uses a locale whose AM/PM text is written that way, often the JVM default locale. We pass Locale.US to ofPattern(), or map the text ourselves as in section 3.
7.3. How do we check if a time is AM or PM?
We read the field ChronoField.AMPM_OF_DAY, which is 0 for AM and 1 for PM.
int marker = LocalTime.of(19, 35).get(ChronoField.AMPM_OF_DAY); // 1
boolean isMorning = LocalTime.of(9, 0).isBefore(LocalTime.NOON); // true
7.4. How do we format a java.util.Date in 12-hour format?
We convert it to an Instant, add the zone, and use the same DateTimeFormatter, so the old SimpleDateFormat is not needed.
Date legacy = Date.from(Instant.parse("2026-03-14T19:35:00Z"));
String fromDate = legacy.toInstant().atZone(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("hh:mm a", Locale.US)); // "07:35 PM"
8. Conclusion
The 12-hour format in Java is the pattern h:mm a or hh:mm a, where h counts from 1 to 12 and a adds the AM/PM marker; midnight prints as 12 AM and noon as 12 PM. We always give the formatter a locale, because the marker text depends on it.
Localized styles adapt to each user but use a narrow no-break space before AM and PM since Java 20, so machine-readable text gets an explicit pattern. Converting between the clocks is a parse and a format through LocalTime. The remaining pattern letters are in the DateTimeFormatter guide, and the Java date and time tutorial lists the related formatting tasks.
9. References
- DateTimeFormatter (Java SE 25 API)
- DateTimeFormatterBuilder (Java SE 25 API)
- JDK-8284840, CLDR version 42
- JDK-8324665, loose matching of space separators
Happy Learning !!