In this tutorial, we'll be taking an in-depth tour of the SimpleDateFormat class.
We'll take a look at simple instantiation and formatting styles as well as useful methods the class exposes for handling locales and time zones.
2. Simple Instantiation
First, let's look at how to instantiate a new SimpleDateFormat object.
Let's start with a dash-separated date pattern like so:
This will correctly format a date starting with the current day of the month, current month of the year, and finally the current year. We can test our new formatter with a simple unit test. We'll instantiate a new SimpleDateFormat object, and pass in a known date:
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy"); assertEquals("24-05-1977", formatter.format(new Date(233345223232L)));
In the above code, the formatter converts milliseconds as long into a human-readable date – the 24th of May, 1977.
2.1. Factory Methods
Although SimpleDateFormat is a handy class to quickly build a date formatter, we're encouraged to use the factory methods on the DateFormat class getDateFormat(), getDateTimeFormat(), getTimeFormat().
The above example looks a little different when using these factory methods:
DateFormat formatter = DateFormat.getDateInstance(DateFormat.SHORT); assertEquals("5/24/77", formatter.format(new Date(233345223232L)));
As we can tell from above, the number of formatting options is pre-determined by the fields on the DateFormat class. This largely restricts our available options for formatting which is why we'll be sticking to SimpleDateFormat in this article.
The JavaDoc for SimpleDateFormat explicitly states:
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
So SimpleDateFormat instances are not thread-safe, and we should use them carefully in concurrent environments.
The best approach to resolve this issue is to use them in combination with a ThreadLocal. This way, each thread ends up with its own SimpleDateFormat instance, and the lack of sharing makes the program thread-safe:
private final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal .withInitial(() -> new SimpleDateFormat("dd-MM-yyyy"));
The argument for the withInitial method is a supplier of SimpleDateFormat instances. Every time the ThreadLocal needs to create an instance, it will use this supplier.
Then we can use the formatter via the ThreadLocal instance:
The ThreadLocal.get() method initializes the SimpleDateFormat for the current thread at first and then reuses that instance.
We call this technique thread confinement as we confine the use of each instance to one specific thread.
There are two other approaches to tackle the same problem:
- Using synchronized blocks or ReentrantLocks
- Creating throw away instances of SimpleDateFormat on-demand
Both of these approaches are not recommended: The former incurs a significant performance hit when the contention is high, and the latter creates a lot of objects, putting pressure on garbage collection.
It's worthwhile to mention that, since Java 8, a new DateTimeFormatter class has been introduced. The new DateTimeFormatter class is immutable and thread-safe. If we're working with Java 8 or later, using the new DateTimeFormatter class is recommended.
3. Parsing Dates
SimpleDateFormat and DateFormat not only allow us to format dates – but we can also reverse the operation. Using the parse method, we can input the String representation of a date and return the Date object equivalent:
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy"); Date myDate = new Date(233276400000L); Date parsedDate = formatter.parse("24-05-1977"); assertEquals(myDate.getTime(), parsedDate.getTime());
It's important to note here that the pattern supplied in the constructor should be in the same format as the date parsed using the parse method.
4. Date-Time Patterns
SimpleDateFormat supplies a vast array of different options when formatting dates. While the full list is available in the JavaDocs, let's explore some of the more commonly used options:
The output returned by the date component also depends heavily on the number of characters used within the String. For example, let's take the month of June. If we define the date string as:
Then our result will appear as the number code – 06. However, if we add another M to our date string:
Then our resulting formatted date appears as the word Jun.
5. Applying Locales
The SimpleDateFormat class also supports a wide range of locales which is set when the constructor is called.
Let's put this into practice by formatting a date in French. We'll instantiate a SimpleDateFormat object whilst supplying Locale.FRANCE to the constructor.
SimpleDateFormat franceDateFormatter = new SimpleDateFormat("EEEEE dd-MMMMMMM-yyyy", Locale.FRANCE); Date myFriday = new Date(1539341312904L); assertTrue(franceDateFormatter.format(myFriday).startsWith("vendredi"));
By supplying a given date, a Wednesday afternoon, we can assert that our franceDateFormatter has correctly formatted the date. The new date correctly starts with Vendredi – French for Friday!
It's worth noting a little gotcha in the Locale version of the constructor – whilst many locales are supported, full coverage is not guaranteed. Oracle recommends using the factory methods on DateFormat class to ensure locale coverage.
6. Changing Time Zones
Since SimpleDateFormat extends the DateFormat class, we can also manipulate the time zone using the setTimeZone method. Let's take a look at this in action:
Date now = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEEE dd-MMM-yy HH:mm:ssZ"); simpleDateFormat.setTimeZone(TimeZone.getTimeZone("Europe/London")); logger.info(simpleDateFormat.format(now)); simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York")); logger.info(simpleDateFormat.format(now));
In the above example, we supply the same Date to two different time zones on the same SimpleDateFormat object. We've also added the ‘Z' character to the end of the pattern String to indicate the time zone differences. The output from the format method is then logged for the user.
Hitting run, we can see the current times relative to the two time zones:
INFO: Friday 12-Oct-18 12:46:14+0100 INFO: Friday 12-Oct-18 07:46:14-0400
In this tutorial, we've taken a deep dive into the intricacies of SimpleDateFormat.
We've looked at how to instantiate SimpleDateFormat as well as how the pattern String impacts how the date is formatted.
We played around with changing the locales of the output String before finally experimenting with using time zones.
As always the complete source code can be found over on GitHub.