Illustration of Java LocalDate localization

Java LocalDate localization tutorial: step by step examples

Some time ago we explored the basics of Java i18n. But if we were to zoom in a bit further, particularly on Java date localization, we’d see that it used to be quite confusing because of a few poorly designed date libraries prior to Java 8. Thankfully though, it didn’t take forever for Oracle to learn its lesson and introduce a java.time.LocalDate API in its subsequent Date and Time API for Java 8. This overcame all the previous issues and introduced localization-related features right out of the box.

Having your Java application adapt to the date formats the current user reads and types could save you (or your organization) a lot of trouble. Or to put it in practical terms, teaching your Java app to distinguish April Fool’s Day from the 4th of January could save you from an embarrassing situation, don’t you think?

So, in this article, let us take a look at the Java LocalDate API and how it natively supports a collection of localization-related methods.

We will cover the following topics in this tutorial:

  • Java LocalDate API localization/internationalization (l10n/i18n).
  • A step-by-step guide, from creating a simple Java app to utilizing l10n-related LocalDate API methods in it.
  • Retrieving the current date in default and specified time zones.
  • Parsing text in default and localized formats to LocalDate objects.
  • Formatting Java LocalDate objects to text strings in localized date formats.
  • Acquiring the DST-considered start of day for a specific locale.

    Assumptions

    Basic knowledge of the Java language.

    Prerequisites

    A local environment set up with:

    • Java SDK 8+
    • Java supported IDE

    Environment

    I will be using the following environment for my development purposes:

    • JDK 8u301-b09
    • IntelliJ IDEA Ultimate 2019.1.2

    The source code is available on GitHub.

    Simple Java project for pending LocalDate localization

    First up, let’s make a basic Java project where we can test out our LocalDate localizations. Hence, we’ll open our IDE and create a Java project named “JavaLocalDateL10n” inside a directory with the same name.
    Note: You’re free to place said directory in any location you like, but make sure the project uses a JDK version past version 8.

    A Java project won’t become an application without a main method, right? So let’s not forget to place a Main class inside the project’s src folder, along with an empty main method inside it:

    public class Main {
        public static void main(String[] args) { }
    }

    Time to ask LocalDate to localize

    Alright, our JavaLocalDateL10n project is ready to branch out into l10n!

    In the upcoming sections, we’ll explore the Java methods LocalDate API provides translation management system that will prove quite handy for localization purposes.

    Ways to retrieve the current date

    Let’s examine a few ways we can get the current date using LocalDate API, shall we?

    Retrieve current date for default time zone

    To start, let’s ask LocalDate to give us the current time. We’ll go ahead and create a new private static method inside our JavaLocalDateL10n project’s Main class, like so:

    private static void getCurrentDateDefaultTimeZone() {  // 1
        LocalDate currentDate = LocalDate.now();  // 2
        System.out.println("Current date: " + currentDate);
    }
    1. Create a new private static method named getCurrentDateDefaultTimeZone.
    2. Call the now method inside LocalDate API and save it in a LocalDate object named currentDate.

    Secondly, let’s call this newly created getCurrentDateDefaultTimeZone method inside our project’s main method:

    public static void main(String[] args) {
        getCurrentDateDefaultTimeZone();
    }

    Running this code will give us a nice date output representing the current date in the ISO 8601 date format.

    Furthermore, a look at the LocalDate.now() method’s source code would show that it actually calls an overloaded LocalDate.now(Clock clock) method, passing in the default time zone:

    But, wait! Is this retrieved date a global value? Is everyone in the whole world experiencing the exact same date as we speak? I believe you’d agree that no matter where we live, we could either be a day ahead compared to one part of the globe, or a day behind. Let’s see how to tackle this in the next section.

    Retrieve current date for a specified time zone

    LocalDate provides another overloaded now(ZoneId zone) method which takes a ZoneId argument this time around.

    Firstly, let us add a new private static method inside the Main class of our JavaLocalDateL10n project as follows:

    private static void getCurrentDateSpecificTimeZone() {  // 1
        ZoneId tokyoZoneId = ZoneId.of("Asia/Tokyo");  // 2
        LocalDate currentDateTokyoTZ = LocalDate.now(tokyoZoneId);  // 3
        System.out.println("Current date in Tokyo: " + currentDateTokyoTZ);
    }
    1. Create a new private static method named getCurrentDateSpecificTimeZone.
    2. Use the ZoneId.of(String zoneId) method to retrieve the zone ID for the “Asia/Tokyo” time zone. The acquired zone ID is saved inside a tokyoZoneId variable.
    3. Call the LocalDate.now(ZoneId zone)method, passing in tokyoZoneId as an argument. Save the retrieved LocalDate object in a currentDateTokyoTZ variable.

    Secondly, let’s not forget to add this method to our JavaLocalDateL10n project’s main method as well:

    public static void main(String[] args) {
        .
        getCurrentDateSpecificTimeZone();
    }

    In this instance, we can just keep calm and run our app, knowing the date value we retrieve surely represents the current day in Tokyo!

    Parse LocalDate from text

    Be it from a customer of our Java app/service or even from another Java application, we retrieve dates in the form of a serialized plain text string. Hence, let’s find out how to parse one of these date strings to a LocalDate object.

    Parse date using default formatter

    Firstly, let’s head over to the Main class in our JavaLocalDateL10n project and add a private static method to it as below:

    private static void parseDateFromString() {
        String dateToParse = "2018-11-22";  // 1
        LocalDate parsedDate = LocalDate.parse(dateToParse);  // 2
        System.out.println("Parsed object type: " + parsedDate.getClass());  // 3
        System.out.println("Parsed date: " + parsedDate.toString());  // 4
    }
    1. Create a dateToParse string holding a date value in the ISO 8601 date format (YYYY-MM-DD).
    2. Call the LocalDate.parse(CharSequence text) method, passing dateToParse as an argument.
    3. Print the parsedDate object type to the console using the getClass method inherited from the java.lang.Object class.
    4. Print the date value stored in parsedDate using the LocalDate.toString method.

    Note: In step 4, explicitly calling .toString() isn’t mandatory since Java implicitly calls an object’s toString() method when concatenating Strings. Thus, replacing the 4th line as follows would work just the same:

    System.out.println("Parsed date: " + parsedDate);  // 4

    Secondly, let’s add this parseDateFromString method to the main method of our JavaLocalDateL10n project:

    public static void main(String[] args) {
        .
        parseDateFromString();
    }

    Running our project would show that the parsed object is indeed a java.time.LocalDate object holding our date:

     

    Parse date using localized date formatter

    Okey-dokey! We learned how to parse a date using LocalDate. But, imagine a situation like this:

    Our JavaLocalDateL10n project is for an e-commerce site that sells and delivers greeting cards online. And, assume our site asks the customer to input the delivery date of their order in a text input field. Phoebe, from the U.S., orders a sweet Mother’s Day card for her lovely mother and waits to surprise her on the 9th of May 2021. But, much to her dismay, the greetings card never arrives at her mom’s address.

    Why?

    Simply because Phoebe ordered her card to reach her mother precisely on “05/09/2021”; which is, the 9th of May the way she reads dates in the United States—in the MM/DD/YYYY format. But, our non-localized JavaLocalDateL10n app tried to parse it in the default ISO 8601 date format (YYYY-MM-DD); so obviously, no card order was placed in the system since no date was parsed that day!

    So, how can our poor JavaLocalDateL10n project overcome this? Well, Java developers have conveniently placed an overloaded LocalDate.parse() method in the LocalDate API just for this use case. The LocalDate.parse(CharSequence text, DateTimeFormatter formatter) method helps our localization efforts by letting us additionally pass a DateTimeFormatter argument holding the localized format we need LocalDate to use when parsing the date string.

    Firstly, let’s go ahead and add a new private static method to our JavaLocalDateL10n project’s Main class as follows:

    private static void parseDateFromStringSpecificFormatter() {
        String dateUSLocalized = "05/09/2021";  // 1
        DateTimeFormatter usDateFormatter = DateTimeFormatter.ofPattern("MM/dd/uuuu");  // 2
        LocalDate parsedDate = LocalDate.parse(dateUSLocalized, usDateFormatter);  // 3
        System.out.println("Parsed date provided in US-localized date format: " + parsedDate);  // 4
    }
    1. dateUSlocalized string holding a date localized in US date format.
    2. Pass the US date format to DateTimeFormatter.ofPattern(String pattern) to create a formatter holding the US-localized date pattern. Save the retrieved DateTimeFormatter object as a variable named usDateFormatter.
    3. Call LocalDate.parse(CharSequence text, DateTimeFormatter formatter) to parse dateUSlocalized using usDateFormatter.
    4. Print the date value stored in parsedDate using the LocalDate.toString method.

    Secondly, let’s make sure to also call parseDateFromStringSpecificFormatter inside the main method of our JavaLocalDateL10n project:

    public static void main(String[] args) {
        .
        parseDateFromStringSpecificFormatter();
    }

    Let’s run this code and see:

    You must have noticed that the parsed LocalDate output doesn’t hold a US localized date format. This is simply because—as we also discussed in the previous sectionLocalDate.toString is implicitly called at the string concatenation in step 4, which outputs the date in the ISO 8601 format.

    Format LocalDate to localized date text

    Okay, we learned how to parse a LocalDate using a text string. But, what about the reverse? Can’t we transform an already initialized LocalDate object holding a date, back into a regular Java String?

    Of course, as we discussed—and used—in the previous section, we can employ the almighty LocalDate.toString method on any LocalDate object to get it in its string form. But, the issue is, the returned date string will always be in the ISO 8601 format (YYYY-MM-DD). How can we retrieve a localized date string, instead?

    In this case, we need some help from DateTimeFormatter. Let’s see how it works!

    Firstly, let’s open up the Main class of our JavaLocalDateL10n project and add a private static method to it:

    private static void formatLocalDateObjToLocalizedDateStr() {
        LocalDate currentDate = LocalDate.now();  // 1
    
        DateTimeFormatter usDateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(Locale.US);  // 2
        String usFormattedDate = currentDate.format(usDateFormatter);  // 3
        System.out.println("Current date in en-US date format: " + usFormattedDate);
    
        DateTimeFormatter frDateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(Locale.FRANCE);  // 4
        String frFormattedDate = currentDate.format(frDateFormatter);  // 5
        System.out.println("Current date in fr-FR date format: " + frFormattedDate);
    }
    1. Retrieve the current date using the LocalDate.now() method. Save the acquired LocalDate in a variable named currentDate.
    2. Use DateTimeFormatter.ofLocalizedDate(FormatStyle dateStyle) to get a date formatter with a FormatStyle.FULL text style. Call the withLocale(Locale locale) method of the date formatter to set its locale to Locale.US. Save the retrieved formatter as usDateFormatter.
    3. Call LocalDate.format(DateTimeFormatter formatter), passing the US-date-localization-holding usDateFormatter as its argument. Store the obtained formatted date string as usFormattedDate.
    4. Similar to step 2, use DateTimeFormatter.ofLocalizedDate(FormatStyle dateStyle) to get a date formatter with a FormatStyle.FULL text style. Call the withLocale(Locale locale) method of the date formatter to set its locale to Locale.FRANCE. Save the retrieved formatter as frDateFormatter.
    5. Call LocalDate.format(DateTimeFormatter formatter), passing the France-date-localization-holding frDateFormatter as its argument. Store the obtained formatted date string as frFormattedDate.

    Secondly, let’s call this newly formed formatLocalDateObjToLocalizedDateStr method inside our project’s main method:

    public static void main(String[] args) {
        .
        formatLocalDateObjToLocalizedDateStr();
    }

    That’s it! Let’s run our JavaLocalDateL10n app to test it out:

    Evidently, we have retrieved the current date stored in LocalDate localized to both the en-US and fr-FR date formats!

    Get a time-zone-aware start of day

    When does the day start? At what time?

    You might be thinking…

    At 12 midnight Sherlock, what other time do you think it starts?

    Well, when daylight saving time (DST) is added into the equation, things could become a bit more complicated. Usually, DST is observed a few hours past midnight, but there’s no hard and fast rule for it. Ergo, some places like Cuba, Jordan, and Palestine could observe their +1 hour spring-forward days at midnight, making the day start at 1 o’clock!

    DateTime provides an overloaded atStartOfDay(ZoneId zone) method which gives us a straightforward solution to this concern.

    Firstly, let’s add a new getLocalizedStartOfDay private static method to our JavaLocalDateL10n app’s Main class, like so:

    private static void getLocalizedStartOfDay() {
        LocalDate springForwardedDate = LocalDate.of(2022,3,13);  // 1
        ZonedDateTime startOfSpringForwardedDate = springForwardedDate.atStartOfDay(ZoneId.of("Cuba"));  // 2
        System.out.println("Start of day for +1 hour spring forwarded day (13-03-2022) in Cuba: " + startOfSpringForwardedDate);
        System.out.println("Day starts at " + startOfSpringForwardedDate.getHour());
    }

    Use the LocalDate.of(int year, int month, int dayOfMonth) method to retrieve a LocalDate for the date 2022-03-13; Cuba observes DST on this particular date at 12:00 am. Store the acquired LocalDate object in a springForwardedDate variable.

    Call atStartOfDay() on springForwardedDate, passing the ZoneId of Cuba for its parameter value. This method call acquires a ZonedDateTime object holding the following:

    Date stored in springForwardedDate + DST-aware time that this day starts for the given locale

     

    Secondly, let us be sure to add this getLocalizedStartOfDay method inside the main method of our JavaLocalDateL10n project:

    public static void main(String[] args) {
        .
        getLocalizedStartOfDay();
    }

    Running our project shows that LocalDate.atStartOfDay has successfully initialized a ZonedDateTime object holding the DST-aware start of day:

    Easier LocalDate localization with Lokalise

    As we saw in this LocalDate context, that even handling a simple date in a Java app could get quite tricky and clear as mud when localization is added to the mix.

    But, what if I told you there’s a far easier, 1000x faster, and more convenient, way to handle LocalDate localization?

    Meet Lokalise, the translation management system that takes care of all your Java app’s internationalization wants and needs! With features like:

    • Easy integration with various other services
    • Collaborative translations
    • Quality assurance tools for translations
    • Easy management of your translations through a central dashboard
    • Plus, loads of others

    Lokalise will make your life a whole lot easier by letting you expand your Java l10n-powered app to all the locales you’ll ever plan to reach.

    Start with Lokalise in just a few steps:

    • Sign up for a free trial (no credit card information required).
    • Log in to your account.
    • Create a new project under any name you like.
    • Upload your translation files and edit them as required.

    End of story! You have already completed the baby steps toward Lokalise-ing your Java application. See the Getting Started section for a collection of articles that will provide all the help you’ll need to kick-start your Lokalise journey. Also, refer to Lokalise API Documentation for a complete list of REST commands you can call on your Lokalise localization project.

    Conclusion

    In this tutorial, we took a peek at the LocalDate API-related date localization in Java apps. We created a simple Java application where we set foot on LocalDate learning to acquire the current date for a specified time zone. We discovered how to parse text in the ISO 8601 date format to a LocalDate object, and further parsed a date text in a localized date format as well.

    Later on, we discerned ways that LocalDate can format one of its instances back to a date text with help from DateTimeFormatter. Additionally, we found out how the atStartOfDay method assists us in acquiring the DST-considered start of day for a particular region.

    So with that, it’s time for another wrap-up! Drop me a line if you have any questions.

    Till we meet again, you may LocalDate at the office or go on a date in public, but always be sure to stick to the pandemic safety guidelines!

    Related articles
    Stop wasting time with manual localization tasks. 

    Launch global products days from now.