Previous in the Series
Current Tutorial
The Temporal Package
Next in the Series

Previous in the Series: Parsing and Formatting

Next in the Series: Period and Duration

The Temporal Package

The java.time.temporal package provides a collection of interfaces, classes, and enums that support date and time code and, in particular, date and time calculations.

These interfaces are intended to be used at the lowest level. Typical application code should declare variables and parameters in terms of the concrete type, such as LocalDate or ZonedDateTime, and not in terms of the Temporal interface. This is exactly the same as declaring a variable of type String, and not of type CharSequence.

 

Temporal and TemporalAccessor

The Temporal interface provides a framework for accessing temporal-based objects, and is implemented by the temporal-based classes, such as Instant, LocalDateTime, and ZonedDateTime. This interface provides methods to add or subtract units of time, making time-based arithmetic easy and consistent across the various date and time classes. The TemporalAccessor interface provides a read-only version of the Temporal interface.

Both Temporal and TemporalAccessor objects are defined in terms of fields, as specified in the TemporalField interface. The ChronoField enum is a concrete implementation of the TemporalField interface and provides a rich set of defined constants, such as DAY_OF_WEEK, MINUTE_OF_HOUR, and MONTH_OF_YEAR.

The units for these fields are specified by the TemporalUnit interface. The ChronoUnit enum implements the TemporalUnit interface. The field ChronoField.DAY_OF_WEEK is a combination of ChronoUnit.DAYS and ChronoUnit.WEEKS. The ChronoField and ChronoUnit enums are discussed in the following sections.

The arithmetic-based methods in the Temporal interface require parameters defined in terms of TemporalAmount values. The Period and Duration classes (discussed in Period and Duration) implement the TemporalAmount interface.

 

ChronoField and IsoFields

The ChronoField enum, which implements the TemporalField interface, provides a rich set of constants for accessing date and time values. A few examples are CLOCK_HOUR_OF_DAY, NANO_OF_DAY, and DAY_OF_YEAR. This enum can be used to express conceptual aspects of time, such as the third week of the year, the 11th hour of the day, or the first Monday of the month. When you encounter a Temporal of unknown type, you can use the TemporalAccessor.isSupported(TemporalField) method to determine if the Temporal supports a particular field. The following line of code returns false, indicating that LocalDate does not support ChronoField.CLOCK_HOUR_OF_DAY:

boolean isSupported = LocalDate.now().isSupported(ChronoField.CLOCK_HOUR_OF_DAY);

Additional fields, specific to the ISO-8601 calendar system, are defined in the IsoFields class. The following examples show how to obtain the value of a field using both ChronoField and IsoFields:

time.get(ChronoField.MILLI_OF_SECOND)
int qoy = date.get(IsoFields.QUARTER_OF_YEAR);

Two other classes define additional fields that may be useful, WeekFields and JulianFields.

 

ChronoUnit

The ChronoUnit enum implements the TemporalUnit interface, and provides a set of standard units based on date and time, from milliseconds to millennia. Note that not all ChronoUnit objects are supported by all classes. For example, the Instant class does not support ChronoUnit.MONTHS or ChronoUnit.YEARS. Classes in the Date-Time API contain the isSupported(TemporalUnit) method that can be used to verify whether a class supports a particular time unit. The following call to isSupported() returns false, confirming that the Instant class does not support ChronoUnit.DAYS.

Instant instant = Instant.now();
boolean isSupported = instant.isSupported(ChronoUnit.DAYS);

 

Temporal Adjuster

The TemporalAdjuster interface, in the java.time.temporal package, provides methods that take a Temporal value and return an adjusted value. The adjusters can be used with any of the temporal-based types.

If an adjuster is used with a ZonedDateTime, then a new date is computed that preserves the original time and time zone values.

Predefined Adjusters

The TemporalAdjusters class (note the plural) provides a set of predefined adjusters for finding the first or last day of the month, the first or last day of the year, the last Wednesday of the month, or the first Tuesday after a specific date, to name a few examples. The predefined adjusters are defined as static methods and are designed to be used with the static import statement.

The following example uses several TemporalAdjusters methods, in conjunction with the with method defined in the temporal-based classes, to compute new dates based on the original date of 15 October 2000:

LocalDate date = LocalDate.of(2000, Month.OCTOBER, 15);
DayOfWeek dotw = date.getDayOfWeek();
System.out.printf("%s is on a %s%n", date, dotw);

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)));
System.out.printf("last day of Month: %s%n",
                  date.with(TemporalAdjusters.lastDayOfMonth()));
System.out.printf("first day of next Month: %s%n",
                  date.with(TemporalAdjusters.firstDayOfNextMonth()));
System.out.printf("first day of next Year: %s%n",
                  date.with(TemporalAdjusters.firstDayOfNextYear()));
System.out.printf("first day of Year: %s%n",
                  date.with(TemporalAdjusters.firstDayOfYear()));

This produces the following output:

2000-10-15 is on a SUNDAY
first day of Month: 2000-10-01
first Monday of Month: 2000-10-02
last day of Month: 2000-10-31
first day of next Month: 2000-11-01
first day of next Year: 2001-01-01
first day of Year: 2000-01-01

Custom Adjusters

You can also create your own custom adjuster. To do this, you create a class that implements the TemporalAdjuster interface with a adjustInto(Temporal) method. The nest example is the PaydayAdjuster custom adjuster. It evaluates the passed-in date and returns the next payday, assuming that payday occurs twice a month: on the 15th, and again on the last day of the month. If the computed date occurs on a weekend, then the previous Friday is used. The current calendar year is assumed.

public class PaydayAdjuster implements TemporalAdjuster {
    /**
     * The adjustInto method accepts a Temporal instance 
     * and returns an adjusted LocalDate. If the passed in
     * parameter is not a LocalDate, then a DateTimeException is thrown.
     */
    public Temporal adjustInto(Temporal input) {
        LocalDate date = LocalDate.from(input);
        int day;
        if (date.getDayOfMonth() < 15) {
            day = 15;
        } else {
            day = date.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
        }
        date = date.withDayOfMonth(day);
        if (date.getDayOfWeek() == DayOfWeek.SATURDAY ||
                date.getDayOfWeek() == DayOfWeek.SUNDAY) {
            date = date.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
        }

        return input.with(date);
    }
}

The adjuster is invoked in the same manner as a predefined adjuster, using the with() method. Let us consider the following example:

LocalDate nextPayday = date.with(new PaydayAdjuster());

In 2013, both June 15 and June 30 occur on the weekend. Running the previous example with the respective dates of June 3 and June 18 (in 2013), gives the following results:

Given the date:  2013 Jun 3
the next payday: 2013 Jun 14

Given the date:  2013 Jun 18
the next payday: 2013 Jun 28

 

Temporal Query

A TemporalQuery can be used to retrieve information from a temporal-based object.

Predefined Queries

The TemporalQueries class (note the plural) provides several predefined queries, including methods that are useful when the application cannot identify the type of temporal-based object. As with the adjusters, the predefined queries are defined as static methods and are designed to be used with the static import statement.

The precision query, for example, returns the smallest ChronoUnit that can be returned by a particular temporal-based object. The following example uses the precision query on several types of temporal-based objects:

TemporalQuery<TemporalUnit> query = TemporalQueries.precision();
System.out.printf("LocalDate precision is %s%n",
                  LocalDate.now().query(query));
System.out.printf("LocalDateTime precision is %s%n",
                  LocalDateTime.now().query(query));
System.out.printf("Year precision is %s%n",
                  Year.now().query(query));
System.out.printf("YearMonth precision is %s%n",
                  YearMonth.now().query(query));
System.out.printf("Instant precision is %s%n",
                  Instant.now().query(query));

The output looks like the following:

LocalDate precision is Days
LocalDateTime precision is Nanos
Year precision is Years
YearMonth precision is Months
Instant precision is Nanos

Custom Queries

You can also create your own custom queries. One way to do this is to create a class that implements the TemporalQuery interface with the queryFrom(TemporalAccessor) method. Here is a first custom query implemented in the FamilyVacations class, which implements the TemporalQuery interface. The queryFrom() method compares the passed-in date against scheduled vacation dates and returns true if it falls within those date ranges.

public class FamilyVacations implements TemporalQuery<Boolean> {
    // Returns true if the passed-in date occurs during one of the
    // family vacations. Because the query compares the month and day only,
    // the check succeeds even if the Temporal types are not the same.
    public Boolean queryFrom(TemporalAccessor date) {
        int month = date.get(ChronoField.MONTH_OF_YEAR);
        int day = date.get(ChronoField.DAY_OF_MONTH);

        // Disneyland over Spring Break
        if ((month == Month.APRIL.getValue()) && ((day >= 3) && (day <= 8)))
            return Boolean.TRUE;

        // Smith family reunion on Lake Saugatuck
        if ((month == Month.AUGUST.getValue()) && ((day >= 8) && (day <= 14)))
            return Boolean.TRUE;

        return Boolean.FALSE;
    }
}

You can use this TemporalQuery with the following pattern:

// define a year, a month and a day
LocalDate = date = LocalDate.of(year, month, day);
boolean isFamilyVacation = date.query(new FamilyVacations());

The second custom query is implemented in the FamilyBirthdays class. This class provides an isFamilyBirthday() method that compares the passed-in date against several birthdays and returns TRUE if there is a match.

public class FamilyBirthdays {
    // Returns true if the passed-in date is the same as one of the
    // family birthdays. Because the query compares the month and day only,
    // the check succeeds even if the Temporal types are not the same.
    public static Boolean isFamilyBirthday(TemporalAccessor date) {
        int month = date.get(ChronoField.MONTH_OF_YEAR);
        int day = date.get(ChronoField.DAY_OF_MONTH);

        // Angie's birthday is on April 3.
        if ((month == Month.APRIL.getValue()) && (day == 3))
            return Boolean.TRUE;

        // Sue's birthday is on June 18.
        if ((month == Month.JUNE.getValue()) && (day == 18))
            return Boolean.TRUE;

        // Joe's birthday is on May 29.
        if ((month == Month.MAY.getValue()) && (day == 29))
            return Boolean.TRUE;

        return Boolean.FALSE;
    }
}

The FamilyBirthday class does not implement the TemporalQuery interface and can be used as part of a lambda expression. The following code shows how to invoke both custom queries.

// Invoking the query without using a lambda expression.
Boolean isFamilyVacation = date.query(new FamilyVacations());

// Invoking the query using a lambda expression.
Boolean isFamilyBirthday = date.query(FamilyBirthdays::isFamilyBirthday);

if (isFamilyVacation.booleanValue() || isFamilyBirthday.booleanValue())
    System.out.printf("%s is an important date!%n", date);
else
    System.out.printf("%s is not an important date.%n", date);

Last update: January 27, 2022


Previous in the Series
Current Tutorial
The Temporal Package
Next in the Series

Previous in the Series: Parsing and Formatting

Next in the Series: Period and Duration