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.

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