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).

Working with Durations – Date and Time

17.6 Working with Durations

The java.time.Duration class implements a time-based amount of time in terms of seconds and nanoseconds, using a long and an int value for these time units, respectively. Although the Duration class models an amount of time in terms of seconds and nanoseconds, a duration can represent an amount of time in terms of days, hours, and minutes. As these time units have fixed lengths, it makes interoperability between these units possible. The time-based Duration class can be used with the LocalTime and LocalDateTime classes, as these classes have time fields. In contrast, the Period class essentially represents a date-based amount of time in terms of years, months, and days (p. 1057).

The Period and Duration classes are in the same package (java.time) as the temporal classes. The Period and the Duration classes provide similar methods, as they share many of the method prefixes and common methods with the temporal classes (Table 17.2, p. 1026, and Table 17.3, p. 1026). Their objects are immutable and thread-safe. However, there are also differences between the two classes (p. 1072). Familiarity with one would go a long way toward understanding the other.

Creating Durations

Like the Period class, the Duration class provides the static factory methods ofUNIT() to construct durations with different units.

Click here to view code image

Duration d1 = Duration.ofDays(1L);                               // PT24H
Duration d2 = Duration.ofHours(24L);                             // PT24H
Duration d3 = Duration.ofMinutes(24L*60);                        // PT24H
Duration d4 = Duration.ofSeconds(24L*60*60);                     // PT24H
Duration d5 = Duration.ofMillis(24L*60*60*1000);                 // PT24H
Duration d6 = Duration.ofSeconds(24L*60*60 – 1, 1_000_000_000L); // (1) PT24H
Duration d7 = Duration.ofNanos(24L*60*60*1_000_000_000); // (2) PT24H
Duration d8 = Duration.ofNanos(24*60*60*1_000_000_000);  // (3) PT-1.857093632S

The durations created above all have a length of 1 day, except for the one in the last declaration statement. Note that the amount specified should be a long value. It is a good defensive practice to always designate the amount as such in order to avoid inadvertent problems if the amount does not fit into an int. The designation L should be placed such that there is no danger of any previous operation in the expression causing a rollover. This problem is illustrated at (3), where the int value of the argument expression is rolled over, as it is greater than Integer.MAX_VALUE.

The statement at (1) above also illustrates that the value of the nanoseconds is adjusted so that it is always between 0 and 999,999,999. The adjustment at (1) results in the value 0 for nanoseconds and the number of seconds being incremented by 1.

Calling the toString() method on the first seven declarations above, the result is the string “PT24H” (a duration of 24 hours), whereas for the last duration at (3), the result string is “PT-1.857093632S”, which clearly indicates that the int amount was not interpreted as intended.

The previous declarations are equivalent to the ones below, where the amount is qualified with a specific unit in the call to the of(value, unit) method.

Click here to view code image

Duration d11 = Duration.of(1L, ChronoUnit.DAYS);              // P24H
Duration d22 = Duration.of(24L, ChronoUnit.HOURS);            // P24H
Duration d33 = Duration.of(24L*60, ChronoUnit.MINUTES);       // P24H
Duration d44 = Duration.of(24L*60*60, ChronoUnit.SECONDS);    // P24H
Duration d88 = Duration.of(24L*60*60*1000, ChronoUnit.MILLIS);// P24H
Duration d77 = Duration.of(24L*60*60*1_000_000_000,
                           ChronoUnit.NANOS);                 // P24H

The code snippet below does not create a duration of 8 days—it creates a duration of 24 hours. The first method call is invoked with the class name, and the subsequent method call is on the new Duration object returned as a consequence of the first call. The of() method creates a new Duration object based on its argument.

Click here to view code image

Duration duration = Duration.ofDays(7).ofHours(24);   // PT24H. Logical error.

Like the Period class, we can create a duration that represents the amount of time between two temporal objects by calling the static method between() of the Duration class.

Click here to view code image

LocalTime startTime = LocalTime.of(14, 30);                  // 14:30
LocalTime endTime   = LocalTime.of(17, 45, 15);              // 17:45:15
Duration interval1 = Duration.between(startTime, endTime);   // PT3H15M15S
Duration interval2 = Duration.between(endTime, startTime);   // PT-3H-15M-15S

Note the exception thrown in the last statement below because a LocalDateTime object cannot be derived from a LocalTime object, whereas the converse is true.

Click here to view code image

LocalDateTime dateTime = LocalDateTime.of(2021, 4, 28,
                                          17, 45, 15);    // 2021-04-28T17:45:15
Duration interval3 = Duration.between(startTime, dateTime);  // PT3H15M15S
Duration interval4 = Duration.between(dateTime, startTime);  // DateTimeException!

The Duration class also provides the parse() static method to create a duration from a text representation of a duration based on the ISO standard. If the format of the string is not correct, a DateTimeParseException is thrown. Formatting according to the toString() method is shown in parentheses.

Click here to view code image

Duration da = Duration.parse(“PT3H15M10.1S”);// 3hrs. 15mins. 10.1s.(PT3H15M10.1S)
Duration db = Duration.parse(“PT0.999S”);    // 999000000 nanos.    (PT0.999S)
Duration dc = Duration.parse(“-PT30S”);      // -30 seconds.        (PT-30S)
Duration dd = Duration.parse(“P-24D”);       // -24 days            (PT-576H)
Duration dd = Duration.parse(“P24H”);        // Missing T. DateTimeParseException!

static Duration ZERO

This constant defines a Duration of length zero (PT0S).

Click here to view code image

static Duration ofDays(long days)
static Duration ofHours(long hours)
static Duration ofMinutes(long minutes)
static Duration ofMillis(long millis)
static Duration ofSeconds(long seconds)
static Duration ofSeconds(long seconds, long nanoAdjustment)
static Duration ofNanos(long nanos)

These static factory methods return a Duration representing an amount of time in seconds and nanoseconds that is equivalent to the specified amount, depending on the method. Nanoseconds are implicitly set to zero. The argument value can be negative. Standard definitions of the units are used. Note that the amount is specified as a long value.

Click here to view code image

static Duration of(long amount, TemporalUnit unit)

This static factory method returns a Duration representing an amount of time in seconds and nanoseconds that is equivalent to the specified amount in the specified temporal unit. The amount is specified as a long value, which can be negative.

Valid ChronoUnit constants to qualify the amount specified in the method call are the following: NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS, HALF_DAYS, and DAYS (p. 1044). These units have a standard or an estimated duration.

Click here to view code image

static Duration between(Temporal startInclusive, Temporal endExclusive)

This static method returns the duration between two temporal objects that must support the seconds unit and where it is possible to convert the second temporal argument to the first temporal argument type, if necessary. Otherwise, a DateTimeException is thrown. The result of this method can be a negative period if the end temporal is before the start temporal.

String toString()

Returns a text representation of a Duration according to the ISO standard: PThHmMd.dS—that is, h Hours, m Minutes, and d.d Seconds, where the nanoseconds are formatted as a fraction of a second.

Click here to view code image

static Duration parse(CharSequence text)

This static method returns a Duration parsed from a character sequence. The formats accepted are based on the ISO duration format PTnHnMn.nS—for example, “PT2H3M4.5S” (2 hours, 3 minutes, and 4.5 seconds). A java.time.format.Date-TimeParseException is thrown if the text cannot be parsed to a duration.

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

Accessing Time Units in a Duration – Date and Time

Accessing Time Units in a Duration

The Duration class provides the getUNIT() methods to read the individual values of its time units. The class also has methods to check if the period has a negative value or if its value is zero.

Click here to view code image

Duration dx = Duration.ofSeconds(12L*60*60, 500_000_000L); // PT12H0.5S
out.println(dx.getNano());                                 // 500000000
out.println(dx.getSeconds());                              // 43200 (i.e. 12 hrs.)

Reading the individual values of time units of a Duration object can also be done using the get(unit) method, where only the NANOS and SECONDS units are allowed. A list of temporal units that are accepted by the get(unit) method can be obtained by calling the getUnits() of the Duration class.

Click here to view code image

out.println(dx.get(ChronoUnit.NANOS));       // 500000000
out.println(dx.get(ChronoUnit.SECONDS));     // 43200
out.println(dx.get(ChronoUnit.MINUTES));     // UnsupportedTemporalTypeException
out.println(dx.getUnits());                  // [Seconds, Nanos]

The class Duration provides the method toUNIT() to derive the total length of the duration in the unit designated by the method name. The seconds and the nanoseconds are converted to this unit, if necessary.

Click here to view code image

out.println(“Days:    ” + dx.toDays());      // Days:    0
out.println(“Hours:   ” + dx.toHours());     // Hours:   12
out.println(“Minutes: ” + dx.toMinutes());   // Minutes: 720
out.println(“Millis:  ” + dx.toMillis());    // Millis:  43200500
out.println(“Nanos:   ” + dx.toNanos());     // Nanos:   43200500000000

int getNano()
long getSeconds()

Return the number of nanoseconds and seconds in this duration, respectively—not the total length of the duration. Note that the first method name is getNano, without the s.

long get(TemporalUnit unit)

Returns the value of the specified unit in this Duration—not the total length of the duration. The only supported ChronoUnit constants are NANOS and SECONDS (p. 1044). Other units result in an UnsupportedTemporalTypeException.

List<TemporalUnit> getUnits()

Returns the list of time units supported by this duration: NANOS and SECONDS (p. 1044). These time units can be used with the get(unit) method.

long toDays()
long toHours()
long toMinutes()
long toMillis()
long toNanos()

Return the total length of this duration, converted to the unit designated by the method, if necessary. Note that there is no toSeconds() method. Also, the method name is toNanos—note the s at the end.

The methods toMillis() and toNanos() throw an ArithmeticException in case of number overflow.

boolean isNegative()

Determines whether the total length of this duration is negative.

boolean isZero()

Determines whether the total length of this duration is zero.

Counting – Streams

Counting

The collector created by the Collectors.counting() method performs a functional reduction to count the input elements.

Click here to view code image

static <T> Collector<T,?,Long> counting()

The collector returned counts the number of input elements of type T. If there are no elements, the result is Long.valueOf(0L). Note that the result is of type Long.

The wildcard ? represents any type, and in the method declaration, it is the type parameter for the mutable type that is accumulated by the reduction operation.

In the stream pipeline at (1), the collector Collectors.counting() is used in a standalone capacity to count the number of jazz music CDs.

Click here to view code image

Long numOfJazzCds1 = CD.cdList.stream().filter(CD::isJazz)
    .collect(Collectors.counting());                  // (1) Standalone collector
System.out.println(numOfJazzCds1);                    // 3

In the stream pipeline at (2), the collector Collectors.counting() is used as a downstream collector in a grouping operation that groups the CDs by musical genre and uses the downstream collector to count the number of CDs in each group.

Click here to view code image

Map<Genre, Long> grpByGenre = CD.cdList.stream()
    .collect(Collectors.groupingBy(
                 CD::genre,
                 Collectors.counting()));             // (2) Downstream collector
System.out.println(grpByGenre);                       // {POP=2, JAZZ=3}
System.out.println(grpByGenre.get(Genre.JAZZ));       // 3

The collector Collectors.counting() performs effectively the same functional reduction as the Stream.count() terminal operation (p. 953) at (3).

Click here to view code image

long numOfJazzCds2 = CD.cdList.stream().filter(CD::isJazz)
    .count();                                         // (3) Stream.count()
System.out.println(numOfJazzCds2);                    // 3

Finding Min/Max

The collectors created by the Collectors.maxBy() and Collectors.minBy() methods perform a functional reduction to find the maximum and minimum elements in the input elements, respectively. As there might not be any input elements, an Optional<T> is returned as the result.

Click here to view code image

static <T> Collector<T,?,Optional<T>> maxBy(Comparator<? super T> cmp)
static <T> Collector<T,?,Optional<T>> minBy(Comparator<? super T> cmp)

Return a collector that produces an Optional<T> with the maximum or minimum element of type T according to the specified Comparator, respectively.

The natural order comparator for CDs defined at (1) is used in the stream pipelines below to find the maximum CD. The collector Collectors.maxBy() is used as a standalone collector at (2), using the natural order comparator to find the maximum CD. The Optional<CD> result can be queried for the value.

Click here to view code image

Comparator<CD> natCmp = Comparator.naturalOrder(); // (1)
Optional<CD> maxCD = CD.cdList.stream()
    .collect(Collectors.maxBy(natCmp));            // (2) Standalone collector
System.out.println(“Max CD: “
    + maxCD.map(CD::title).orElse(“No CD.”));      // Max CD: Java Jive

In the pipeline below, the CDs are grouped by musical genre, and the CDs in each group are reduced to the maximum CD by the downstream collector Collectors.maxBy() at (3). Again, the downstream collector uses the natural order comparator, and the Optional<CD> result in each group can be queried.

Click here to view code image

// Group CDs by musical genre, and max CD in each group.
Map<Genre, Optional<CD>> grpByGenre = CD.cdList.stream()
    .collect(Collectors.groupingBy(
        CD::genre,
        Collectors.maxBy(natCmp)));       // (3) Downstream collector
System.out.println(grpByGenre);
//{JAZZ=Optional[<Jaav, “Java Jam”, 6, 2017, JAZZ>],
// POP=Optional[<Jaav, “Java Jive”, 8, 2017, POP>]}

System.out.println(“Title of max Jazz CD: “
    + grpByGenre.get(Genre.JAZZ)
                .map(CD::title)
                .orElse(“No CD.”));       // Title of max Jazz CD: Java Jam

The collectors created by the Collectors.maxBy() and Collectors.minBy() methods are effectively equivalent to the max() and min() terminal operations provided by the stream interfaces (p. 954), respectively. In the pipeline below, the max() terminal operation reduces the stream of CDs to the maximum CD at (4) using the natural order comparator, and the Optional<CD> result can be queried.

Click here to view code image

Optional<CD> maxCD1 = CD.cdList.stream()
    .max(natCmp);                         // (4) max() on Stream<CD>.
System.out.println(“Title of max CD: “
    + maxCD1.map(CD::title)
            .orElse(“No CD.”));           // Title of max CD: Java Jive

Comparing Durations – Date and Time

Comparing Durations

The Duration class overrides the equals() method and the hashCode() method of the Object class, and implements the Comparable<Duration> interface. Durations can readily be used in collections. The code below illustrates comparing durations.

Click here to view code image

Duration eatBreakFast = Duration.ofMinutes(20L);                // PT20M
Duration eatLunch     = Duration.ofSeconds(30L*60);             // PT30M
Duration eatSupper    = Duration.of(45L, ChronoUnit.MINUTES);   // PT45M
out.println(eatBreakFast.equals(eatLunch));                     // false
out.println(Duration.ofSeconds(0).equals(Duration.ZERO));       // true
List<Duration> ld = Arrays.asList(eatSupper, eatBreakFast, eatLunch );
Collections.sort(ld);                            // Natural order.
out.println(ld);                                 // [PT20M, PT30M, PT45M]

Click here to view code image

boolean equals(Object otherDuration)

Determines whether the total length of this duration is equal to the total length of the other duration.

int hashCode()

Returns a hash code for this duration.

Click here to view code image

int compareTo(Duration otherDuration)

Compares the total length of this duration to the total length of the other duration.

Creating Modified Copies of Durations

The Duration class provides withUNIT() methods to set a new value for each time unit individually, while the value of the other time unit is retained. Note that each method call returns a new Duration object, and chaining method calls works as expected.

Click here to view code image

Duration oneDuration =  Duration.ZERO                     // PT0S
                                .withNanos(500_000_000)   // New copy: PT0.5S
                                .withSeconds(12L*60*60);  // New copy: PT12H0.5S

Click here to view code image

Duration withNanos(int nanoOfSecond)
Duration withSeconds(long seconds)

Return a copy of this duration where either the nanosecond or the seconds are set to the value of the argument, respectively. The value of the other time unit is retained.

Temporal Arithmetic with Durations

The Duration 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 unit—for example, as a number of days, hours, minutes, or seconds.

Click here to view code image

Duration max20H = Duration.ZERO                           // PT0S
                          .plusHours(10)                  // PT10H
                          .plusMinutes(10*60 + 30)        // PT20H30M
                          .plusSeconds(6*60*60 + 15)      // PT26H30M15S
                          .minusMinutes(2*60 + 30)        // PT24H15S
                          .minusSeconds(15);              // PT24H

The plus() and the minus() methods also allow the amount to be qualified by a unit that has a standard or an estimated duration, as illustrated by the statement below, which is equivalent to the one above.

Click here to view code image

Duration max20H2 =
    Duration.ZERO                                         // PT0S
            .plus(10L,           ChronoUnit.HOURS)        // PT10H
            .plus(10*60 + 30,    ChronoUnit.MINUTES)      // PT20H30M
            .plus(6*60*60L + 15, ChronoUnit.SECONDS)      // PT26H3015S
            .minus(2*60 + 30,    ChronoUnit.MINUTES)      // PT24H15S
            .minus(15,           ChronoUnit.SECONDS);     // PT24H

The code below shows the plus() and the minus() methods of the Duration class that take a Duration as the amount to add or subtract.

Click here to view code image

Duration eatBreakFast = Duration.ofMinutes(20L);                // PT20M
Duration eatLunch     = Duration.ofSeconds(30L*60);             // PT30M
Duration eatSupper    = Duration.of(45L, ChronoUnit.MINUTES);   // PT45M

Duration totalTimeForMeals = eatBreakFast                       // PT20M
    .plus(eatLunch)                                             // PT50M
    .plus(eatSupper);                                           // PT1H35M

The statement below shows other arithmetic operations on durations and how they are carried out, together with what would be printed if the intermediate results were also written out.

Click here to view code image

Duration result = Duration.ofSeconds(-100, -500_000_000) // -100.5 => PT-1M-40.5S
                          .abs()           // abs(-100.5) = 100.5  => PT1M40.5S
                          .multipliedBy(4) // 100.5*4 = 402 => PT6M42S
                          .dividedBy(2);   // 402 / 2 = 201 => PT3M21S

Click here to view code image

Duration plusDays/minusDays(long days)
Duration plusHours/minusHours(long hours)
Duration plusMinutes/minusMinutes(long minutes)
Duration plusSeconds/minusSeconds(long seconds)
Duration plusMillis/minusMillis(long millis)
Duration plusNanos/minusNanos(long nanos)

Return a copy of this duration, with the specified value of the unit designated by the method name added or subtracted, but converted first to seconds, if necessary. Note that the argument type is long.

Click here to view code image

Duration plus(long amountToAdd, TemporalUnit unit)
Duration minus(long amountToSub, TemporalUnit unit)

Return a copy of this duration with the specified amount added or subtracted, respectively, according to the TemporalUnit specified (p. 1044).

Valid ChronoUnit constants to qualify the amount specified in the method call are the following: NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS, HALF_DAYS, and DAYS (p. 1044). These units have a standard or an estimated duration. Other units result in an UnsupportedTemporalTypeException.

Click here to view code image

Duration plus(Duration duration)
Duration minus(Duration duration)

Return a copy of this duration, with the specified duration added or subtracted.

Duration abs()

Returns a copy of this duration with a positive length.

Duration negated()

Returns a copy of this duration where the length has been negated.

Click here to view code image

Duration dividedBy(long divisor)
Duration multipliedBy(long multiplicand)

The first method returns a new instance with the result of dividing the length of this duration by the specified divisor. Division by zero would bring down untold calamities.

The second method returns a new instance with the result of multiplying the length of this duration by the specified multiplicand.

We can perform arithmetic operations on durations and temporal objects. The following code uses the plus() and minus() methods of the LocalTime and LocalDateTime classes that take a TemporalAmount as an argument (p. 1040). We can add and subtract durations from LocalTime and LocalDateTime objects, but not from LocalDate objects, as a LocalDate object only supports date units.

Click here to view code image

LocalTime timeA = LocalTime.of(14,45,30);                 // 14:45:30
LocalDate dateA = LocalDate.of(2021, 4, 28);              // 2021-04-28
LocalDateTime dateTimeA = LocalDateTime.of(dateA, timeA); // 2021-04-28T14:45:30
Duration amount = Duration.ofMinutes(20);                 // PT20M
timeA = timeA.plus(amount);                               // 15:05:30
dateTimeA = dateTimeA.minus(amount);                      // 2021-04-28T14:25:30
dateA = dateA.minus(amount);                 // UnsupportedTemporalTypeException

Example 17.7 illustrates implementing duration-based loops. The program prints the showtimes, given when the first show starts, the duration of the show, and when the theatre closes. The for(;;) loop at (1) uses the LocalTime.isBefore() method and the LocalTime.plus(duration) method to calculate the showtimes.

Example 17.7 Duration-Based Loop

Click here to view code image

import java.time.LocalTime;
import java.time.Duration;
public class DurationBasedLoop {
  public static void main(String[] args) {
    Duration duration = Duration.ofHours(2).plusMinutes(15);     // PT2H15M
    LocalTime firstShowTime = LocalTime.of(10, 10);              // 10:10
    LocalTime endTimeExclusive = LocalTime.of(23, 0);            // 23:00
    for (LocalTime time = firstShowTime;                         // (1)
         time.plus(duration).isBefore(endTimeExclusive);
         time = time.plus(duration)) {
      System.out.println(“Showtime (” + duration + “): ” + time);
    }
    System.out.println(“Closing time: ” + endTimeExclusive);
  }
}

Output from the program:

Showtime (PT2H15M): 10:10
Showtime (PT2H15M): 12:25
Showtime (PT2H15M): 14:40
Showtime (PT2H15M): 16:55
Showtime (PT2H15M): 19:10
Closing time: 23:00

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.

The LocalDateTime Class – Date and Time

The LocalDateTime Class

The class LocalDateTime allows the date and the time to be combined into one entity, which is useful for representing such concepts as appointments that require both a time and a date. The of() methods in the LocalDateTime class are combinations of the of() methods from the LocalTime and LocalDate classes, taking values of both time and date fields as arguments. The toString() method of this class will format the temporal fields according to the ISO standard (§18.6, p. 1134):

uuuu-MM-ddTHH:mm:ss.SSSSSSSSS

The letter T separates the values of the date fields from those of the time fields.

Click here to view code image

// 2021-04-28T12:15
LocalDateTime dt1 = LocalDateTime.of(2021, 4, 28, 12, 15);
// 2021-08-19T14:00
LocalDateTime dt2 = LocalDateTime.of(2021, Month.AUGUST, 19, 14, 0);

The LocalDateTime class also provides an of() method that combines a LocalDate object and a LocalTime object. The first declaration in the next code snippet combines a date and a time. The static field LocalTime.NOON defines the time at noon. In addition, the LocalTime class provides the instance method atDate(), which takes a date as an argument and returns a LocalDateTime object. The second declaration combines the time at noon with the date referred to by the reference date1. Conversely, the LocalDate class provides the overloaded instance method atTime() to combine a date with a specified time. In the last two declarations, the atTime() method is passed a LocalTime object and values for specific time fields, respectively.

Click here to view code image

// LocalDate date1 is 1969-07-20.
LocalDateTime dt3 = LocalDateTime.of(date1, LocalTime.NOON); // 1969-07-20T12:00
LocalDateTime dt4 = LocalTime.of(12, 0).atDate(date1);       // 1969-07-20T12:00
LocalDateTime dt5 = date1.atTime(LocalTime.NOON);            // 1969-07-20T12:00
LocalDateTime dt6 = date1.atTime(12, 0);                     // 1969-07-20T12:00

As a convenience, each temporal class provides a static method now() that reads the system clock and returns the values for the relevant temporal fields in an instance of the target class.

Click here to view code image

LocalTime currentTime = LocalTime.now();
LocalDate currentDate = LocalDate.now();
LocalDateTime currentDateTime = LocalDateTime.now();

Example 17.1 includes the different ways to create temporal objects that we have discussed so far.

Click here to view code image

// LocalTime
LocalDateTime atDate(LocalDate date)

Returns a LocalDateTime that combines this time with the specified date.

Click here to view code image

// LocalDate
LocalDateTime atTime(LocalTime time)
LocalDateTime atTime(int hour, int minute)
LocalDateTime atTime(int hour, int minute, int second)
LocalDateTime atTime(int hour, int minute, int second, int nanoOfSecond)
LocalDateTime atStartOfDay()

Return a LocalDateTime that combines this date with the specified values for time fields. The second and nanosecond fields are set to zero, if their values are not specified. In the last method, this date is combined with the time at midnight.

Click here to view code image

// LocalDateTime
ZonedDateTime atZone(ZoneId zone)

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

Click here to view code image

// LocalTime, LocalDate, LocalDateTime, respectively.
static LocalTime now()
static LocalDate now()
static LocalDateTime now()

Each temporal class has this static factory method, which returns either the current time, date, or date-time from the system clock.

Example 17.1 Creating Local Dates and Local Times

Click here to view code image

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
public class CreatingTemporals {
  public static void main(String[] args) {
    // Creating a specific time from time-based values:
    LocalTime time1 = LocalTime.of(8, 15, 35, 900);// 08:15:35.000000900
    LocalTime time2 = LocalTime.of(16, 45);        // 16:45
//  LocalTime time3 = LocalTime.of(25, 13, 30);    // DateTimeException
    System.out.println(“Surveillance start time: ” + time1);
    System.out.println(“Closing time: ” + time2);
    // Creating a specific date from date-based values:
    LocalDate date1 = LocalDate.of(1969, 7, 20);            // 1969-07-20
    LocalDate date2 = LocalDate.of(-3113, Month.AUGUST, 11);// -3113-08-11
//  LocalDate date3 = LocalDate.of(2021, 13, 11);           // DateTimeException
//  LocalDate date4 = LocalDate.of(2021, 2, 29);            // DateTimeException
    System.out.println(“Date of lunar landing:        ” + date1);
    System.out.println(“Start Date of Mayan Calendar: ” + date2);
    // Creating a specific date-time from date- and time-based values.
    // 2021-04-28T12:15
    LocalDateTime dt1 = LocalDateTime.of(2021, 4, 28, 12, 15);
    // 2021-08-17T14:00
    LocalDateTime dt2 = LocalDateTime.of(2021, Month.AUGUST, 17, 14, 0);
    System.out.println(“Car service appointment: ” + dt1);
    System.out.println(“Hospital appointment:    ” + dt2);
    // Combining date and time objects.
    // 1969-07-20T12:00
    LocalDateTime dt3 = LocalDateTime.of(date1, LocalTime.NOON);
    LocalDateTime dt4 = LocalTime.of(12, 0).atDate(date1);
    LocalDateTime dt5 = date1.atTime(LocalTime.NOON);
    LocalDateTime dt6 = date1.atTime(12, 0);
    System.out.println(“Factory date-time combo: ” + dt3);
    System.out.println(“Time with date combo:    ” + dt4);
    System.out.println(“Date with time combo:    ” + dt5);
    System.out.println(“Date with explicit time combo: ” + dt6);
    // Current time:
    LocalTime currentTime = LocalTime.now();
    System.out.println(“Current time:      ” + currentTime);
    // Current date:
    LocalDate currentDate = LocalDate.now();
    System.out.println(“Current date:      ” + currentDate);
    // Current date and time:
    LocalDateTime currentDateTime = LocalDateTime.now();
    System.out.println(“Current date-time: ” + currentDateTime);
  }
}

Possible output from the program:

Click here to view code image Surveillance start time: 08:15:35.000000900
Closing time: 16:45
Date of lunar landing:        1969-07-20
Start Date of Mayan Calendar: -3113-08-11
Car service appointment: 2021-04-28T12:15
Hospital appointment:    2021-08-17T14:00
Factory date-time combo: 1969-07-20T12:00
Time with date combo:    1969-07-20T12:00
Date with time combo:    1969-07-20T12:00
Date with explicit time combo: 1969-07-20T12:00
Current time:      10:55:41.296744
Current date:      2021-03-05
Current date-time: 2021-03-05T10:55:41.299318

The LocalTime Class – Date and Time

The LocalTime Class

The declaration statements below show examples of creating instances of the LocalTime class to represent time on a 24-hour clock in terms of hours, minutes, seconds, and nanoseconds.

Click here to view code image

LocalTime time1 = LocalTime.of(8, 15, 35, 900);   // 08:15:35.000000900
LocalTime time2 = LocalTime.of(16, 45);           // 16:45
// LocalTime time3 = LocalTime.of(25, 13, 30);    // DateTimeException

The ranges of values for time fields hour (0–23), minute (0–59), second (0–59), and nanosecond (0–999,999,999) are defined by the ISO standard. The toString() method of the class will format the time fields according to the ISO standard as follows:

HH:mm:ss.SSSSSSSSS

Omitting the seconds (ss) and fractions of seconds (SSSSSSSSS) in the call to the of() method implies that their value is zero. (More on formatting in §18.6, p. 1134.) In the second declaration statement above, the seconds and the nanoseconds are not specified in the method call, resulting in their values being set to zero. In the third statement, the value of the hour field (25) is out of range, and if the statement is uncommented, it will result in a DateTimeException.

The LocalDate Class

Creating instances of the LocalDate class is analogous to creating instances of the LocalTime class. The of() method of the LocalDate class is passed values for date fields: the year, month of the year, and day of the month.

Click here to view code image

LocalDate date1 = LocalDate.of(1969, 7, 20);            // 1969-07-20
LocalDate date2 = LocalDate.of(-3113, Month.AUGUST, 11);// -3113-08-11
//  LocalDate date3 = LocalDate.of(2021, 13, 11);       // DateTimeException
//  LocalDate date4 = LocalDate.of(2021, 2, 29);        // DateTimeException

The ranges of the values for date fields year, month, and day are (–999,999,999 to +999,999,999), (1–12), and (1–31), respectively. The month can also be specified using the enum constants of the java.time.Month class, as in the second declaration statement above. A DateTimeException is thrown if the value of any parameter is out of range, or if the day is invalid for the specified month of the year. In the third declaration, the value of the month field 13 is out of range. In the last declaration, the month of February cannot have 29 days, since the year 2021 is not a leap year.

The toString() method of the LocalDate class will format the date fields according to the ISO standard (§18.6, p. 1134):

uuuu-MM-dd

The year is represented as a proleptic year in the ISO standard, which can be negative. A year in CE (Current Era, or AD) has the same value as a proleptic year; for example, 2021 CE is the same as the proleptic year 2021. However, for a year in BCE (Before Current Era, or BC), the proleptic year 0 corresponds to 1 BCE, the proleptic year –1 corresponds to 2 BCE, and so on. In the second declaration in the preceding set of examples, the date -3113-08-11 corresponds to 11 August 3114 BCE.

Working with Dates and Times – Date and Time

17.2 Working with Dates and Times

The classes LocalTime, LocalDate, and LocalDateTime in the java.time package represent time-based, date-based, and combined date-based and time-based temporal objects, respectively, that are all time zone agnostic. These classes represent human time that is calendar-based, meaning it is defined in terms of concepts like year, month, day, hour, minute, and second, that humans use. The Instant class can be used to represent machine time, which is defined as a point measured with nanosecond precision on a continuous timeline starting from a specific origin (p. 1049).

Time zones and daylight savings are discussed in §17.7, p. 1072.

Creating Dates and Times

The temporal classes in the java.time package do not provide any public constructors to create temporal objects. Instead, they provide overloaded static factory methods named of which create temporal objects from constituent temporal fields. We use the term temporal fields to mean both time fields (hours, minutes, seconds, nanoseconds) and date fields (year, month, day). The of() methods check that the values of the arguments are in range. Any invalid argument results in a java.time.DateTimeException.

Click here to view code image

// LocalTime
static LocalTime of(int hour, int minute)
static LocalTime of(int hour, int minute, int second)
static LocalTime of(int hour, int minute, int second, int nanoOfSecond)
static LocalTime ofSecondOfDay(long secondOfDay)

These static factory methods in the LocalTime class return an instance of Local-Time based on the specified values for the specified time fields. The second and nanosecond fields are set to zero, if not specified.

The last method accepts a value for the secondOfDay parameter in the range [0, 24 * 60 * 60 – 1] to create a LocalTime.

Click here to view code image

// LocalDate
static LocalDate of(int year, int month, int dayOfMonth)
static LocalDate of(int year, Month month, int dayOfMonth)
static LocalDate ofYearDay(int year, int dayOfYear)

These static factory methods in the LocalDate class return an instance of LocalDate based on the specified values for the date fields. The java.time.Month enum type allows months to be referred to by name—for example, Month.MARCH. Note that month numbering starts with 1 (Month.JANUARY).

The last method creates a date from the specified year and the day of the year.

Click here to view code image

// LocalDateTime
static LocalDateTime of(int year, int month, int dayOfMonth,
                        int hour, int minute)
static LocalDateTime of(int year, int month, int dayOfMonth,
                        int hour, int minute, int second)
static LocalDateTime of(int year, int month, int dayOfMonth, int hour,
                        int minute, int second, int nanoOfSecond)
static LocalDateTime of(int year, Month month, int dayOfMonth,
                        int hour, int minute, int second)
static LocalDateTime of(int year, Month month, int dayOfMonth,
                        int hour, int minute)
static LocalDateTime of(int year, Month month, int dayOfMonth,
                        int hour, int minute, int second, int nanoOfSecond)

These static factory methods in the LocalDateTime class return an instance of LocalDateTime based on the specified values for the time and date fields. The second and nanosecond fields are set to zero, if not specified. The java.time.Month enum type allows months to be referred to by name—for example, Month.MARCH (i.e., month 3 in the year).

Click here to view code image

static LocalDateTime of(LocalDate date, LocalTime time)

Combines a LocalDate and a LocalTime into a LocalDateTime.

All code snippets in this subsection can be found in Example 17.1, p. 1031, ready for running and experimenting. An appropriate import statement with the java.time package should be included in the source file to access any of the temporal classes by their simple name.