Localized Date Format in Java with Locale and Time Zone

Localized date format in Java with DateTimeFormatter, FormatStyle and Locale. Show timestamps in the user’s time zone and use ofLocalizedPattern().

Diagram showing an Instant converted to a ZonedDateTime with a ZoneId and then formatted with a Locale into US, German and Japanese text

A localized date format in Java is a DateTimeFormatter created with ofLocalizedDate(), ofLocalizedTime() or ofLocalizedDateTime() and a Locale, so one date prints in the order, words and separators that readers of that language expect. We pick a length with FormatStyle (SHORT, MEDIUM, LONG or FULL) and never write the pattern ourselves.

We use localized formats on websites, emails and reports that reach users in more than one country, for example, an online course platform that shows the same class date to students in New York, Berlin and Tokyo.

The following example prints one class date for four locales with the same MEDIUM style.

LocalDate classDate = LocalDate.of(2026, 3, 14);
DateTimeFormatter medium = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
String us = classDate.format(medium.withLocale(Locale.US));             // "Mar 14, 2026"
String german = classDate.format(medium.withLocale(Locale.GERMANY));    // "14.03.2026"
String french = classDate.format(medium.withLocale(Locale.FRANCE));     // "14 mars 2026"
String japanese = classDate.format(medium.withLocale(Locale.JAPAN));    // "2026/03/14"

Notice that we wrote no pattern at all. The JDK takes the layout of each locale from the Unicode CLDR data, and we cover the four styles, the time zone step for timestamps, the user locale in a Spring app, the flexible ofLocalizedPattern() method from Java 19 and the parsing traps of localized text.

1. Display Locale-formatted Date and Time

A localized formatter answers one question, namely how people who speak a language write a date. Americans write the month first, Germans separate the parts with dots, and the French use lowercase month names. A Locale changes only the text, never the date or the time value, so converting a moment to the user’s time zone is a separate step.

Diagram showing an Instant converted to a ZonedDateTime with a ZoneId and then formatted with a Locale into US, German and Japanese text
The ZoneId decides which hour the user sees; the Locale decides how that hour is written

The table shows the four styles for 14 March 2026 in three locales. A SHORT date is numeric, MEDIUM abbreviates the month, LONG spells it out, and FULL adds the day of the week.

FormatStyleLocale.USLocale.UKLocale.GERMANY
SHORT3/14/2614/03/202614.03.26
MEDIUMMar 14, 202614 Mar 202614.03.2026
LONGMarch 14, 202614 March 202614. März 2026
FULLSaturday, March 14, 2026Saturday, 14 March 2026Samstag, 14. März 2026

1.1. ofLocalizedDate(), ofLocalizedTime() and ofLocalizedDateTime()

The three factory methods format the date part, the time part or both. The method ofLocalizedDateTime() also accepts two styles, one for the date and one for the time, which suits a calendar entry such as a long date with a short time.

LocalDateTime start = LocalDateTime.of(2026, 3, 14, 17, 45, 30);
String ukTime = start.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(Locale.UK));          // "17:45"
String usTime = start.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(Locale.US));          // "5:45 PM"
String deBoth = start.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.GERMANY));   // "14.03.2026, 17:45:30"
String mixed = start.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT).withLocale(Locale.US));   // "March 14, 2026, 5:45 PM"

The LONG and FULL time styles print a time zone name, so they work only with a ZonedDateTime or a formatter that has a zone. Formatting a LocalDateTime with them throws a DateTimeException.

LocalDateTime noZone = LocalDateTime.of(2026, 3, 14, 17, 45, 30);
String fails = noZone.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG).withLocale(Locale.US));   // DateTimeException: Unable to extract ZoneId from temporal 2026-03-14T17:45:30
ZonedDateTime withZone = noZone.atZone(ZoneId.of("America/New_York"));
String longText = withZone.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG).withLocale(Locale.US));   // "March 14, 2026, 5:45:30 PM EDT"

If we do not call withLocale(), the formatter uses the default FORMAT locale of the JVM. On a server, that default comes from the operating system and rarely matches the reader, so we always pass the locale of the user.

1.2. withLocale() vs localizedBy()

A locale can carry Unicode extensions, such as -u-nu-arab for the numbering system or -u-ca-japanese for the calendar. The method withLocale() uses the locale only for names and layout. The method localizedBy(), added in Java 10, also applies these extensions, so digits and the calendar change too.

LocalDate day = LocalDate.of(2026, 3, 14);
DateTimeFormatter longDate = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);
Locale japaneseCalendar = Locale.forLanguageTag("ja-JP-u-ca-japanese");
String gregorian = day.format(longDate.withLocale(japaneseCalendar));      // "2026年3月14日"
String imperial = day.format(longDate.localizedBy(japaneseCalendar));      // "令和8年3月14日"

The second line prints the year as Reiwa 8 of the Japanese imperial calendar. We use localizedBy() when users can choose a calendar or a numbering system in their profile, and withLocale() when we only need the language.

2. Showing a Timestamp in the User’s Time Zone

Applications store a timestamp once, as an Instant or as UTC in the database, and show it to many users. Each user expects the time in their own zone and in their own language, which needs both a ZoneId and a Locale.

Say a customer places an order at 21:45 UTC, and the shop sends confirmation emails to a team in Berlin, Los Angeles and Tokyo. We give the formatter the zone with withZone(), which converts the instant to that zone before formatting.

Instant placedAt = Instant.parse("2026-03-14T21:45:30Z");
DateTimeFormatter base = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
String berlin = base.withLocale(Locale.GERMANY).withZone(ZoneId.of("Europe/Berlin")).format(placedAt);   // "14.03.2026, 22:45:30"
String losAngeles = base.withLocale(Locale.US).withZone(ZoneId.of("America/Los_Angeles")).format(placedAt);   // "Mar 14, 2026, 2:45:30 PM"
String tokyo = base.withLocale(Locale.JAPAN).withZone(ZoneId.of("Asia/Tokyo")).format(placedAt);       // "2026/03/15 6:45:30"

The Tokyo team sees 15 March, because it is already the next morning there. The same conversion with a ZonedDateTime is placedAt.atZone(zone), and Convert Date Between Time Zones covers the zone rules in detail.

2.1. Converting on the Server or in the Browser

An HTTP request carries the user’s language in the Accept-Language header, but it carries no time zone. So we choose one of two designs.

  • The server converts and formats the timestamp. It needs the user’s ZoneId, which we save in the user profile at sign-up or send from the browser once. Emails, PDFs and server-rendered pages need this design.
  • The server sends an ISO-8601 UTC string, such as 2026-03-14T21:45:30Z, and JavaScript in the browser formats it with the zone and the language of the device. Single-page apps and mobile apps use this design.

In the browser, Intl.DateTimeFormat returns the IANA zone ID of the device, which we can also post to the server and store as a ZoneId.

const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;   // "Europe/Berlin"
const text = new Date("2026-03-14T21:45:30Z").toLocaleString();   // "14.3.2026, 22:45:30" in a German browser

We do not derive the zone from Date.getTimezoneOffset() in JavaScript. An offset such as +60 minutes fits several zones with different daylight saving rules, whereas a zone ID such as Europe/Berlin keeps the rules for every date.

3. Getting the User Locale

In a desktop or command-line program, the user locale is the JVM default, which Getting the Current Locale explains. In a web application, the browser sends the preferred languages in the Accept-Language header, and the framework turns the header into a Locale.

In Spring MVC, an AcceptHeaderLocaleResolver bean reads the header and picks the best match from the locales our app supports.

@Bean
public LocaleResolver localeResolver() {
    AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
    resolver.setSupportedLocales(List.of(Locale.US, Locale.GERMANY, Locale.JAPAN));
    resolver.setDefaultLocale(Locale.US);
    return resolver;
}

Inside a request, LocaleContextHolder.getLocale() returns the resolved locale, so a service method can format a timestamp without a request parameter. The full setup with message files is in Spring Boot REST i18n.

public String confirmationTime(Instant placedAt, ZoneId userZone) {
    Locale locale = LocaleContextHolder.getLocale();
    return DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
            .withLocale(locale)
            .withZone(userZone)
            .format(placedAt);
}

4. Using ofLocalizedPattern() since Java 19

The four styles do not cover every layout. A course card may need only the month and the year, or the month and the day without the year. Since Java 19, ofLocalizedPattern() takes a template of the fields we want, such as yMMM, and the JDK picks the best layout for the locale from CLDR.

LocalDate exam = LocalDate.of(2026, 3, 14);
String usMonth = exam.format(DateTimeFormatter.ofLocalizedPattern("yMMM").withLocale(Locale.US));          // "Mar 2026"
String deMonth = exam.format(DateTimeFormatter.ofLocalizedPattern("yMMM").withLocale(Locale.GERMANY));     // "März 2026"
String usDay = exam.format(DateTimeFormatter.ofLocalizedPattern("MMMd").withLocale(Locale.US));            // "Mar 14"
String ukDay = exam.format(DateTimeFormatter.ofLocalizedPattern("MMMd").withLocale(Locale.UK));            // "14 Mar"
String frWeekday = exam.format(DateTimeFormatter.ofLocalizedPattern("yMMMEd").withLocale(Locale.FRANCE));  // "sam. 14 mars 2026"

A template is not a pattern. It lists the fields from the largest to the smallest unit, in the order era, year, quarter, month, week, day of week, day, day period, hour, minute, second and zone, and it may contain only the letters G y Q M w E d B h H j C m s v z. The letter j asks for the hour in the 12-hour or 24-hour clock of the locale. Any other letter or order throws an IllegalArgumentException.

DateTimeFormatter wrongOrder = DateTimeFormatter.ofLocalizedPattern("MMMy");   // IllegalArgumentException: Requested template is invalid: MMMy
String clock = LocalTime.of(17, 45).format(DateTimeFormatter.ofLocalizedPattern("jm").withLocale(Locale.GERMANY));   // "17:45"

5. Parsing Localized Text Safely

A localized formatter also parses text, but the input must match the CLDR layout character by character. Since Java 20 and CLDR 42, US English times contain a narrow no-break space (U+202F) before AM and PM, so text typed with a regular space no longer parses.

DateTimeFormatter usShort = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).withLocale(Locale.US);
LocalTime typed = LocalTime.parse("5:45 PM", usShort);         // DateTimeParseException: Text '5:45 PM' could not be parsed at index 4
LocalTime exact = LocalTime.parse("5:45\u202FPM", usShort);    // 17:45

Since Java 23, a lenient formatter matches any space character with any other. We build it with parseLenient() on a DateTimeFormatterBuilder when the text comes from people, and keep the strict formatter for text that our own code produced.

DateTimeFormatter lenient = new DateTimeFormatterBuilder()
        .parseLenient()
        .append(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT))
        .toFormatter(Locale.US);
LocalTime fromUser = LocalTime.parse("5:45 PM", lenient);      // 17:45

For data exchange between systems, we avoid localized text altogether and use the ISO formats, which do not depend on a locale or a CLDR version. LocalDateTime parse() shows ISO parsing.

6. Localized Date Format FAQs

A second language in an app brings up time zones, JDK upgrades, month names and the choice between patterns and styles.

6.1. Does setting a Locale change the time zone of a date?

No. A Locale changes the language and the layout of the text. The time zone comes only from the date object, such as a ZonedDateTime, or from withZone() on the formatter, so a user in India with Locale.UK still needs ZoneId.of(“Asia/Kolkata”).

6.2. Why does the same code print different text on Java 8 and Java 11?

Since Java 9, the JDK uses CLDR locale data by default (JEP 252), whereas Java 8 used its own older data. Newer JDKs also update CLDR, for example, Java 20 added the narrow space before AM and PM, and Java 23 removed the old COMPAT data, so tests that compare localized strings break after upgrades. We compare parsed values in tests, or use fixed patterns where the exact text matters.

6.3. How do we print a month name in another language?

We format with the pattern MMMM and a locale, or call Month.getDisplayName(). Both read the names from the same CLDR data.

String spanish = Month.MARCH.getDisplayName(TextStyle.FULL, Locale.forLanguageTag("es"));   // "marzo"
String italian = LocalDate.of(2026, 3, 14).format(DateTimeFormatter.ofPattern("d MMMM", Locale.ITALY));   // "14 marzo"

6.4. Should we use ofPattern() with a Locale or ofLocalizedDate()?

We use ofLocalizedDate() or ofLocalizedPattern() for text that people read, because they also change the order of the fields. A pattern such as dd MMMM yyyy with Locale.US translates the month name but keeps the day first, which looks wrong to American readers.

7. Conclusion

Localized formats come from ofLocalizedDate(), ofLocalizedTime(), ofLocalizedDateTime() and, since Java 19, ofLocalizedPattern(). We pass the user’s Locale with withLocale(), or with localizedBy() when calendar and digit extensions matter.

To show a stored timestamp, we add the user’s ZoneId with withZone(), because the locale never moves the time. Tests that compare localized text break with new CLDR versions, so we parse leniently from user input and use ISO formats between systems. Every other formatting topic is collected in the java.time tutorial hub, and Java DateTimeFormatter covers custom patterns.

8. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.