Dates in Java 8 and beyond 📆

Using Dates in programming languages makes many people sweat at the thought, they can be tricky to understand and use. In Java, they were badly realised in the core library, and that led to a lot people to use external libraries such as Joda Time. 😥

It was only in Java 8, that it was addressed in the java.time package, and a lot of the pain has been removed. 🙏

Key temporal classes

These classes are immutable, which means you can’t change values of an instance. Why? We want to ensure thread-safety. If you have different threads updating a date, you will get funky results when you want to lookup the value.

Instant ⚡

An Instant is a moment on the timeline in UTC. It is the count in nanoseconds since the first moment of 1970 UTC (unix epoch time). It is essentially a timestamp (often referred to as “machine time”).

Java
Instant a = Instant.now();
System.out.println(a); //2018-02-10T17:06:04.194Z

ZoneId 🌎

A ZoneId is a time zone.

Java
ZoneId z = ZoneId.of(Africa/Johannesburg) ;

A time zone is a set of rules for handling adjustments and anomalies as practiced by a country or region. The most common rule applied is Daylight Saving Time (DST). A time zone has the history of past rules, present rules, and rules confirmed for the near future. 📏

These rules change more often than you might expect. Be sure to keep your date-time library’s rules, usually a copy of the ‘tz’ database, up to date. Keeping up-to-date is easier than ever now in Java 8 with Oracle releasing a Timezone Updater Tool.

Use complete time zone names, as much as possible. These names take the form of continent, a SLASH, and a city or region. Avoid the 3-4 letter codes such as ‘EST’ or ‘IST’. They are neither standardized, nor unique. They further confuse the messiness of DST. ‘UTC’ is the only one that can be used without issue as it is the basis of DST.

Daylight saving, turning back time!

Here is a list of the long version of all the zone IDs. The code prints them all out, if you want to do it yourself!

Java
List<String> zoneList = new ArrayList<>(ZoneId.getAvailableZoneIds());
Collections.sort(zoneList);
for(int i = 0; i < zoneList.size(); i++){
System.out.println(zoneList.get(i));
}

ZoneOffset ➕🌎

ZoneOffset is an offset from UTC, such as +02:00.

For example, Paris is one hour ahead of Greenwich/UTC in winter and two hours ahead in summer. The ZoneId instance for Paris will reference two ZoneOffset instances - a +01:00 instance for winter, and a +02:00 instance for summer

You’re unlikely to use this class directly or it’s related classes: OffsetDateTime, and OffsetTime.

ZonedDateTime 🌎📅

ZonedDateTime is a date-time with a time-zone. Think…

ZonedDateTime = Instant + ZoneId

Java
ZonedDateTime now = ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("Europe/Dublin"));
System.out.print(now); //current time in Dublin

Local representations 🏠️

These “local” date representations are without timezones.

Amount of time ⌚

To specify an amount of time, we can use:

Which of these classes should I use?

When choosing a temporal-based class, you first identify what aspects of time you need to represent:

Nearly all of your backend, database, business logic, data persistence, data exchange should be in UTC, you will use Instant the most.

But for presentation to users you need to adjust into a time zone expected by the user. For this, you will probably use ZonedDateTime.

For recording something like a birthday, you might use a LocalDate, because most people observe their birthday on the same day, whether they are in their birth city or somewhere else.

Why would you use OffsetDateTime instead of ZonedDateTime? It would be for special cases. If you are writing complex software that models its own rules for date and time calculations based on geographic locations.

Class/Enum Year Month Day Hours Minutes Seconds Zone Offset Zone ID toString()
Instant X 2013-08-20T15:16:26.355Z
LocalDate X X X 2013-08-20
LocalDateTime X X X X X X 2013-08-20T08:16:26.937
OffsetDateTime X X X X X X X 2013-08-20T08:16:26.954-07:00
ZonedDateTime X X X X X X X X 2013-08-20T08:16:26.937
LocalTime X X X 08:16:26.937
OffsetTime X X X X 08:16:26.954-07:00
MonthDay X X –08-20
Year X 2013
YearMonth X X 2013-08
Month X AUGUST
Duration ^ ^ ^ X PT20H (20 hours)
Period X X X P10D (10 days)

^ does not store the value but has methods to access these values

Using Dates 📅

Creating a date 🐣

We use these static methods to get an instance. You can get the current date via now(); or a fixed date using of().

Java
Instant now = Instant.now();
System.out.println(now.toString()); //current date

ZonedDateTime date = ZonedDateTime.of(2017, 12, 31, 23, 59, 59, 0, ZoneId.systemDefault());
System.out.println(date.toString()); //2017-11-25

Comparing dates 🔎

Use int compareTo(ChronoLocalDate other) to assert the equality of dates by returning a comparsion number, which can be: negative (before); zero (equal); or positive (after).

The self-explanatory boolean isAfter(ChronoLocalDate other), boolean isBefore(ChronoLocalDate other), and boolean isBefore(ChronoLocalDate other) could be more convenient if you want a more specific test.

These methods are available in LocalDate, LocalTime, LocalDateTime, and ZonedDateTime (but the datatype of the parameter is ChronoZonedDateTime).

Java
LocalDate christmas2017 = LocalDate.of(2017, 12, 25 );
LocalDate christmas2018 = LocalDate.of(2018, 12, 25 );
System.out.println(christmas2017.isBefore(christmas2018)); //true

Difference between 2 dates 🕒…🕕

Use the time units in java.time.temporal.ChronoUnit and between(…).

For example, to get the logical calendar days between 2 dates, you can use DAYS.between(Temporal temporal1Inclusive, Temporal temporal2Exclusive).

Java
import java.time.LocalDate;

import static java.time.temporal.ChronoUnit.DAYS;
import static java.time.temporal.ChronoUnit.MONTHS;
import static java.time.temporal.ChronoUnit.YEARS;

public class Test {
public static void main(String[] args){
LocalDate now = LocalDate.now();
System.out.println(now.toString()); //2017-11-26
LocalDate christmas = LocalDate.of(2017, 12, 25 );
System.out.println(christmas.toString()); //2017-11-25
LocalDate christmas2018 = LocalDate.of(2018, 12, 25 );

long daysBetween = DAYS.between(now, christmas);
System.out.println("Days to christmas: " + daysBetween); //Days to christmas: 29
long monthsBetween = MONTHS.between(now, christmas2018);
System.out.println("Months to christmas 2018: " + monthsBetween); //Months to christmas 2018: 12
long yearsBetween = YEARS.between(now, christmas2018);
System.out.println("Years to christmas 2018: " + yearsBetween); //Years to christmas 2018: 1
}
}

Adding and subtracting time units from dates and times ➕📅

To add time units, you can:

To subtract time units, you can:

Java
public class Test {

public static void main(String[] args){
LocalDate a = LocalDate.of(2012, 6, 30);
System.out.println(a.minusDays(30)); //2012-05-31

LocalTime b = LocalTime.of(20,00,00);
System.out.println(b.plusHours(3)); //23:00
}
}

Adding a Duration to a ZonedDateTime, time differences are not observed.

Adding a Period to a ZonedDateTime, the time differences are observed.

Parsing dates 🔁

Use parse(CharSequence text) for dates in the format of “yyyy-mm-dd”.

Java
LocalDate a = LocalDate.parse("2018-02-01");
System.out.println(a.toString());

To parse a date in another format, use the second version, which requires a DateTimeFormatter, through which you can specify the format: parse(CharSequence text, DateTimeFormatter formatter)

Java
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate a = LocalDate.parse("01/02/2018", formatter);
System.out.println(a.toString()); //2018-02-01

Formatting dates 📁️

DateTimeFormatter provides methods for parsing and printing dates using predefined constants (e.g. ISO_LOCAL_DATE), and patterns (e.g. yyyy-MMM-dd).

More complex formatting can be done with DateTimeFormatterBuilder.

Java
import java.time.LocalDate;
import java.time.Month;
import java.time.format.DateTimeFormatter;

public class ExampleFormatter {
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM d yyyy");
// use this format to get always two digits for the day
DateTimeFormatter f1 = DateTimeFormatter.ofPattern("MMM dd yyyy");
LocalDate date = LocalDate.of(2015, Month.JULY, 1);
System.out.println(date.format(formatter));
System.out.println(date.format(f1));
LocalDate d2 = LocalDate.of(2015, Month.JULY, 15);
System.out.println(d2.format(formatter));
}
}

Get a particular day 🎣

The TemporalAdjusters class contains a standard set of adjusters, which are available as static methods. These include:

Java
LocalDate date = LocalDate.of(2000, Month.OCTOBER, 15);
System.out.printf("first day of Month: %s%n", date.with(TemporalAdjusters.firstDayOfMonth()));
System.out.printf("first Monday of Month: %s%n", date.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)));

Java 7 and below 📅️👴👵

In short, don’t waste your time doing it this way! But you may need to maintain code that uses these classes.

Some of the issues are:

But for completeness, lets show a little of the way it was done.

The Date class
is the raw form of a date, it is the number of milliseconds
since January 1, 1970. Most of it’s methods are deprecated, we are encouraged to
use Calendar GregorianCalendar for creating a specific date.

You can create a Date using java.util.Date in 2 forms:

Java
Date now = Date(); //this moment
Date someDate = Date(1511563121308); //ms since 1970 = 24.11.2017 23:39

You would probably not use the second version, and you may want a date other
than now! So…

Really, Date is used as the type to transfer a date between Calendar
and SimpleDateFormat:

Java
Calendar rightNow = Calendar.getInstance();
Java
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

public class Test {
public static void main(String[] args){
GregorianCalendar now = new GregorianCalendar();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MMM-dd");
System.out.println("Today is: " + formatter.format(now.getTime()));

GregorianCalendar christmas = new GregorianCalendar(2017,Calendar.DECEMBER, 25);

System.out.println("Christmas day is:" + formatter.format(christmas.getTime()));
}
}

Example application 💾

For a loan of a book, we want to store when the book is loaned, and when it is returned. It is a better fit to use an Instant for these fields, and stick with UTC timezone implicitly. The user can decide how to record the value (timezone), and display it to the user.

To see if the book was returned late, in getDaysLate(), we want to use the beginning of the loan date (midnight) as the basis of the calculation, to include the full day. The easiest way (I could find) to do this, is to convert the Instant to a ZonedDateTime using the “UTC” ZoneId, and use truncatedTo() to only take the date portion, therefore resetting the time portion to 00:00:00. 😅

This is a good example of how to use temporal classes for storing dates, and making calculations based on them.

Java
public class Loan {
private Book book;
private Instant loanDate;
private Instant returnDate;
public static final int NUM_OF_DAYS_PER_LOAN = 10;

public Loan(Book book) {
this.book = book;
book.setLoan(this);
loanDate = Instant.now();
}

//getters and setters

public Instant getDueDate() {
return loanDate.plus(NUM_OF_DAYS_PER_LOAN, ChronoUnit.DAYS);
}

public int getDaysLate() {
int days = 0;
if (returnDate != null) {
ZoneId utc = ZoneId.of("UTC");
//resets time to midnight
ZonedDateTime startOfLoanDate = ZonedDateTime.ofInstant(loanDate, utc).truncatedTo(ChronoUnit.DAYS);
int daysOnLoan = (int) DAYS.between(startOfLoanDate.toInstant(), returnDate);

if (daysOnLoan > NUM_OF_DAYS_PER_LOAN) {
days = daysOnLoan - NUM_OF_DAYS_PER_LOAN;
}
}

return days;
}
}

Tagged