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); }
- Create a new private static method named
getCurrentDateDefaultTimeZone
. - Call the now method inside
LocalDate
API and save it in aLocalDate
object namedcurrentDate
.
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); }
- Create a new private static method named
getCurrentDateSpecificTimeZone
. - 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. - Call the
LocalDate.now(ZoneId zone)
method, passing intokyoZoneId
as an argument. Save the retrievedLocalDate
object in acurrentDateTokyoTZ
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 }
- Create a
dateToParse
string holding a date value in the ISO 8601 date format (YYYY-MM-DD). - Call the LocalDate.parse(CharSequence text) method, passing
dateToParse
as an argument. - Print the
parsedDate
object type to the console using the getClass method inherited from thejava.lang.Object
class. - 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 }
dateUSlocalized
string holding a date localized in US date format.- 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 namedusDateFormatter
. - Call LocalDate.parse(CharSequence text, DateTimeFormatter formatter) to parse
dateUSlocalized
usingusDateFormatter
. - 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 section—LocalDate.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); }
- Retrieve the current date using the
LocalDate.now()
method. Save the acquiredLocalDate
in a variable namedcurrentDate
. - 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
. - Call LocalDate.format(DateTimeFormatter formatter), passing the US-date-localization-holding
usDateFormatter
as its argument. Store the obtained formatted date string asusFormattedDate
. - 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
. - Call LocalDate.format(DateTimeFormatter formatter), passing the France-date-localization-holding
frDateFormatter
as its argument. Store the obtained formatted date string asfrFormattedDate
.
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!