Parallel Streams – Streams

16.9 Parallel Streams

The Stream API makes it possible to execute a sequential stream in parallel without rewriting the code. The primary reason for using parallel streams is to improve performance, but at the same time ensuring that the results obtained are the same, or at least compatible, regardless of the mode of execution. Although the API goes a long way to achieve its aim, it is important to understand the pitfalls to avoid when executing stream pipelines in parallel.

Building Parallel Streams

The execution mode of an existing stream can be set to parallel by calling the parallel() method on the stream (p. 933). The parallelStream() method of the Collection interface can be used to create a parallel stream with a collection as the data source (p. 897). No other code is necessary for parallel execution, as the data partitioning and thread management for a parallel stream are handled by the API and the JVM. As with any stream, the stream is not executed until a terminal operation is invoked on it.

The isParallel() method of the stream interfaces can be used to determine whether the execution mode of a stream is parallel (p. 933).

Parallel Stream Execution

The Stream API allows a stream to be executed either sequentially or in parallel— meaning that all stream operations can execute either sequentially or in parallel. A sequential stream is executed in a single thread running on one CPU core. The elements in the stream are processed sequentially in a single pass by the stream operations that are executed in the same thread (p. 891).

A parallel stream is executed by different threads, running on multiple CPU cores in a computer. The stream elements are split into substreams that are processed by multiple instances of the stream pipeline being executed in multiple threads. The partial results from processing of each substream are merged (or combined) into a final result (p. 891).

Parallel streams utilize the Fork/Join Framework (§23.3, p. 1447) under the hood for executing parallel tasks. This framework provides support for the thread management necessary to execute the substreams in parallel. The number of threads employed during parallel stream execution is dependent on the CPU cores in the computer.

Figure 16.12, p. 963, illustrates parallel functional reduction using the three-argument reduce(identity, accumulator, combiner) terminal operation (p. 962).

Figure 16.14, p. 967, illustrates parallel mutable reduction using the three-argument collect(supplier, accumulator, combiner) terminal operation (p. 966).

Temporal Arithmetic with Periods – Date and Time

Temporal Arithmetic with Periods

The Period class provides plus and minus methods that return a copy of the original object that has been incremented or decremented by a specific amount specified in terms of a date unit—for example, as a number of years, months, or days. As the following code snippets show, only the value of a specific date unit is changed; the values of other date fields are unaffected. There is no implicit normalization performed, unless the normalized() method that normalizes only the months is called, adjusting the values of the months and years as necessary.

Click here to view code image

Period p6 = Period.of(2, 10, 30)  // P2Y10M30D
    .plusDays(10)                 // P2Y10M40D
    .plusMonths(8)                // P2Y18M40D
    .plusYears(1)                 // P3Y18M40D
    .normalized();                // P4Y6M40D

We can do simple arithmetic with periods. The code examples below use the plus() and the minus() methods of the Period class that take a TemporalAmount as an argument. Both the Period and the Duration classes implement the TemporalAmount interface. In the last assignment statement, we have shown the state of both new Period objects that are created.

Click here to view code image

Period p7 = Period.of(1, 1, 1);               // P1Y1M1D
Period p8 = Period.of(2, 12, 30);             // P2Y12M30D
Period p9 = p8.minus(p7);                     // P1Y11M29D
p8 = p8.plus(p7).plus(p8);                    // P3Y13M31D, P5Y25M61D

Click here to view code image

Period plusYears/minusYears(long years)
Period plusMonths/minusMonths(long months)
Period plusDays/minusDays(long days)

Return a copy of this period, with the specified value for the date unit added or subtracted. The values of other date units are unaffected.

Click here to view code image

Period plus(TemporalAmount amount)
Period minus(TemporalAmount amount)

Return a copy of this period, with the specified temporal amount added or subtracted. The amount is of the interface type TemporalAmount that is implemented by the classes Period and Duration, but only Period is valid here. The operation is performed separately on each date unit. There is no normalization performed. A DateTimeException is thrown if the operation cannot be performed.

Period normalized()

Returns a copy of this period where the years and months are normalized. The number of days is not affected.

Click here to view code image

Period negated()
Period multipliedBy(int scalar)

Return a new instance of Period where the value of each date unit in this period is individually negated or multiplied by the specified scalar, respectively.

We can also do simple arithmetic with dates and periods. The following code uses the plus() and minus() methods of the LocalDate class that take a TemporalAmount as an argument (p. 1040). Note the adjustments performed to the month and the day fields to return a valid date in the last assignment statement.

Click here to view code image

Period p10 = Period.of(1, 1, 1);               // P1Y1M1D
LocalDate date1 = LocalDate.of(2021, 3, 1);    // 2021-03-01
LocalDate date2 = date1.plus(p10);             // 2022-04-02
LocalDate date3 = date1.minus(p10);            // 2020-01-31

We can add and subtract periods from LocalDate and LocalDateTime objects, but not from LocalTime objects, as a LocalTime object has only time fields.

Click here to view code image

LocalTime time = LocalTime.NOON;
time = time.plus(p10);     // java.time.temporal.UnsupportedTemporalTypeException

Example 17.5 is a simple example to illustrate implementing period-based loops. The method reserveDates() at (1) is a stub for reserving certain dates, depending on the period passed as an argument. The for(;;) loop at (2) uses the Local-Date.isBefore() method to terminate the loop, and the LocalDate.plus() method to increment the current date with the specified period.

Example 17.5 Period-Based Loop

Click here to view code image

import java.time.LocalDate;
import java.time.Period;
public class PeriodBasedLoop {
  public static void main(String[] args) {
    reserveDates(Period.ofDays(7),
                 LocalDate.of(2021, 10, 20), LocalDate.of(2021, 11, 20));
    System.out.println();
    reserveDates(Period.ofMonths(1),
                 LocalDate.of(2021, 10, 20), LocalDate.of(2022, 1, 20));
    System.out.println();
    reserveDates(Period.of(0, 1, 7),
                 LocalDate.of(2021, 10, 20), LocalDate.of(2022, 1, 21));
  }
  public static void reserveDates(Period period,                 // (1)
                                  LocalDate fromDate,
                                  LocalDate toDateExclusive) {
    System.out.println(“Start date: ” + fromDate);
    for (LocalDate date = fromDate.plus(period);                 // (2)
         date.isBefore(toDateExclusive);
         date = date.plus(period)) {
      System.out.println(“Reserved (” + period + “): ” + date);
    }
    System.out.println(“End date: ” + toDateExclusive);
  }
}

Output from the program:

Click here to view code image

Start date: 2021-10-20
Reserved (P7D): 2021-10-27
Reserved (P7D): 2021-11-03
Reserved (P7D): 2021-11-10
Reserved (P7D): 2021-11-17
End date: 2021-11-20
Start date: 2021-10-20
Reserved (P1M): 2021-11-20
Reserved (P1M): 2021-12-20
End date: 2022-01-20
Start date: 2021-10-20
Reserved (P1M7D): 2021-11-27
Reserved (P1M7D): 2022-01-03
End date: 2022-01-21

We conclude this section with Example 17.6, which brings together some of the methods of the Date and Time API. Given a date of birth, the method birthdayInfo() at (1) calculates the age and the time until the next birthday. The age is calculated at (2) using the Period.between() method, which computes the period between two dates. The date for the next birthday is set at (3) as the birth date with the current year. The if statement at (4) adjusts the next birthday date by 1 year at (5), if the birthday has already passed. The statement at (6) calculates the time until the next birthday by calling the LocalDate.until() method. We could also have used the Period.between() method at (6). The choice between these methods really depends on which method makes the code more readable in a given context.

Example 17.6 More Temporal Arithmetic

Click here to view code image

import java.time.LocalDate;
import java.time.Month;
import java.time.Period;
public class ActYourAge {
  public static void main(String[] args) {
    birthdayInfo(LocalDate.of(1981, Month.AUGUST, 19));
    birthdayInfo(LocalDate.of(1935, Month.JANUARY, 8));
  }
  public static void birthdayInfo(LocalDate dateOfBirth) {           // (1)
    LocalDate today = LocalDate.now();
    System.out.println(“Today:         ” + today);
    System.out.println(“Date of Birth: ” + dateOfBirth);
    Period p1 = Period.between(dateOfBirth, today);                  // (2)
    System.out.println(“Age:           ” +
                                 p1.getYears()  + ” years, ” +
                                 p1.getMonths() + ” months, and ” +
                                 p1.getDays()   + ” days”);
    LocalDate nextBirthday =  dateOfBirth.withYear(today.getYear()); // (3)
    if (nextBirthday.isBefore(today) ||                              // (4)
        nextBirthday.isEqual(today)) {
      nextBirthday = nextBirthday.plusYears(1);                      // (5)
    }
    Period p2 = today.until(nextBirthday);                           // (6)
    System.out.println(“Birthday in ” + p2.getMonths() + ” months and ” +
                                        p2.getDays()   + ” days”);
  }
}

Possible output from the program:

Click here to view code image

Today:         2021-03-05
Date of Birth: 1981-08-19
Age:           39 years, 6 months, and 14 days
Birthday in 5 months and 14 days
Today:         2021-03-05
Date of Birth: 1935-01-08
Age:           86 years, 1 months, and 25 days
Birthday in 10 months and 3 days

Differences between Periods and Durations – Date and Time

Differences between Periods and Durations

Table 17.4 summarizes the differences between selected methods of the Period and the Duration classes, mainly in regard to the temporal units supported, representation for parsing and formatting, and comparison. N/A stands for Not Applicable.

Table 17.4 Some Differences between the Period Class and the Duration Class

MethodsThe Period classThe Duration class
of(amount, unit)N/AValid ChronoUnits: NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS, HALF_DAYS, DAYS (p. 1065).
parse(text) toString()Representation based on: PnYnMnD and PnW (p. 1057).Representation based on: PnDTnHnMn.nS (p. 1065).
get(unit)Supported ChronoUnits: YEARS, MONTHS, DAYS (p. 1059).Supported ChronoUnits: NANOS, SECONDS (p. 1067).
getUnits()Supported ChronoUnits: YEARS, MONTHS, DAYS (p. 1059).Supported ChronoUnits: NANOS, SECONDS (p. 1067).
equals(other)Based on values of individual units (p. 1059).Based on total length (p. 1067).
compareTo(other)N/ANatural order: total length (p. 1067).
minus(amount, unit) plus(amount, unit)N/AValid ChronoUnits: NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS, HALF_DAYS, DAYS (p. 1069).
abs()N/AReturns copy with positive length (p. 1069).
dividedBy(divisor)N/AReturns copy after dividing by divisor (p. 1069).
normalized()Only years and months normalized (p. 1061).N/A

17.7 Working with Time Zones and Daylight Savings

The following three classes in the java.time package are important when dealing with date and time in different time zones and daylight saving hours: ZoneId, ZoneOffset, and ZonedDateTime.

UTC (Coordinated Universal Time) is the primary time standard used for keeping time around the world. UTC/Greenwich is the time at Royal Observatory, Greenwich, England. It is the basis for defining time in different regions of the world.

Time Zones and Zone Offsets

A time zone defines a region that observes the same standard time. The time observed in a region is usually referred to as the local time. A time zone is described by a zone offset from UTC/Greenwich and any rules for applying daylight saving time (DST). Time zones that practice DST obviously have variable offsets during the year to account for DST.

In Java, each time zone has a zone ID that is represented by the class java.time.ZoneId. The class java.time.ZoneOffset, which extends the ZoneId class, represents a zone offset from UTC/Greenwich. For example, the time zone with US/Eastern as the zone ID has the offset -04:00 during daylight saving hours—that is, it is 4 hours behind UTC/Greenwich when DST is in effect.

The time zone offset at UTC/Greenwich is represented by ZoneOffset.UTC and, by convention, is designated by the letter Z. GMT (Greenwich Mean Time) has zero offset from UTC/Greenwich (UTC+0), thus the two are often used as synonyms; for example, GMT-4 is equivalent to UTC-4. However, GMT is a time zone, whereas UTC is a time standard.

Java uses the IANA Time Zone Database (TZDB) maintained by the Internet Assigned Numbers Authority (IANA) that updates the database regularly, in particular, regarding changes to the rules for DST practiced by a time zone (p. 1073).

A set with names of available time zones can readily be obtained by calling the ZoneId.getAvailableZoneIds() method. Time zones have unique names of the form Area/Location—for example, US/Eastern, Europe/Oslo. The following code prints a very long list of time zones that are available to a Java application.

Click here to view code image

ZoneId.getAvailableZoneIds()                 // Prints a long list of zone names.
      .stream()
      .sorted()
      .forEachOrdered(System.out::println);  // Output not shown intentionally.

The ZoneId.of() method creates an appropriate zone ID depending on the format of its argument:

  • UTC-equivalent ID, if only “Z”, “UTC”, or “GMT” is specified. As these designations are equivalent, the result is ZoneOffset.UTC; that is, it represents the offset UTC+0.
  • Offset-based ID, if the format is “+hh:mm” or “-hh:mm”—for example, “+04:00”, “-11:30”. The result is an instance of the ZoneOffset class with the parsed offset.
  • Prefix offset-based ID, if the format is the prefix UTC or GMT followed by a numerical offset—for example, “UTC+04:00”, “GMT-04:00”. The result is a time zone represented by a ZoneId with the specified prefix and a parsed offset.
  • Region-based ID, if the format is “Area/Location”—for example, “US/Eastern”, “Europe/Oslo”. The result is a ZoneId that can be used, among other things, to look up the underlying zone rules associated with the zone ID. In the examples in this section, a zone ID is specified in the format of a region-based ID.

The code below creates a region-based zone ID. The method ZoneId.systemDefault() returns the system default zone ID.

Click here to view code image

ZoneId estZID = ZoneId.of(“US/Eastern”);              // Create a time zone ID.
System.out.println(estZID);                           // US/Eastern
System.out.println(ZoneId.systemDefault());           // Europe/Oslo

Selected methods in the ZoneId abstract class are presented below. The concrete class ZoneOffset extends the ZoneId class.

Click here to view code image

static ZoneId of(String zoneId)

Returns an appropriate zone ID depending on the format of the zone ID string. See the previous discussion on zone ID.

Click here to view code image

String          toString()
abstract String getId()

Return a string with the zone ID, typically in one of the formats accepted by the of() method.

abstract ZoneRules getRules()

Retrieves the associated time zone rules for this zone ID. The rules determine the functionality associated with a time zone, such as daylight savings (p. 1082).

Click here to view code image

static Set<String> getAvailableZoneIds()

Returns a set with the available time zone IDs.

static ZoneId systemDefault()

Returns the zone ID of the default time zone of the system.

Reducing – Streams

Reducing

Collectors that perform common statistical operations, such as counting, averaging, and so on, are special cases of functional reduction that can be implemented using the Collectors.reducing() method.

Click here to view code image

static <T> Collector<T,?,Optional<T>> reducing(BinaryOperator<T> bop)

Returns a collector that performs functional reduction, producing an Optional with the cumulative result of applying the binary operator bop on the input elements: e1 bop e2 bop e3 …, where each ei is an input element. If there are no input elements, an empty Optional<T> is returned.

Note that the collector reduces input elements of type T to a result that is an Optional of type T.

Click here to view code image

static <T> Collector<T,?,T> reducing(T identity, BinaryOperator<T> bop)

Returns a collector that performs functional reduction, producing the cumulative result of applying the binary operator bop on the input elements: identity bop e1 bop e2 …, where each ei is an input element. The identity value is the initial value to accumulate. If there are no input elements, the identity value is returned.

Note that the collector reduces input elements of type T to a result of type T.

Click here to view code image

static <T,U> Collector<T,?,U> reducing(
       U                               identity,
       Function<? super T,? extends U> mapper,
       BinaryOperator<U>               bop)

Returns a collector that performs a map-reduce operation. It maps each input element of type T to a mapped value of type U by applying the mapper function, and performs functional reduction on the mapped values of type U by applying the binary operator bop. The identity value of type U is used as the initial value to accumulate. If the stream is empty, the identity value is returned.

Note that the collector reduces input elements of type T to a result of type U.

Collectors returned by the Collectors.reducing() methods effectively perform equivalent functional reductions as the reduce() methods of the stream interfaces. However, the three-argument method Collectors.reducing(identity, mapper, bop) performs a map-reduce operation using a mapper function and a binary operator bop, whereas the Stream.reduce(identity, accumulator, combiner) performs a reduction using an accumulator and a combiner (p. 955). The accumulator is a BiFunction<U,T,U> that accumulates a partially accumulated result of type U with an element of type T, whereas the bop is a BinaryOperator<U> that accumulates a partially accumulated result of type U with an element of type U.

The following comparators are used in the examples below:

Click here to view code image

// Comparator to compare CDs by title.
Comparator<CD> cmpByTitle = Comparator.comparing(CD::title);        // (1)
// Comparator to compare strings by their length.
Comparator<String> byLength = Comparator.comparing(String::length); // (2)

The collector returned by the Collectors.reducing() method is used as a standalone collector at (3) to find the CD with the longest title. The result of the operation is an Optional<String> as there might not be any input elements. This operation is equivalent to using the Stream.reduce() terminal operation at (4).

Click here to view code image

Optional<String> longestTitle1 = CD.cdList.stream()
    .map(CD::title)
    .collect(Collectors.reducing(
        BinaryOperator.maxBy(byLength)));            // (3) Standalone collector
System.out.println(longestTitle1.orElse(“No title”));// Keep on Erasing
Optional<String> longestTitle2 = CD.cdList.stream()  // Stream<CD>
    .map(CD::title)                                  // Stream<String>
    .reduce(BinaryOperator.maxBy(byLength));         // (4) Stream.reduce(bop)

The collector returned by the one-argument Collectors.reducing() method is used as a downstream collector at (5) to find the CD with the longest title in each group classified by the year a CD was released. The collector at (5) is equivalent to the collector returned by the Collectors.maxBy(cmpByTitle) method.

Click here to view code image

Map<Year, Optional<CD>> cdWithMaxTitleByYear = CD.cdList.stream()
    .collect(Collectors.groupingBy(
         CD::year,
         Collectors.reducing(                        // (5) Downstream collector
             BinaryOperator.maxBy(cmpByTitle))
         ));
System.out.println(cdWithMaxTitleByYear);
// {2017=Optional[<Jaav, “Java Jive”, 8, 2017, POP>],
//  2018=Optional[<Funkies, “Lambda Dancing”, 10, 2018, POP>]}
System.out.println(cdWithMaxTitleByYear.get(Year.of(2018))
                       .map(CD::title).orElse(“No title”)); // Lambda Dancing

The collector returned by the three-argument Collectors.reducing() method is used as a downstream collector at (6) to find the longest title in each group classified by the year a CD was released. Note that the collector maps a CD to its title. The longest title is associated with the map value for each group classified by the year a CD was released. The collector will return an empty string (i.e., the identity value “”) if there are no CDs in the stream. In comparison, the collector Collectors.mapping() at (7) also maps a CD to its title, and uses the downstream collector Collectors.maxBy(byLength) at (7) to find the longest title (p. 993). The result in this case is an Optional<String>, as there might not be any input elements.

Click here to view code image

Map<Year, String> longestTitleByYear = CD.cdList.stream()
    .collect(Collectors.groupingBy(
         CD::year,
         Collectors.reducing(“”, CD::title,          // (6) Downstream collector
             BinaryOperator.maxBy(byLength))
         ));
System.out.println(longestTitleByYear);  // {2017=Java Jive, 2018=Keep on Erasing}
System.out.println(longestTitleByYear.get(Year.of(2018)));      // Keep on Erasing
Map<Year, Optional<String>> longestTitleByYear2 = CD.cdList.stream()
    .collect(Collectors.groupingBy(
         CD::year,
         Collectors.mapping(CD::title,               // (7) Downstream collector
             Collectors.maxBy(byLength))
         ));
System.out.println(longestTitleByYear2);
// {2017=Optional[Java Jive], 2018=Optional[Keep on Erasing]}
System.out.println(longestTitleByYear2.get(Year.of(2018))
                       .orElse(“No title.”));        // Keep on Erasing

The pipeline below groups CDs according to the year they were released. For each group, the collector returned by the three-argument Collectors.reducing() method performs a map-reduce operation at (8) to map each CD to its number of tracks and accumulate the tracks in each group. This map-reduce operation is equivalent to the collector returned by the Collectors.summingInt() method at (9).

Click here to view code image

Map<Year, Integer> noOfTracksByYear = CD.cdList.stream()
    .collect(Collectors.groupingBy(
         CD::year,
         Collectors.reducing(                        // (8) Downstream collector
             0, CD::noOfTracks, Integer::sum)));
System.out.println(noOfTracksByYear);                   // {2017=14, 2018=28}
System.out.println(noOfTracksByYear.get(Year.of(2018)));// 28
Map<Year, Integer> noOfTracksByYear2 = CD.cdList.stream()
    .collect(Collectors.groupingBy(
         CD::year,
         Collectors.summingInt(CD::noOfTracks)));    // (9) Special case collector

Side Effects – Streams

Side Effects

Efficient execution of parallel streams that produces the desired results requires the stream operations (and their behavioral parameters) to avoid certain side effects.

  • Non-interfering behaviors

The behavioral parameters of stream operations should be non-interfering (p. 909)—both for sequential and parallel streams. Unless the stream data source is concurrent, the stream operations should not modify it during the execution of the stream. See building streams from collections (p. 897).

  • Stateless behaviors

The behavioral parameters of stream operations should be stateless (p. 909)— both for sequential and parallel streams. A behavioral parameter implemented as a lambda expression should not depend on any state that might change during the execution of the stream pipeline. The results from a stateful behavioral parameter can be nondeterministic or even incorrect. For a stateless behavioral parameter, the results are always the same.

Shared state that is accessed by the behavior parameters of stream operations in a pipeline is not a good idea. Executing the pipeline in parallel can lead to race conditions in accessing the global state, and using synchronization code to provide thread-safety may defeat the purpose of parallelization. Using the three-argument reduce() or collect() method can be a better solution to encapsulate shared state.

The intermediate operations distinct(), skip(), limit(), and sorted() are stateful (p. 915, p. 915, p. 917, p. 929). See also Table 16.3, p. 938. They can carry extra performance overhead when executed in a parallel stream, as such an operation can entail multiple passes over the data and may require significant data buffering.

Ordering

An ordered stream (p. 891) processed by operations that preserve the encounter order will produce the same results, regardless of whether it is executed sequentially or in parallel. However, repeated execution of an unordered stream— sequential or parallel—can produce different results.

Preserving the encounter order of elements in an ordered parallel stream can incur a performance penalty. The performance of an ordered parallel stream can be improved if the ordering constraint is removed by calling the unordered() intermediate operation on the stream (p. 932).

The three stateful intermediate operations distinct(), skip(), and limit() can improve performance in a parallel stream that is unordered, as compared to one that is ordered (p. 915, p. 915, p. 917). The distinct() operation need only buffer any occurrence of a duplicate value in the case of an unordered parallel stream, rather than the first occurrence. The skip() operation can skip any n elements in the case of an unordered parallel stream, not necessarily the first n elements. The limit() operation can truncate the stream after any n elements in the case of an unordered parallel stream, and not necessarily after the first n elements.

The terminal operation findAny() is intentionally nondeterministic, and can return any element in the stream (p. 952). It is specially suited for parallel streams.

The forEach() terminal operation ignores the encounter order, but the forEachOrdered() terminal operation preserves the order (p. 948). The sorted() stateful intermediate operation, on the other hand, enforces a specific encounter order, regardless of whether it executed in a parallel pipeline (p. 929).

Autoboxing and Unboxing of Numeric Values

As the Stream API allows both object and numeric streams, and provides support for conversion between them (p. 934), choosing a numeric stream when possible can offset the overhead of autoboxing and unboxing in object streams.

As we have seen, in order to take full advantage of parallel execution, composition of a stream pipeline must follow certain rules to facilitate parallelization. In summary, the benefits of using parallel streams are best achieved when:

  • The stream data source is of a sufficient size and the stream is easily splittable into substreams.
  • The stream operations have no adverse side effects and are computation-intensive enough to warrant parallelization.

Date and Time API Overview – Date and Time

17.1 Date and Time API Overview

The java.time package provides the main support for dealing with dates and times. It contains the main classes that represent date and time values, including those that represent an amount of time.

  • LocalDate: This class represents a date in terms of date fields (year, month, day). A date has no time fields or a time zone. (This class is not to be confused with the java.util.Date legacy class.)
  • LocalTime: This class represents time in a 24-hour day in terms of time fields (hour, minute, second, nanosecond). A time has no date fields or a time zone.
  • LocalDateTime: This class represents the concept of date and time combined, in terms of both date and time fields. A date-time has no time zone.
  • ZonedDateTime: This class represents the concept of a date-time with a time zone— that is, a zoned date-time.
  • Instant: This class represents a measurement of time as a point on a timeline starting from a specific origin (called the epoch). An instant is represented with nanosecond precision and can be a negative value.
  • Period: This class represents an amount or quantity of time in terms of number of days, months, and years, which can be negative. A period is a date-based amount of time. It has no notion of a clock time, a date, or a time zone.
  • Duration: This class represents an amount or quantity of time in terms of number of seconds and nanoseconds, which can be negative. A duration is a time-based amount of time. As with instants, durations have no notion of a clock time, a date, or a time zone.

We will use the term temporal objects to mean objects of classes that represent temporal concepts.

The temporal classes implement immutable and thread-safe temporal objects. The state of an immutable object cannot be changed. Any method that is supposed to modify such an object returns a modified copy of the temporal object. It is a common mistake to ignore the object returned, thinking that the current object has been modified. Thread-safety guarantees that the state of such an object is not compromised by concurrent access.

Table 17.1 summarizes the fields in selected classes from the Date and Time API. The table shows the relative size of the objects of these classes in terms of their fields; for example, a LocalTime has only time fields, whereas a ZonedDateTime has time-, date-, and zone-based fields. The three asterisks *** indicate that this information can be derived by methods provided by the class, even though these fields do not exist in an object of the Duration class.

Table 17.1 Fields in Selected Classes in the Date and Time API

ClassesYearMonthDayHoursMinutesSeconds/NanosZone offsetZone ID
LocalTime
(p. 1027)
   +++  
LocalDate
(p. 1027)
+++     
LocalDateTime
(p. 1027)
++++++  
ZonedDate-Time
(p. 1072)
++++++++
Instant
(p. 1049)
     +  
Period
(p. 1057)
+++     
Duration
(p. 1064)
  *********+  

The information in Table 17.1 is crucial to understanding how the objects of these classes can be used. A common mistake is to access, format, or parse a temporal object that does not have the required temporal fields. For example, a LocalTime object has only time fields, so trying to format it with a formatter for date fields will result in a java.time.DateTimeException. Many methods will also throw an exception if an invalid or an out-of-range argument is passed in the method call. It is important to keep in mind which temporal fields constitute the state of a temporal object.

Table 17.2 provides an overview of the method naming conventions used in the temporal classes LocalTime, LocalDate, LocalDateTime, ZonedDateTime, and Instant. This method naming convention makes it easy to use the API, as it ensures method naming is standardized across all temporal classes. Depending on the method, the suffix XXX in a method name can be a specific field (e.g., designate the Year field in the getYear method name), a specific unit (e.g., designate the unit Days for number of days in the plusDays method name), or a class name (e.g., designate the class type in the toLocalDate method name).

Table 17.2 Selected Method Name Prefixes in the Temporal Classes

Prefix (parameters not shown)Usage
atXXX()Create a new temporal object by combining this temporal object with another temporal object. Not provided by the ZonedDateTime class.
of() ofXXX()Static factory methods for constructing temporal objects from constituent temporal fields.
get() getXXX()Access specific fields in this temporal object.
isXXX()Check specific properties of this temporal object.
minus() minusXXX()Return a copy of this temporal object after subtracting an amount of time.
plus() plusXXX()Return a copy of this temporal object after adding an amount of time.
toXXX()Convert this temporal object to another type.
with() withXXX()Create a copy of this temporal object with one field modified.

Apart from the methods shown in Table 17.2, the selected methods shown in Table 17.3 are common to the temporal classes LocalTime, LocalDate, LocalDateTime, ZonedDateTime, and Instant.

Table 17.3 Selected Common Methods in the Temporal Classes

Method (parameters not shown)Usage
now()Static method that obtains the current time from the system or specified clock in the default or specified time zone.
from()Static method to obtain an instance of this temporal class from another temporal.
until()Calculate the amount of time from this temporal object to another temporal object.
toString()Create a text representation of this temporal object.
equals()Compare two temporal objects for equality.
hashCode()Returns a hash code for this temporal object.
compareTo()Compare two temporal objects. (The class ZonedDateTime does not implement the Comparable<E> interface.)
parse()Static method to obtain a temporal instance from a specified text string (§18.6, p. 1127).
format()Create a text representation of this temporal object using a specified formatter (§18.6, p. 1127). (Instant class does not provide this method.)

Subsequent sections in this chapter provide ample examples of how to create, combine, convert, access, and compare temporal objects, including the use of temporal arithmetic and dealing with time zones and daylight savings. For formatting and parsing temporal objects, see §18.6, p. 1127.

Comparing Dates and Times – Date and Time

Comparing Dates and Times

It is also possible to check whether a temporal object represents a point in time before or after another temporal object of the same type. In addition, the LocalDate and LocalDateTime classes provide an isEqual() method that determines whether a temporal object is equal to another temporal object of the same type. In contrast, the equals() method allows equality comparison with an arbitrary object.

Click here to view code image

LocalDate d1 = LocalDate.of(1948, 2, 28);                  // 1948-02-28
LocalDate d2 = LocalDate.of(1949, 3, 1);                   // 1949-03-01
boolean result1 = d1.isBefore(d2);                         // true
boolean result2 = d2.isAfter(d1);                          // true
boolean result3 = d1.isAfter(d1);                          // false
boolean result4 = d1.isEqual(d2);                          // false
boolean result5 = d1.isEqual(d1);                          // true
boolean result6 = d1.isLeapYear();                         // true

The temporal classes implement the Comparable<E> interface, providing the compareTo() method so that temporal objects can be compared in a meaningful way. The temporal classes also override the equals() and the hashCode() methods of the Object class. These methods make it possible to both search for and sort temporal objects.

Click here to view code image

// LocalTime
boolean isBefore(LocalTime other)
boolean isAfter(LocalTime other)

Determine whether this LocalTime represents a point on the timeline before or after the other time, respectively.

Click here to view code image

// LocalDate
boolean isBefore(ChronoLocalDate other)
boolean isAfter(ChronoLocalDate other)
boolean isEqual(ChronoLocalDate other)
boolean isLeapYear()

The first two methods determine whether this LocalDate represents a point on the timeline before or after the other date, respectively. The LocalDate class implements the ChronoLocalDate interface.

The third method determines whether this date is equal to the specified date.

The last method checks for a leap year according to the ISO proleptic calendar system rules.

Click here to view code image

// LocalDateTime
boolean isBefore(ChronoLocalDateTime<?> other)
boolean isAfter(ChronoLocalDateTime<?> other)
boolean isEqual(ChronoLocalDateTime<?> other)

The first two methods determine whether this LocalDateTime represents a point on the timeline before or after the specified date-time, respectively. The Local-DateTime class implements the ChronoLocalDateTime<LocalDateTime> interface.

The third method determines whether this date-time object represents the same point on the timeline as the other date-time.

Click here to view code image

int compareTo(LocalTime other)                // LocalTime
int compareTo(ChronoLocalDate other)          // LocalDate
int compareTo(ChronoLocalDateTime<?> other)   // LocalDateTime

Compare this temporal object to another temporal object. The three temporal classes implement the Comparable<E> functional interface. The compareTo() method returns 0 if the two temporal objects are equal, a negative value if this temporal object is less than the other temporal object, and a positive value if this temporal object is greater than the other temporal object.

Temporal Arithmetic with Dates and Times – Date and Time

Temporal Arithmetic with Dates and Times

The temporal classes provide plus and minus methods that return a copy of the original object that has been incremented or decremented by a specific amount of time— for example, by number of hours or by number of months.

The LocalTime and LocalDate classes provide plus/minus methods to increment/ decrement a time or a date by a specific amount in terms of a time unit (e.g., hours, minutes, and seconds) or a date unit (e.g., years, months, and days), respectively. The LocalDateTime class provides plus/minus methods to increment/decrement a date-time object by an amount that is specified in terms of either a time unit or a date unit. For example, the plusMonths(m) and plus(m, ChronoUnit.MONTHS) method calls to a LocalDate object will return a new LocalDate object after adding the specified number of months passed as an argument to the method. Similarly, the minus-Minutes(mm) and minus(mm, ChronoUnit.MINUTES) method calls on a LocalTime class will return a new LocalTime object after subtracting the specified number of minutes passed as an argument to the method. The change is relative, and is reflected in the new temporal object that is returned. Such plus/minus methods are also called relative adjusters, in contrast to absolute adjusters (p. 1035). The ChronoUnit enum type implements the TemporalUnit interface (p. 1044).

Click here to view code image

// LocalTime, LocalDateTime
LocalTime/LocalDateTime minusHours(long hours)
LocalTime/LocalDateTime plusHours(long hours)
LocalTime/LocalDateTime minusMinutes(long minutes)
LocalTime/LocalDateTime plusMinutes(long minutes)
LocalTime/LocalDateTime minusSeconds(long seconds)
LocalTime/LocalDateTime plusSeconds(long seconds)
LocalTime/LocalDateTime minusNanos(long nanos)
LocalTime/LocalDateTime plusNanos(long nanos)

Return a copy of this LocalTime or LocalDateTime object with the specified amount either subtracted or added to the value of a specific time field. The calculation always wraps around midnight.

For the methods of the LocalDateTime class, a DateTimeException is thrown if the result exceeds the date range.

Click here to view code image

// LocalDate, LocalDateTime
LocalDate/LocalDateTime minusYears(long years)
LocalDate/LocalDateTime plusYears(long years)
LocalDate/LocalDateTime minusMonths(long months)
LocalDate/LocalDateTime plusMonths(long months)
LocalDate/LocalDateTime minusWeeks(long weeks)
LocalDate/LocalDateTime plusWeeks(long weeks)
LocalDate/LocalDateTime minusDays(long days)
LocalDate/LocalDateTime plusDays(long days)

Return a copy of this LocalDate or LocalDateTime with the specified amount either subtracted or added to the value of a specific date field.

All methods throw a DateTimeException if the result exceeds the date range.

The first four methods will change the day of the month to the last valid day of the month if necessary, when the day of the month becomes invalid as a result of the operation.

The last four methods will adjust the month and year fields as necessary to ensure a valid result.

Click here to view code image

// LocalTime, LocalDate, LocalDateTime
LocalTime/LocalDate/LocalDateTime minus(long amountToSub,
                                        TemporalUnit unit)
LocalTime/LocalDate/LocalDateTime plus(long amountToAdd, TemporalUnit unit)
boolean isSupported(TemporalUnit unit)

The minus() and plus() methods return a copy of this temporal object with the specified amount subtracted or added, respectively, according to the TemporalUnit specified. The ChronoUnit enum type implements the TemporalUnit interface, and its enum constants define specific temporal units (p. 1044).

The isSupported() method checks if the specified TemporalUnit is supported by this temporal object. It avoids an exception being thrown if it has been determined that the unit is supported.

The minus() or the plus() method can result in any one of these exceptions: DateTimeException (the amount cannot be subtracted or added), Unsupported-TemporalTypeException (unit is not supported), or ArithmeticException (numeric overflow occurred).

Click here to view code image

// LocalTime, LocalDate, LocalDateTime
LocalTime/LocalDate/LocalDateTime minus(TemporalAmount amountToSub)
LocalTime/LocalDate/LocalDateTime plus(TemporalAmount amountToAdd)

Return a copy of this temporal object with the specified temporal amount subtracted or added, respectively. The classes Period (p. 1057) and Duration (p. 1064) implement the TemporalAmount interface.

The minus() or the plus() method can result in any one of these exceptions: DateTimeException (the temporal amount cannot be subtracted or added) or ArithmeticException (numeric overflow occurred).

Click here to view code image

// LocalTime, LocalDate, LocalDateTime
long until(Temporal endExclusive, TemporalUnit unit)

Calculates the amount of time between two temporal objects in terms of the specified TemporalUnit (p. 1044). The start and the end points are this temporal object and the specified temporal argument endExclusive, where the end point is excluded. The result will be negative if the other temporal is before this temporal.

The until() method can result in any one of these exceptions: DateTime-Exception (the temporal amount cannot be calculated or the end temporal cannot be converted to the appropriate temporal object), UnsupportedTemporalType-Exception (unit is not supported), or ArithmeticException (numeric overflow occurred).

Click here to view code image

// LocalDate
Period until(ChronoLocalDate endDateExclusive)

Calculates the amount of time between this date and another date as a Period (p. 1057). The calculation excludes the end date. The LocalDate class implements the ChronoLocalDate interface.

Example 17.3 demonstrates what we can call temporal arithmetic, where a LocalDate object is modified by adding or subtracting an amount specified as days, weeks, or months. Note how the value of the date fields is adjusted after each operation. In Example 17.3, the date 2021-10-23 is created at (1), and 10 months, 3 weeks, and 40 days are successively added to the new date object returned by each plus method call at (2), (3), and (4), respectively, resulting in the date 2022-10-23. We then subtract 2 days, 4 weeks, and 11 months successively from the new date object returned by each minus() method call at (5), (6), and (7), respectively, resulting in the date 2021-10-23. The method calls at (5), (6), and (7) are passed the temporal unit explicitly. In Example 17.3, several assignment statements are used to print the intermediate dates, but the code can be made more succinct by method chaining.

Click here to view code image

LocalDate date = LocalDate.of(2021, 10, 23);             // 2021-10-23
date = date.plusMonths(10).plusWeeks(3).plusDays(40);    // Method chaining
System.out.println(date);                                // 2022-10-23
date = date.minus(2, ChronoUnit.DAYS)
           .minus(4, ChronoUnit.WEEKS)
           .minus(11, ChronoUnit.MONTHS);                // Method chaining
System.out.println(date);                                // 2021-10-23

The following code snippet illustrates the wrapping of time around midnight, as one would expect on a 24-hour clock. Each method call returns a new LocalTime object.

Click here to view code image

LocalTime witchingHour = LocalTime.MIDNIGHT              // 00:00
    .plusHours(14)                                       // 14:00
    .plusMinutes(45)                                     // 14:45
    .plusMinutes(30)                                     // 15:15
    .minusHours(15)                                      // 00:15
    .minusMinutes(15);                                   // 00:00

The next code snippet illustrates how the plusYears() method adjusts the day of the month, if necessary, when the year value is changed. The year in the date 2020-02-29 is changed to 2021 by adding 1 year, resulting in the following date: 2021-02-29. The plusYears() method adjusts the day of the month to the last valid day of the month, 28; as the year 2021 is not a leap year, the month of February cannot have 29 days.

Click here to view code image

LocalDate date5 = LocalDate.of(2020, 2, 29);  // Original: 2020-02-29
date5 = date5.plusYears(1);                   // Expected: 2021-02-29
System.out.println(“Date5: ” + date5);        // Adjusted: 2021-02-28

A temporal can also be adjusted by a temporal amount—for example, by a Period (p. 1057) or a Duration (p. 1064). The methods plus() and minus() accept the temporal amount as an argument, as shown by the code below.

Click here to view code image

LocalTime busDep = LocalTime.of(12, 15);                   // 12:15
Duration d1 = Duration.ofMinutes(30);                      // PT30M
LocalTime nextBusDep = busDep.plus(d1);                    // 12:45
LocalDate birthday = LocalDate.of(2020, 10, 23);           // 2020-10-23
Period p1 = Period.ofYears(1);                             // P1Y
LocalDate nextBirthday = birthday.plus(p1);                // 2021-10-23

The until() method can be used to calculate the amount of time between two compatible temporal objects. The code below calculates the number of days to New Year’s Day from the current date; the result, of course, will depend on the current date. In the call to the until() method at (1), the temporal unit specified is ChronoUnit.DAYS, as we want the difference between the dates to be calculated in days.

Click here to view code image

LocalDate currentDate = LocalDate.now();
LocalDate newYearDay = currentDate.plusYears(1).withMonth(1).withDayOfMonth(1);
long daysToNewYear = currentDate.until(newYearDay, ChronoUnit.DAYS); // (1)
System.out.println(“Current Date: ” + currentDate); // Current Date: 2021-03-08
System.out.println(“New Year’s Day: ” + newYearDay);// New Year’s Day: 2022-01-01
System.out.println(“Days to New Year: ” + daysToNewYear);// Days to New Year: 299

The statement at (1) below is meant to calculate the number of minutes until midnight from now, but throws a DateTimeException because it is not possible to obtain a LocalDateTime object from the end point, which is a LocalTime object.

Click here to view code image

long minsToMidnight = LocalDateTime.now()             // (1) DateTimeException!
         .until(LocalTime.MIDNIGHT.minusSeconds(1), ChronoUnit.MINUTES);

However, the statement at (2) executes normally, as both the start and end points are LocalTime objects.

Click here to view code image

long minsToMidnight = LocalTime.now()                 // (2)
         .until(LocalTime.MIDNIGHT.minusSeconds(1), ChronoUnit.MINUTES);

Example 17.3 Temporal Arithmetic

Click here to view code image

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class TemporalArithmetic {
  public static void main(String[] args) {
    LocalDate date = LocalDate.of(2021, 10, 23);           // (1)
    System.out.println(“Date:             ” + date);       // 2021-10-23
    date = date.plusMonths(10);                            // (2)
    System.out.println(“10 months after:  ” + date);       // 2022-08-23
    date = date.plusWeeks(3);                              // (3)
    System.out.println(“3 weeks after:    ” + date);       // 2022-09-13
    date = date.plusDays(40);                              // (4)
    System.out.println(“40 days after:    ” + date);       // 2022-10-23
    date = date.minus(2, ChronoUnit.DAYS);                 // (5)
    System.out.println(“2 days before:    ” + date);       // 2022-10-21
    date = date.minus(4, ChronoUnit.WEEKS);                // (6)
    System.out.println(“4 weeks before:   ” + date);       // 2022-09-23
    date = date.minus(11, ChronoUnit.MONTHS);              // (7)
    System.out.println(“11 months before: ” + date);       // 2021-10-23
  }
}

Output from the program:

Date:             2021-10-23
10 months after:  2022-08-23
3 weeks after:    2022-09-13
40 days after:    2022-10-23
2 days before:    2022-10-21
4 weeks before:   2022-09-23
11 months before: 2021-10-23

Using Temporal Units and Temporal Fields – Date and Time

17.3 Using Temporal Units and Temporal Fields

Temporal units and temporal fields allow temporal objects to be accessed and manipulated in a human-readable way.

For temporal units and temporal fields supported by the Period and Duration classes, see §17.5, p. 1057, and §17.6, p. 1064, respectively.

Temporal Units

The java.time.temporal.TemporalUnit interface represents a unit of measurement, rather than an amount of such a unit—for example, the unit years to qualify that an amount of time should be interpreted as number of years. The java.time.temporal.ChronoUnit enum type implements this interface, defining the temporal units by constant names to provide convenient unit-based access to manipulate a temporal object. Constants defined by the ChronoUnit enum type include the following temporal units, among others: SECONDS, MINUTES, HOURS, DAYS, MONTHS, and YEARS.

The output from Example 17.4 shows a table with all the temporal units defined by the ChronoUnit enum type. It is not surprising that not all temporal units are valid for all types of temporal objects. The time units, such as SECONDS, MINUTES, and HOURS, are valid for temporal objects that are time-based, such as LocalTime, LocalDateTime, and Instant. Likewise, the date units, such as DAYS, MONTHS, and YEARS, are valid units for temporal objects that are date-based, such as LocalDate, LocalDateTime, and ZonedDateTime.

A ChronoUnit enum constant can be queried by the following selected methods:

Duration getDuration()

Gets the estimated duration of this unit in the ISO calendar system. For example, ChronoUnit.DAYS.getDuration() has the duration PT24H (i.e., 24 hours).

boolean isDateBased()
boolean isTimeBased()

Check whether this unit is a date unit or a time unit, respectively. For example, ChronoUnit.HOURS.isDateBased() is false, but ChronoUnit.SECONDS.isTimeBased() is true.

Click here to view code image

boolean isSupportedBy(Temporal temporal)

Checks whether this unit is supported by the specified temporal object. For example, ChronoUnit.YEARS.isSupportedBy(LocalTime.MIDNIGHT) is false.

Click here to view code image

static ChronoUnit[] values()

Returns an array containing the unit constants of this enum type, in the order they are declared. This method is called at (2) in Example 17.4.

The temporal classes provide the method isSupported(unit) to determine whether a temporal unit is valid for a temporal object. In Example 17.4, this method is used at (3), (4), and (5) to determine whether each temporal unit defined by the ChronoUnit enum type is a valid unit for the different temporal classes.

The following methods of the temporal classes all accept a temporal unit that qualifies how a numeric quantity should be interpreted:

  • minus(amount, unit) and plus(amount, unit), (p. 1040)

Click here to view code image

LocalDate date = LocalDate.of(2021, 10, 23);
System.out.print(“Date ” + date);
date = date.minus(10, ChronoUnit.MONTHS).minus(3, ChronoUnit.DAYS);
System.out.println(” minus 10 months and 3 days: ” + date);
// Date 2021-10-23 minus 10 months and 3 days: 2020-12-20

LocalTime time = LocalTime.of(14, 15);
System.out.print(“Time ” + time);
time = time.plus(9, ChronoUnit.HOURS).plus(70, ChronoUnit.MINUTES);
System.out.println(” plus 9 hours and 70 minutes is ” + time);
// Time 14:15 plus 9 hours and 70 minutes is 00:25

  • until(temporalObj, unit), (p. 1040)

Click here to view code image

LocalDate fromDate = LocalDate.of(2021, 3, 1);
LocalDate xmasDate = LocalDate.of(2021, 12, 25);
long tilChristmas = fromDate.until(xmasDate, ChronoUnit.DAYS);
System.out.println(“From ” + fromDate + “, days until Xmas: ” + tilChristmas);
// From 2021-03-01, days until Xmas: 299

Working with Instants – Date and Time

17.4 Working with Instants

The temporal classes LocalTime, LocalDate, LocalDateTime, and ZonedDateTime are suitable for representing human time in terms of year, month, day, hour, minute, second, and time zone. The Instant class can be used for representing computer time, specially timestamps that identify to a higher precision when an event occurred on the timeline. Instants are suitable for persistence purposes—for example, in a database.

An Instant represents a point on the timeline, measured with nanosecond precision from a starting point or origin which is defined to be at 1970-01-01T00:00:00Z—that is, January 1, 1970, at midnight—and is called the epoch. Instants before the epoch have negative values, whereas instants after the epoch have positive values. The Z represents the time zone designator for the zero UTC offset, which is the time zone offset for all instants in the UTC standard (p. 1072). The text representation of the epoch shown above is in the ISO standard format used by the toString() method of the Instant class.

An Instant is modeled with two values:

  • A long value to represent the epoch-second
  • An int value to represent the nano-of-second

The nano-of-second must be a value in the range [0, 999999999]. This representation is reflected in the methods provided for dealing with instants. The Instant class shares many of the method name prefixes and the common method names in Table 17.2, p. 1026, and Table 17.3, p. 1026, with the other temporal classes, respectively. Although the Instant class has many methods analogous to the other temporal classes, as we shall see, there are also differences. Instant objects are, like objects of the other temporal classes, immutable and thread-safe.

Creating Instants

The Instant class provides the following predefined instants:

static Instant EPOCH
static Instant MAX
static Instant MIN

These static fields of the Instant class define constants for the epoch (1970-01-01T00:00:00Z), the maximum (1000000000-12-31T23:59:59.999999999Z), and the minimum instants (-1000000000-01-01T00:00Z), respectively.

Following are selected methods for creating and converting instances of the Instant class:

static Instant now()

Returns the current instant based on the system clock.

Click here to view code image

static Instant ofEpochMilli(long epochMilli)
static Instant ofEpochSecond(long epochSecond)
static Instant ofEpochSecond(long epochSecond, long nanoAdjustment)

These static factory methods return an Instant based on the millisecond, second, and nanosecond specified.

Nanoseconds are implicitly set to zero. The argument values can be negative. Note that the amount is specified as a long value.

String toString()

Returns a text representation of this Instant, such as “2021-01-11T14:18:30Z”. Formatting is based on the ISO instant format for date-time:

Click here to view code image

uuuu-MM-ddTHH:mm:ss.SSSSSSSSSZ

where Z designates the UTC standard (also known as Coordinated Universal Time).

Click here to view code image

static Instant parse(CharSequence text)

Returns an Instant parsed from a character sequence, such as “2021-04-28T14:18:30Z”, based on the ISO instant format. A DateTimeParse-Exception is thrown if the text cannot be parsed to an instant.

Click here to view code image

ZonedDateTime atZone(ZoneId zone)

Returns a ZonedDateTime by combining this instant with the specified time zone (p. 1072).

Analogous to the other temporal classes, the Instant class also provides the now() method to obtain the current instant from the system clock.

Click here to view code image

Instant currentInstant = Instant.now();      // 2021-03-09T10:48:01.914826Z

The Instant class provides the static factory method ofEpochUNIT() to construct instants from seconds and nanoseconds. There is no method to construct an instant from just nanoseconds.

Click here to view code image

Instant inst1 = Instant.ofEpochMilli(-24L*60*60*1000);// Date 1 day before epoch.
Instant inst2 = Instant.ofEpochSecond(24L*60*60);     // Date 1 day after epoch.
Instant inst3 = Instant.ofEpochSecond(24L*60*60 – 1,  // Date 1 day after epoch.
                                      1_000_000_000L);
out.println(“A day before: ” + inst1); // Date 1 day before: 1969-12-31T00:00:00Z
out.println(“A day after:  ” + inst2); // Date 1 day after : 1970-01-02T00:00:00Z
out.println(“A day after:  ” + inst3); // Date 1 day after : 1970-01-02T00:00:00Z

Note that the amount specified is a long value. The last statement above also illustrates that the nanosecond is adjusted so that it is always between 0 and 999,999,999. The adjustment results in the nanosecond being set to 0 and the second being incremented by 1.

The toString() method of the Instant class returns a text representation of an Instant based on the ISO standard. The code shows the text representation of the instant 500 nanoseconds after the epoch.

Click here to view code image

Instant inst4 = Instant.ofEpochSecond(0, 500);
out.println(“Default format:  ” + inst4);       // 1970-01-01T00:00:00.000000500Z

The Instant class also provides the parse() static method to create an instant from a string that contains a text representation of an instant, based on the ISO standard. Apart from treating the value of the nanosecond as optional, the method is strict in parsing the string. If the format of the string is not correct, a DateTimeParseException is thrown.

Click here to view code image

Instant instA = Instant.parse(“1970-01-01T00:00:00.000000500Z”);
Instant instB = Instant.parse(“1949-03-01T12:30:15Z”);
Instant instC = Instant.parse(“-1949-03-01T12:30:15Z”);
Instant instD = Instant.parse(“-1949-03-01T12:30:15”); // DateTimeParseException!

The code below illustrates creating an Instant by combining a LocalDateTime object with a time zone offset. Three different zone-time offsets are specified at (2), (3), and (4) to convert the date-time created at (1) to an Instant on the UTC timeline, which has offset zero. Note that an offset ahead of UTC is subtracted and an offset behind UTC is added to adjust the values of the date/time from the LocalDateTime object to the UTC timeline.

Click here to view code image

LocalDateTime ldt = LocalDate.of(2021, 12, 25).atStartOfDay();  //(1)
Instant i1 = ldt.toInstant(ZoneOffset.of(“+02:00”));     // (2) Ahead of UTC
Instant i2 = ldt.toInstant(ZoneOffset.UTC);              // (3) At UTC
Instant i3 = ldt.toInstant(ZoneOffset.of(“-02:00”));     // (4) Behind UTC
System.out.println(“ldt: ” + ldt);
System.out.println(“i1:  ” + i1);
System.out.println(“i2:  ” + i2);
System.out.println(“i3:  ” + i3);

Output from the code:

ldt: 2021-12-25T00:00
i1:  2021-12-24T22:00:00Z
i2:  2021-12-25T00:00:00Z
i3:  2021-12-25T02:00:00Z

Click here to view code image

// LocalDateTime
default Instant toInstant(ZoneOffset offset)

Converts a date-time to an instant by combining this LocalDateTime object with the specified time zone. The valid offset in Java is in the range from –18 to +18 hours. The absolute value of the offset is added to or subtracted from the date-time depending on whether it is specified as a negative or positive value, respectively, keeping in mind that an Instant represents a point in time on the UTC timeline.

This method is inherited by the LocalDateTime class from its superinterface java.time.chrono.ChronoLocalDateTime.