A Simple Guide for Java.util.Date

In this article, we will explore java.util.Date, consume it as a basic cheatsheet. We will see several examples as it's still used in several code bases and later, I mean really later, we will conspire to throw it out of the window and explore better alternatives for date and time handling in Java.

Why am I talking about java.util.Date in this day and age when the “whole” community has moved on? Because the whole community has not yet moved on and several of us still work with legacy code bases, some going as far back as Java 6 and much as we are moving forth, we can’t go into the future without thoroughly coming to terms with our past :)

I bet you often deal with dates and have to frequently google how to parse date from string, convert date to different string formats for SQL storage or user-friendly display, compare dates, add and subtract days, months, years etc from a date. These are pretty common needs for most developers so let’s get on with it and move on with our lives. I will do my best to keep it short and sweet!

Date Creation

The java.util.Date class has 2 constructors, a default one and one that take a long value which represents the the number of milliseconds that have elapsed since 00:00:00 Thursday, 1 January 1970 AKA Unix Epoch time. It’s just a way to keep ourselves and our computers sane while dealing with time in different locales and timezones: To make for easier testing, I captured a long time value on the day of writing this article as the default constructor internally invokes System.currentTimeInMillis() to get the now time:

@Test
public void test_getDateFromTimestamp_basic() {
	Date now = new Date();
//	assertEquals("current time", now.toString());
	Date fixedDate = new Date(1550487961686L);
	assertEquals("Mon Feb 18 14:06:01 EAT 2019", fixedDate.toString());
}

Take note of the default format when the date is toString‘d

Comparing Dates

The Date object has a method called getTime which converts the date back to a long unix epoch time. It’s a reversal of the previously seen creation process. We can just compare the long values of 2 dates to know which is an earlier(less) date:

	@Test
	public void test_compareDates() {
		Date older = new Date(1550487961686L);
		Date newer = new Date();
		assertTrue(older.getTime() < newer.getTime());
	}

We can use the compareTo instance method to compare one date to the other which is passed as an argument to compareTo. If the date on which compareTo is called is earlier(less) than the argument date, the result is -1, if they are the same instance in time, we get a 0 and a 1 if the argument date is less:

	@Test
	public void test_compareDates() {
		Date older = new Date(1550487961686L);
		Date newer = new Date();
		assertTrue(older.compareTo(newer) == -1);
		assertTrue(newer.compareTo(older) == 1);
		assertTrue(newer.compareTo(newer) == 0);
		assertTrue(older.compareTo(older) == 0);
	}

Finally,we have the instance methods before and after. They are definitely much more readable and intuitive than the previous 2, especially compareTo. We call each of these on a Date object and pass the second date to be compared as an argument. The results are boolean:

	@Test
	public void test_compareDates() {
		Date older = new Date(1550487961686L);
		Date newer = new Date();
		assertTrue(older.before(newer));
		assertFalse(newer.before(older));
	}

Date to String conversion

Frequently we will want to convert a date to a string either for storage or user-friendly display, this is done by formatting the date. java.text.DateFormat class comes in handly, most commonly though its subclass java.text.SimpleDateFormat. SimpleDateFormat is very simple to use and the main requirement is to understand the notation used to define formats.

For reference information about the different formatting patterns and codes we will use here(m for minute, M for month, s for seconds etc), head over here.

Let’s first recall the default conversion when we haven’t specified any format:

@Test
public void test_dateFormatting() {
// ie Mon Feb 18 14:06:01 EAT 2019
	Date fixedDate = new Date(1550487961686L);
	assertEquals("Mon Feb 18 14:06:01 EAT 2019", fixedDate.toString());
}

Mon stands for monday, Feb for february then 18 is day of month, followed by time in hours, minutes and seconds, timezone and finally year. This may be a little verbose for your needs or you would like to have time in 12 hour clock system or remove the timezone or use the full month name etc, let’s first try to recreate the above string by specifying our own format(remember to keep this reference doc close):

@Test
public void test_dateFormatting() {
    ...
	SimpleDateFormat defaultFormat = new SimpleDateFormat("E M d HH:mm:ss z y");
	assertEquals("Mon 2 18 14:06:01 EAT 2019", defaultFormat.format(fixedDate));
}

Everything seems to be alright except the month section, it’s a 2 because Feb is the second month of the year, we used its code M which represents the simplest format for the month, we could choose to make it 2 digits so that single digit months match double digit months(nov, dec) in length, we do this by appending another M

SimpleDateFormat defaultFormat2 = new SimpleDateFormat("E MM d HH:mm:ss z y");
assertEquals("Mon 02 18 14:06:01 EAT 2019", defaultFormat2.format(fixedDate));

To get month in words but abbreviated as our default example gives, we append another M to make them 3:

SimpleDateFormat defaultFormat3 = new SimpleDateFormat("E MMM d HH:mm:ss z y");
assertEquals("Mon Feb 18 14:06:01 EAT 2019", defaultFormat3.format(fixedDate));

If we want full month name, then make them 4:

SimpleDateFormat defaultFormat4 = new SimpleDateFormat("E MMMM d HH:mm:ss z y");
assertEquals("Mon February 18 14:06:01 EAT 2019", defaultFormat4.format(fixedDate));

This pattern of adding and reducing the number of codes for each part of the date is common in formatting dates. Play around with this while referring to the reference doc for further clarity. For instance, let’s get the full day name by using 4 Es:

SimpleDateFormat defaultFormat5 = new SimpleDateFormat("EEEE MMMM d HH:mm:ss z y");
assertEquals("Monday February 18 14:06:01 EAT 2019", defaultFormat5.format(fixedDate));

Watch out though, as this does not work for all codes. Let’s try to convert our time to 12 hour clock system(h for 12 and H for 24, a for the AM/PM marker of 12 hour system):

SimpleDateFormat defaultFormat6 = new SimpleDateFormat("EEEE MMMM d hh:mm:ss a z y");
assertEquals("Monday February 18 02:06:01 PM EAT 2019", defaultFormat6.format(fixedDate));

We can strip the zeros that appear to pad up the time in case it’s all single digits. We do this by using single forms of the codes hh, mm and ss:

SimpleDateFormat defaultFormat7 = new SimpleDateFormat("EEEE MMMM d h:m:s a z y");
assertEquals("Monday February 18 2:6:1 PM EAT 2019", defaultFormat7.format(fixedDate));

But it does not look intuitive right? I have commonly seen the zero stripped from the hour part but not from the minute and second parts, do what works best for your use case. Another caveat and common cause of bugs is developers confusing upper case and lower case formatting codes. m and M are different, the former for minute and the latter for month. Always remember that these codes are case sensitive:

SimpleDateFormat defaultFormat8 = new SimpleDateFormat("M m");
assertEquals("2 6", defaultFormat8.format(fixedDate));

With the above examples, we can now achieve almost any format we need by a combination of the formatting codes picked from the reference documentation, see SQL date for example:

SimpleDateFormat sqlFormat = new SimpleDateFormat("y-MM-d HH:mm:ss");
assertEquals("2019-02-18 14:06:01", sqlFormat.format(fixedDate));

One of the most commont ISO-8601 formats that has given me headache in the past. Some APIs are strict about the date format they accept. Take note of how we are using single quotes here, they are delimiters and enable us to inject any text in the date format:

SimpleDateFormat isoFormat = new SimpleDateFormat("y-MM-d'T'HH:mm:ss'Z'");
assertEquals("2019-02-18T14:06:01Z", isoFormat.format(fixedDate));

Sometimes the ISO-8601 format requires fractions of a second to be represented, we use the SSS code:

SimpleDateFormat isoFormat2 = new SimpleDateFormat("y-MM-d'T'HH:mm:ss.SSS'Z'");
assertEquals("2019-02-18T14:06:01.686Z", isoFormat2.format(fixedDate));

Finally, using the powerful single quotes, we can inject any text in the formatted date:

SimpleDateFormat customStringFormat = new SimpleDateFormat("'Payment received on' y-MM-d 'at' HH:mm:ss");
assertEquals("Payment received on 2019-02-18 at 14:06:01", customStringFormat.format(fixedDate));

String to Date conversion

SimpleDateFormat has a parse method that does the opposite of format as we have seen above. It takes any string we have received from a file, from an API response or created by ourselves and attempts to parse it into a java.util.Date object or throws a java.text.ParseException if the pattern we used does not match the date.

Here it is very important to get the pattern right, that means you need to understand the format of the date string before hand. Remember, date will still give the default value we saw earlier if it is toString‘d:

String dateString1 = "20190218";
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyMMdd");
Date date1 = sdf1.parse(dateString1);
assertEquals("Mon Feb 18 00:00:00 EAT 2019", date1.toString());

Let’s see a few other examples to gain more clarity, using the / date delimiter which we haven’t seen explicitly before:

String dateString2 = "2019/02/18";
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy/MM/dd");
Date date2 = sdf2.parse(dateString2);
assertEquals("Mon Feb 18 00:00:00 EAT 2019", date2.toString());

Let’s also see a case where we have text in the date e.g. month in words:

String dateString3 = "2019-February-18";
SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy-MMMM-dd");
Date date3 = sdf3.parse(dateString3);
assertEquals("Mon Feb 18 00:00:00 EAT 2019", date3.toString());

We used MMMM month code because we expected a full month name, sometimes, the month is abbreviated, just do what we know already, strip a single M:

String dateString4 = "2019-Feb-18";
SimpleDateFormat sdf4 = new SimpleDateFormat("yyyy-MMM-dd");
Date date4 = sdf4.parse(dateString4);
assertEquals("Mon Feb 18 00:00:00 EAT 2019", date4.toString());

Conclusion

In this article, we have gone through the most important ways of using java.util.Date class along side the helper classes for formatting. This should act as a quick refresher for those that use this class once in a while or if you are preparing for an interview and just need to remind yourself.

In the next article, we will look at how to add and subtract dates such that you can get a date before or after the available date without apply string manipulation kung-fu. We will also look at how to decompose dates by extracting component parts i.e. year, month, day etc from a Date object as well as applying internationalisation. These are made possible by the Calendar class.