Java 101: Catching up with the Java Date and Time API

feature
Apr 08, 201324 mins
Core JavaDesign PatternsJava

Get to know the java.time classes you're most likely to use in Java 8

Among the most anticipated additions to the Java platform coming in Java 8 is JSR 310: The Date and Time API. Find out how the Java Date and Time API addresses the need for a more robust date and time infrastructure in Java SE, then familiarize yourself with the java.time classes you’re most likely to use, in this inaugural installment of Java 101: The next generation.

Articles in the original Java 101 series are among the most frequently sought archives on JavaWorld. Published from 2000 to 2004, the series was launched with Jacob Weintraub’s “Learn Java from the ground up.” My subsequent 30 installments addressed a wide range of Java programming topics, including object-oriented language basics, threaded programming, and garbage collection in Java. (Visit the Java 101 series for a listing.)

Naturally, the Java platform has evolved significantly since those days. JDK 5, released in September 2004, was a game-changer for many Java developers, and some believe that Java 8 will be just as significant. Both releases feature important additions to the Java language, as well as APIs that address the emergent needs (then and now) of Java programming. Java 101: The next generation will guide you through some of the newer Java language syntax, APIs, and libraries that are most relevant to Java developers today, starting with one of the most anticipated additions to Java SE 8: the Java Date and Time API.

I’ll start with an overview of the Date and Time API, explaining why it’s such an important addition to Java SE. Then we’ll tour the three fundamental Date and Time API type categories, which are found in the java.time package:

Introducing the Java Date and Time API

Date and Time is a new date, time, and calendar API for the Java SE platform. It was developed under JSR 310 and replaces Java’s existing date and time infrastructure based on java.util.Date and java.util.Calendar. You should use this API in any application that would otherwise use the legacy Date, Calendar, or java.util.TimeZone classes; Calendar or TimeZone subclasses such as java.util.GregorianCalendar; or associated classes such as java.text.DateFormat.

The Date and Time API distinguishes between machine and human views of a timeline, which is an always increasing sequence of instants, or points along the timeline. The machine view reveals a sequence of integral values relative to the epoch (e.g., midnight, January 1, 1970), with positive values identifying instants after the epoch and negative values identifying instants before the epoch. The human view reveals a set of fields (e.g., year, month, day-of-month, hour, minute, and second).

Why do we need a new Date and Time API?

Java 1.0 introduced System.currentTimeMillis(), which could return a machine view of the timeline with millisecond precision. It also introduced the java.util.Date class, which returned a human view. It soon became evident that there were several flaws with this class:

  • Constructors that accept year arguments require offsets from 1900, which has been a source of bugs.
  • January is represented by 0 instead of 1, also a source of bugs.
  • Date doesn’t describe a date but describes a date-time combination.
  • Date‘s mutability makes it unsafe to use in multithreaded scenarios without external synchronization.
  • Date isn’t amenable to internationalization.

In an attempt to fix these flaws, Sun introduced java.util.Calendar and related classes with the Java 1.1 release. Unfortunately, Calendar was also riddled with flaws, including the following:

  • It isn’t possible to format a calendar.
  • January is represented by 0 instead of 1, a source of bugs.
  • Calendar isn’t type-safe; for example, you must pass an int-based constant to the get(int field) method. (In fairness, enums weren’t available when Calendar was released.)
  • Calendar‘s mutability makes it unsafe to use in multithreaded scenarios without external synchronization. (The companion java.util.TimeZone and java.text.DateFormat classes share this problem.)
  • Calendar stores its state internally in two different ways — as a millisecond offset from the epoch and as a set of fields — resulting in many bugs and performance issues.

Additionally, java.sql‘s Date, Time, and Timestamp classes extend java.util.Date and inherit its problems. Also, in order to prevent setting the time part in the Date class and the date part in the Time class, Date and Time force various setter methods to throw IllegalArgumentException, which is messy.

Clearly, a more robust API was needed in order to address date and time functionality in Java programs.

Joda Time in JSR 310

Joda Time is an open source date and time API. Released by Stephen Colebourne in February 2005, it became a popular alternative to the standard Java date and time APIs. While the JSR 310: Date and Time API isn’t Joda Time (due to various design flaws) it is heavily influenced by it. In fact, Colebourne is a JSR 310 co-lead.

Java Date and Time API overview

JSR 310: Date and Time API was developed to overcome numerous problems with Java’s previous date and time APIs. As a result, it has been architected around a number of important design principles:

  • Immutability and thread safety: All of the Date and Time API’s core classes are immutable, which ensures that you don’t have to worry about threading issues caused by lack of thread synchronization. Immutable objects are simple to construct, use, and test, they make for good hash keys, and so on.
  • Fluency: Date and Time presents a fluent interface, which should make its methods more readable and easier to learn, especially when chained together. Fluent factory methods (e.g., now(), from(), and of-prefixed methods) are used as an alternative to constructors. You’ll also be able to use with-prefixed methods if you need to return a copy of the current instance with additional information.
  • Clarity: Each method in the Date and Time API is well-defined and clear about what it accomplishes. Additionally, Date and Time rejects null arguments early. Unless otherwise noted, passing a null argument to a method in any class or interface will cause a NullPointerException to be thrown. Validation methods that take object arguments and return Boolean values are an exception: they generally return false when null is passed.
  • Extensibility: The Strategy design pattern is used throughout the API to allow for extension while avoiding confusion. For example, although Date and Time’s classes are based on the ISO-8601 calendar system, you can also work with the non-ISO calendars (such as Japanese Imperial) that are included in the API, or you could even introduce your own calendar.

Date and Time is described by some 60 types (at the time of this writing) that are organized into a main package and four subpackages:

  • java.time presents classes that represent the principal date-time concepts: instants, durations, dates, times, time zones, partials, and periods. All of this package’s classes are immutable and thread-safe.
    • java.time.chrono provides a generic API that describes calendar systems other than the default ISO-8601 calendar system.
    • java.time.format presents classes for formatting and parsing date-time objects.
    • java.time.temporal offers field, unit, or adjustment access to a temporal object such as a date.
    • java.time.zone presents classes that support time zones and their rules.

The types in the java.time package should serve most of your needs. You’ll work directly with the types in the other four packages only when you need to go beyond java.time‘s default ISO-8601 calendar system.

Retrofitting the old date and time APIs

To ease the transition to the new Date and Time API, the old date and time APIs have been retrofitted to interoperate with Date and Time. For example, an Instant toInstant() method has been added to java.util.Date to convert a Date instance to an Instant instance.

Touring the Date and Time API

Date and Time’s many types can seem overwhelming. However, you’ll often work with only a subset of the types in the java.time package. The remainder of the article will be a tour of the java.time types that you are most likely to use: classes used to store and manipulate machine time, local date and time, and international time zones in your Java applications. For each of the API types I’ll include a brief overview followed by one or more working applications that demonstrate how classes are instantiated, how instances are accessed, and how instances are manipulated to obtain new instances.

Instant and Duration

The java.time package’s Instant and Duration classes provide a machine view of the timeline, which is a single, continually incrementing number. Instant stores a point in time relative to the epoch; Duration stores a time-based amount of time, like 25.8 seconds.

Use cases for machine time classes

You could use Instant to record event timestamps and Duration to record animation cycle lengths.

Instant stores an instant relative to the epoch as a number of seconds in a long integer field and as a number of nanoseconds beyond the last second (i.e., nanosecond-of-second) in an integer field. Duration stores a time-based amount of time similarly.

Instantiating Instant and Duration

Instant and Duration declare a number of constants that describe pre-created Instants and Durations:

  • EPOCH is an Instant representing the epoch.
  • MAX is an Instant that identifies the largest possible (i.e., far future) instant.
  • MIN is an Instant that identifies the smallest possible (i.e., far past) instant.
  • ZERO is a Duration representing a duration of zero.

Instant and Duration also declare several fluent factory methods for instantiating these classes:

  • Instant now() returns an Instant from the system clock using the UTC (Coordinated Universal Time) time zone.
  • Instant ofEpochSecond(long epochSecond) returns an Instant identifying the instant epochSecond seconds past the epoch. The nanosecond-of-second field is set to zero.
  • Instant parse(CharSequence text) returns an Instant from an ISO-8601 text string such as 2007-12-03T10:15:30Z. If the string doesn’t represent a valid instant in UTC, this method throws an instance of the java.time.format.DateTimeParseException runtime exception class. (ISO-8601 specifies T as the time designator that precedes the time components in the formatted string representation, and it specifies Z to indicate UTC.)
  • Duration ofSeconds(long seconds) returns a Duration identifying seconds seconds and setting the nanosecond-of-second field to 0.
  • Duration parse(CharSequence text) returns a Duration from an ISO-8601 text string such as PT3M20S. (ISO-8601 specifies P — which historically stood for “period” — to signify a duration. It also specifies M to indicate minutes and S to indicate seconds.)

Machine time classes: A demo

Listing 1 demonstrates the above-listed constants and fluent factory methods in a simple application that outputs constant values and the results of calls to these methods.

Listing 1. MachineTimeDemo.java (version 1)

import java.time.Duration;
import java.time.Instant;

public class MachineTimeDemo
{
   public static void main(String[] args)
   {
      System.out.printf("EPOCH = %s%n", Instant.EPOCH);
      System.out.printf("MAX = %s%n", Instant.MAX);
      System.out.printf("MIN = %s%n", Instant.MIN);

      System.out.printf("Now = %s%n", Instant.now());
      System.out.printf("50 seconds past epoch = %s%n",
                        Instant.ofEpochSecond(50));
      System.out.printf("Parsed = %s%n",
                        Instant.parse("2007-12-03T10:15:30Z"));

      System.out.printf("ZERO = %s%n", Duration.ZERO);

      System.out.printf("30-second duration = %s%n",
                        Duration.ofSeconds(30));
      System.out.printf("Parsed = %s%n",
                        Duration.parse("PT3M20S"));     
   }
}

Note in Listing 1 that Instant, Duration (and other java.time classes) provide a toString() method that returns a string representation of the object’s value in ISO-8601 format. This method is automatically called by System.out.printf().

If you compile this application (javac MachineTimeDemo.java) and run it (java MachineTimeDemo) your output should be similar to what’s shown below:

EPOCH = 1970-01-01T00:00Z
MAX = +1000000000-12-31T23:59:59.999999999Z
MIN = -1000000000-01-01T00:00Z
Now = 2013-03-24T00:33:20.986Z
50 seconds past epoch = 1970-01-01T00:00:50Z
Parsed = 2007-12-03T10:15:30Z
ZERO = PT0S
30-second duration = PT30S
Parsed = PT3M20S

Getter methods for Instant and Duration

Instant and Duration declare several getter methods for reading their component fields and obtaining additional information. Instant‘s getter methods include the following:

  • long getEpochSecond() returns the number of seconds relative to the epoch.
  • int getNano() returns the number of nanoseconds past the last second.
  • boolean isAfter(Instant otherInstant) returns true when this Instant comes after the specified Instant.
  • boolean isBefore(Instant otherInstant) returns true when this Instant comes before the specified Instant.

Duration‘s getter methods include the following:

  • int getNano() returns the number of nanoseconds past the duration’s last second.
  • long getSeconds() returns the number of seconds in the duration.
  • boolean isNegative() returns true when this Duration is negative.
  • boolean isZero() returns true when this Duration has zero length.

Listing 2. MachineTimeDemo.java (version 2)

import java.time.Duration;
import java.time.Instant;

public class MachineTimeDemo
{
   public static void main(String[] args)
   {
      Instant now = Instant.now();
      System.out.printf("Now = %s%n", now);
      System.out.printf("Epoch second = %d%n", now.getEpochSecond());
      System.out.printf("Nano = %d%n", now.getNano());
      System.out.printf("After epoch = %b%n", now.isAfter(Instant.EPOCH));
      System.out.printf("Before epoch = %b%n", now.isBefore(Instant.EPOCH));

      Duration dur = Duration.ofSeconds(49L-(long) (Math.random()*80));
      System.out.printf("Nano = %d%n", dur.getNano());
      System.out.printf("Seconds = %d%n", dur.getSeconds());
      System.out.printf("Is negative = %b%n", dur.isNegative());
      System.out.printf("Is zero = %b%n", dur.isZero());
   }
}

Compile Listing 2 and run it. Your output should be similar to the following:

Now = 2013-03-24T00:34:04.811Z
Epoch second = 1364085244
Nano = 811000000
After epoch = true
Before epoch = false
Nano = 0
Seconds = -18
Is negative = true
Is zero = false

Manipulating instant and duration instances

There are several methods for manipulating instances of Instant and Duration. Because these classes are immutable, the instances are not affected by these methods. Instead, copies of the instances with manipulated values will be returned.

Instant declares the following methods, which can be used to manipulate its instances:

  • Instant minusSeconds(long secondsToSubtract) returns a copy of the given Instant decreased by the specified number of seconds, which might be negative.
  • Instant plusNanos(long nanosToAdd) returns a copy of the given Instant increased by the specified number of nanoseconds, which might be negative.

Similar methods for Duration include the following:

  • Duration dividedBy(long divisor) returns a copy of the given Duration divided by the specified value.
  • Duration minusDays(long daysToSubtract) returns a copy of the given Duration decreased by the specified number of days, which might be negative.
  • Duration multipliedBy(long multiplicand) returns a copy of the given Duration multiplied by the specified value.
  • Duration plusHours(long hoursToAdd) returns a copy of the given Duration increased by the specified number of hours, which might be negative.
  • Duration withNanos(int nanoOfSecond) returns a copy of the given Duration with the specified number of nanoseconds. The number of seconds isn’t affected.
  • Duration withSeconds(long seconds) returns a copy of the given Duration with the specified number of seconds. The number of nanoseconds isn’t affected.

Listing 3 demonstrates these manipulation methods at work in MachineTimeDemo.java.

Listing 3. MachineTimeDemo.java (version 3)

import java.time.Duration;
import java.time.Instant;

public class MachineTimeDemo
{
   public static void main(String[] args)
   {
      Instant now = Instant.now();
      System.out.printf("Now = %s%n", now);
      System.out.printf("Now-20 seconds = %s%n", now.minusSeconds(20));
      System.out.printf("Now+500,000,000 nanoseconds = %s%n", now.plusNanos(500000000));

      Duration dur = Duration.ofDays(10);
      System.out.printf("Dur = %s%n", dur);
      System.out.printf("Dur/2 = %s%n", dur.dividedBy(2));
      System.out.printf("Dur-3 days = %s%n", dur.minusDays(3));
      System.out.printf("Dur*2 = %s%n", dur.multipliedBy(2));
      System.out.printf("Dur+12 hours = %s%n", dur.plusHours(12));
      System.out.printf("Dur nanos set to 500,000,000 = %s%n", dur.withNanos(500000000));
      System.out.printf("Dur seconds set to 3600 = %s%n", dur.withSeconds(3600));
   }
}

Compile Listing 3 and run the application. Your output should be similar to what’s below:

Now = 2013-03-24T00:34:38.788Z
Now-20 seconds = 2013-03-24T00:34:18.788Z
Now+500,000,000 nanoseconds = 2013-03-24T00:34:39.288Z
Dur = PT240H
Dur/2 = PT120H
Dur-3 days = PT168H
Dur*2 = PT480H
Dur+12 hours = PT252H
Dur nanos set to 500,000,000 = PT240H0.5S
Dur seconds set to 3600 = PT1H

LocalDate, LocalTime, and LocalDateTime

The java.time package provides LocalDate and LocalTime classes that model date and time values in an ISO calendar system and local time-zone context. They represent dates and times without taking time zones into consideration.

Use cases for local date and time classes

LocalDate and LocalTime are appropriate for use cases where time zones are irrelevant. For example, you might use LocalDate to store a birthdate or an employee hire-date, and you might use LocalTime to store the time that an alarm clock sounds its alarm.

The java.time, LocalDateTime class combines LocalDate with LocalTime, for greater precision (date plus time) than you could achieve with LocalDate or LocalTime alone.

Instantiating LocalDate, LocalTime, and LocalDateTime

There are several categories of fluent factory methods for instantiating LocalDate, LocalTime, and LocalDateTime. These categories include the following:

  • now() methods return instances created from clocks.
  • of-prefixed methods return instances created from specific values.
  • from-prefixed methods return instances created from other types (for example, obtaining a LocalDate instance from a LocalDateTime instance).

Local date and time classes: A demo

The application code in Listing 4 instantiates LocalDate, LocalTime, and LocalDateTime via some of the above fluent factory methods.

Listing 4. HumanTimeDemo.java (version 1)

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class HumanTimeDemo
{
   public static void main(String[] args)
   {
      // create from current date
      LocalDate localDate = LocalDate.now();
      System.out.printf("Local date = %s%n", localDate);

      // create from specific values
      localDate = LocalDate.of(2012, 12, 21);
      System.out.printf("Local date = %s%n", localDate);

      // create from 100 days into 1970
      localDate = LocalDate.ofEpochDay(100);
      System.out.printf("Local date = %s%n", localDate);
      
      // create from another local date
      localDate = LocalDate.from(localDate);
      System.out.printf("Local date = %s%n%n", localDate);


      // create from current time
      LocalTime localTime = LocalTime.now();
      System.out.printf("Local time = %s%n", localTime);

      // create from specific values
      localTime = LocalTime.of(9, 15);
      System.out.printf("Local time = %s%n", localTime);

      // create from 120 seconds past midnight
      localTime = LocalTime.ofSecondOfDay(120);
      System.out.printf("Local time = %s%n", localTime);

      // create from another local time
      localTime = LocalTime.from(localTime);
      System.out.printf("Local time = %s%n%n", localTime);


      // create from current date and time
      LocalDateTime localDateTime = LocalDateTime.now();
      System.out.printf("Local date and time = %s%n", localDateTime);
   }
}

Listing 4 reveals several ways to obtain local dates, times, and date-times. now() returns an instance based on the system clock in the default time zone; of returns an instance based on passed arguments; and from() returns an instance based on the same or another type.

Compile Listing 4 and run this application. Your output should be similar to the following:

Local date = 2013-03-23
Local date = 2012-12-21
Local date = 1970-04-11
Local date = 1970-04-11

Local time = 19:35:19.021
Local time = 09:15
Local time = 00:02
Local time = 00:02

Local date and time = 2013-03-23T19:35:19.021

Getter methods for LocalDate, LocalTime, and LocalDateTime

Each of LocalDate, LocalTime, and LocalDateTime presents various getter methods for returning values such as year, day-of-month, hour, and minute. Listing 5 demonstrates a few of these methods.

Listing 5. HumanTimeDemo.java (version 2)

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class HumanTimeDemo
{
   public static void main(String[] args)
   {
      LocalDateTime localDateTime = LocalDateTime.now();
      System.out.printf("Date-time: %s%n", localDateTime);
      System.out.printf("Day of year: %d%n", localDateTime.getDayOfYear());
      LocalDate localDate = localDateTime.toLocalDate();
      LocalTime localTime = localDateTime.toLocalTime();
      System.out.printf("Date: %02d-%02d-%02d%n", localDate.getYear(),
                        localDate.getMonthValue(), localDate.getDayOfMonth());
      System.out.printf("Time: %02d:%02d:%-2d%n", localTime.getHour(),
                        localTime.getMinute(), localTime.getSecond());
   }
}

Note that Listing 5 also demonstrates LocalDateTime‘s LocalDate toLocalDate() and LocalTime toLocalTime() methods for returning the local date and time components of a local date-time object.

Compile Listing 5 and run this application. Your output should be similar to what’s shown below:

Date-time: 2013-03-23T19:35:53.154
Day of year: 82
Date: 2013-03-23
Time: 19:35:53

Manipulating local date, time, and date-time instances

LocalDate, LocalTime, and LocalDateTime declare several methods for manipulating their instances. Because these classes are immutable, their instances are not affected. Instead, copies of the instances with manipulated values are returned.

ANSI SQL date and time types

ANSI SQL defines several date and time types. LocalDate corresponds to the SQL DATE type, LocalTime corresponds to the SQL TIME type, and LocalDateTime corresponds to the SQL TIMESTAMP type.

One way to manipulate local dates, times, and date-times is to call any of their with-prefixed methods. Any of these methods will return a copy of the given object, because the core classes are immutable. (This is also why there are no setter methods.)

LocalDateTime withYear(int year), for example, returns a copy of a given LocalDateTime object with the year field set to year. The time doesn’t affect the calculation and will be the same in the result.

You can also modify multiple fields by calling various plus-prefixed and minus-prefixed methods. For example, LocalDateTime minusWeeks(long weeks) subtracts the number of weeks from the current local date-time, which can affect the month and year fields.

Finally, the Date and Time API provides adjusters, which are strategies (as in the Strategy design pattern) for adjusting temporal objects. Adjusters externalize the process of adjustment, permitting different approaches, such as setting the date to the last day of the month.

Adjusters are described by the java.time.temporal.TemporalAdjuster interface. You can create your own adjusters (an example of Date and Time’s extensibility) or obtain a predefined adjuster from the java.time.temporal.Adjusters class (one example being static TemporalAdjuster lastDayOfMonth()). Once you have an adjuster, pass it to a with() method such as LocalDateTime‘s LocalDateTime with(TemporalAdjuster adjuster) method.

Instant and adjusters

Instant declares an Instant with(TemporalAdjuster adjuster) method that lets you obtain a copy that’s been modified by an adjuster. However, you’ll have to create your own adjuster, because none of the predefined adjusters in the Adjusters class can be used with Instant.

Listing 6 demonstrates three approaches for obtaining a modified copy of a LocalDateTime object.

Listing 6. HumanTimeDemo.java (version 3)

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

import static java.time.temporal.Adjusters.*;

public class HumanTimeDemo
{
   public static void main(String[] args)
   {
      LocalDateTime localDateTime = LocalDateTime.now();
      System.out.printf("Date-time: %s%n", localDateTime);
      System.out.printf("Date-time: %s%n", localDateTime.withYear(2012));
      System.out.printf("Date-time: %s%n", localDateTime.minusWeeks(3));
      System.out.printf("Date-time: %s%n", 
                        localDateTime.with(lastDayOfMonth()));
      LocalDate localDate = LocalDate.of(2010, 12, 1);
      LocalTime localTime = LocalTime.of(10, 15);
      localDateTime = localDateTime.with(localDate).with(localTime);
      System.out.printf("Date-time: %s%n", localDateTime);
   }
}

Listing 6 shows that you can use core classes such as LocalDate and LocalTime as adjusters, because most core classes implement the TemporalAdjuster interface.

Compile Listing 6 and run this application. Your output should be similar to the following:

Date-time: 2013-03-23T19:36:29.190
Date-time: 2012-03-23T19:36:29.190
Date-time: 2013-03-02T19:36:29.190
Date-time: 2013-03-31T19:36:29.190
Date-time: 2010-12-01T10:15

ZoneId, ZoneOffset, ZonedDateTime, and OffsetDateTime

A time zone is a region of the Earth’s surface in which all localities have the same standard time. Each time zone has an identifier (such as Europe/Paris) and an offset from UTC/Greenwich (such as +01:00) that changes where daylight savings time is observed and is in effect.

The java.time package provides ZoneId, ZoneOffset, ZonedDateTime, and OffsetDateTime classes for working with time zones:

  • ZoneId describes a time-zone identifier and provides rules for converting between an Instant and a LocalDateTime.
  • ZoneOffset describes a time-zone offset, which is the amount of time (typically in hours) by which a time zone differs from UTC/Greenwich.
  • ZonedDateTime describes a date-time with a time zone in the ISO-8601 calendar system (such as 2007-12-03T10:15:30+01:00 Europe/Paris).
  • OffsetDateTime describes a date-time with an offset from UTC/Greenwich in the ISO-8601 calendar system (such as 2007-12-03T10:15:30+01:00).

ZonedDateTime and OffsetDateTime are similar in that they both have offsets from UTC/Greenwich. However, ZonedDateTime also identifies the time zone whose rules are used to handle ambiguous local date-times.

For example, suppose you moved the clock back one hour from 2 a.m. to 1 a.m. at the end of daylight savings time. In this case, 1:30 a.m. would occur twice, causing an ambiguity. OffsetDateTime doesn’t account for this case but ZonedDateTime does.

Use cases for time zone classes

You could use ZonedDateTime to represent a date and time without relying on a specific server context. You could use OffsetDateTime to serialize data to a database and as the serialization format for logging timestamps when working with servers in different time zones.

Time zone classes: A demo

Like the previously discussed classes, java.time‘s time-zone classes use fluent factory methods to obtain instances. They also support getters and adjusters. Listing 7 demonstrates class instantiation, getters, and adjusters.

Listing 7. HumanTimeDemo.java (version 4)

import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;

import java.time.temporal.Adjusters;

public class HumanTimeDemo
{
   public static void main(String[] args)
   {
      ZoneId zid = ZoneId.systemDefault();
      System.out.printf("Zone Id = %s%n", zid);
      System.out.printf("Rules = %s%n", zid.getRules());
      System.out.printf("DST in effect: %b%n",
                        zid.getRules().isDaylightSavings(Instant.now()));

      zid = ZoneId.of("Europe/Paris");
      System.out.printf("Zone Id = %s%n", zid);

      ZoneOffset zoffset = ZoneOffset.of("+06:00");
      System.out.printf("Zone Offset = %s%n", zoffset);
      System.out.printf("Total seconds = %d%n", zoffset.getTotalSeconds());

      ZonedDateTime zonedDateTime = ZonedDateTime.now();
      System.out.printf("Zoned date and time = %s%n", zonedDateTime);
      System.out.printf("Zone = %s%n", zonedDateTime.getZone());

      zoffset = ZoneOffset.from(zonedDateTime);
      System.out.printf("Zone Offset = %s%n", zoffset);
 
      OffsetDateTime offsetDateTime = OffsetDateTime.now();
      System.out.printf("Offset date and time = %s%n", offsetDateTime);
      System.out.printf("Offset date and time = %s%n", 
                        offsetDateTime.with(Adjusters.lastDayOfMonth()));

      zonedDateTime = ZonedDateTime.of(2013, 11, 2, 3, 00, 0, 0, 
                                       ZoneId.of("America/Chicago"));
      System.out.printf("Zoned date and time = %s%n", zonedDateTime);


      zonedDateTime = ZonedDateTime.of(2013, 11, 3, 3, 00, 0, 0, 
                                       ZoneId.of("America/Chicago"));
      System.out.printf("Zoned date and time = %s%n", zonedDateTime);

      offsetDateTime = OffsetDateTime.of(2013, 11, 2, 3, 00, 0, 0, zoffset);
      System.out.printf("Offset date and time = %s%n", offsetDateTime);

      offsetDateTime = OffsetDateTime.of(2013, 11, 3, 3, 00, 0, 0, zoffset);
      System.out.printf("Offset date and time = %s%n", offsetDateTime);
   }
}

The main() method in Listing 7 is fairly easy to follow and proves that the new Java time-zone classes aren’t hard to use. For example, you can easily determine if the current instant lies in daylight savings time (DST) for a specific zone ID.

The final part of the main() method demonstrates the difference between ZonedDateTime and OffsetDateTime in my time zone. For each class, I obtained an instance that reflected 3 a.m. on November 2, 2013 and November 3, 2013. (DST ends in my time zone at 2 a.m. on November 3.)

At the time of this writing, DST is in effect and the offset is -05:00. ZonedDateTime will change this offset to -06:00 when DST ends. However, OffsetDateTime will not change the offset because it has no access to time zone rules, so it’s unable to find out when DST ends.

Compile Listing 7 and run the application. Your output should be similar to what’s below:

Zone Id = America/Chicago
Rules = ZoneRules[currentStandardOffset=-06:00]
DST in effect: true
Zone Id = Europe/Paris
Zone Offset = +06:00
Total seconds = 21600
Zoned date and time = 2013-03-23T19:37:03.042-05:00[America/Chicago]
Zone = America/Chicago
Zone Offset = -05:00
Offset date and time = 2013-03-23T19:37:03.104-05:00
Offset date and time = 2013-03-31T19:37:03.104-05:00
Zoned date and time = 2013-11-02T03:00-05:00[America/Chicago]
Zoned date and time = 2013-11-03T03:00-06:00[America/Chicago]
Offset date and time = 2013-11-02T03:00-05:00
Offset date and time = 2013-11-03T03:00-05:00

In conclusion

The new Java Date and Time API overcomes various problems with Java’s previous date and time APIs, and is organized into the main java.time package and its four subpackages. Although you’ll most commonly use java.time‘s Instant, Duration, LocalDate, LocalTime, LocalDateTime, ZoneId, ZoneOffset, ZonedDateTime, and OffsetDateTime classes, there are other types to consider. Check out the exercises in this article’s code source file to learn more about the Java Date and Time API.

Jeff Friesen is a freelance tutor and software developer with an emphasis on Java and Android. In addition to writing Java and Android books for Apress, Jeff has written numerous articles on Java and other technologies for JavaWorld, informIT, Java.net, and DevSource. Jeff can be contacted via his website at TutorTutor.ca.