75. Start and end of a day

In JDK 8, trying to find the start/end of a day can be accomplished in several ways.

Let's consider a day expressed via LocalDate:

LocalDate localDate = LocalDate.of(2019, 2, 28);

The solution to finding the start of the day February 28, 2019, relies on a method named atStartOfDay(). This method returns LocalDateTime from this date at the time of midnight, 00:00:

// 2019-02-28T00:00
LocalDateTime ldDayStart = localDate.atStartOfDay();

Alternatively, the solution can use the of(LocalDate date, LocalTime time) method. This method combines the given date and time into LocalDateTime. So, if the passed time is LocalTime.MIN (the time of midnight at the start of the day) then the result will be as follows:

// 2019-02-28T00:00
LocalDateTime ldDayStart = LocalDateTime.of(localDate, LocalTime.MIN);

The end of the day of a LocalDate object can be obtained using at least two solutions. One solution consist of relying on LocalDate.atTime(LocalTime time). The resulting LocalDateTime can represent the combination of this date with the end of the day, if the solution passes as an argument, LocalTime.MAX (the time just before midnight at the end of the day):

// 2019-02-28T23:59:59.999999999
LocalDateTime ldDayEnd = localDate.atTime(LocalTime.MAX);

Alternatively, the solution can combine LocalTime.MAX with the given date, via the atDate(LocalDate date) method:

// 2019-02-28T23:59:59.999999999
LocalDateTime ldDayEnd = LocalTime.MAX.atDate(localDate);

Since LocalDate doesn't have the concept of a time zone, the preceding examples are prone to issues caused by different corner-cases, for example, Daylight Saving Time. Some Daylight Saving Times impose a change of hour at midnight (00:00 becomes 01:00 AM), which means that the start of the day is at 01:00:00, not at 00:00:00. In order to mitigate these issues, consider the following examples that extend the preceding examples to use ZonedDateTime, which is Daylight Saving Time aware:

// 2019-02-28T00:00+08:00[Australia/Perth]
ZonedDateTime ldDayStartZone
= localDate.atStartOfDay(ZoneId.of("Australia/Perth"));

// 2019-02-28T00:00+08:00[Australia/Perth]
ZonedDateTime ldDayStartZone = LocalDateTime
.of(localDate, LocalTime.MIN).atZone(ZoneId.of("Australia/Perth"));

// 2019-02-28T23:59:59.999999999+08:00[Australia/Perth]
ZonedDateTime ldDayEndZone = localDate.atTime(LocalTime.MAX)
.atZone(ZoneId.of("Australia/Perth"));

// 2019-02-28T23:59:59.999999999+08:00[Australia/Perth]
ZonedDateTime ldDayEndZone = LocalTime.MAX.atDate(localDate)
.atZone(ZoneId.of("Australia/Perth"));

Now, let's consider the following—LocalDateTime, February 28, 2019, 18:00:00:

LocalDateTime localDateTime = LocalDateTime.of(2019, 2, 28, 18, 0, 0);

The obvious solution is to extract LocalDate from LocalDateTime and apply the previous approaches. Another solution relies on the fact that every implementation of the Temporal interface (including LocalDate) can take advantage of the with(TemporalField field, long newValue) method. Mainly, the with() method returns a copy of this date with the specified field, ChronoField, set to newValue. So, if the solution sets ChronoField.NANO_OF_DAY (nanoseconds of a day) as LocalTime.MIN, then the result will be the start of the day. The trick here is to convert LocalTime.MIN to nanoseconds via toNanoOfDay(), as follows:

// 2019-02-28T00:00
LocalDateTime ldtDayStart = localDateTime
.with(ChronoField.NANO_OF_DAY, LocalTime.MIN.toNanoOfDay());

This is equivalent to the following:

LocalDateTime ldtDayStart 
= localDateTime.with(ChronoField.HOUR_OF_DAY, 0);

The end of the day is pretty similar. Just pass LocalTime.MAX instead of MIN:

// 2019-02-28T23:59:59.999999999
LocalDateTime ldtDayEnd = localDateTime
.with(ChronoField.NANO_OF_DAY, LocalTime.MAX.toNanoOfDay());

This is equivalent to the following:

LocalDateTime ldtDayEnd = localDateTime.with(
ChronoField.NANO_OF_DAY, 86399999999999L);

Like LocalDate, the LocalDateTime object is not aware of time zones. In this case, ZonedDateTime can help:

// 2019-02-28T00:00+08:00[Australia/Perth]
ZonedDateTime ldtDayStartZone = localDateTime
.with(ChronoField.NANO_OF_DAY, LocalTime.MIN.toNanoOfDay())
.atZone(ZoneId.of("Australia/Perth"));

// 2019-02-28T23:59:59.999999999+08:00[Australia/Perth]
ZonedDateTime ldtDayEndZone = localDateTime
.with(ChronoField.NANO_OF_DAY, LocalTime.MAX.toNanoOfDay())
.atZone(ZoneId.of("Australia/Perth"));

As a bonus here, let's see the start/end of the day with UTC. Beside the solution relying on the with() method, another solution can rely on toLocalDate(), as follows:

// e.g., 2019-02-28T09:23:10.603572Z
ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);

// 2019-02-28T00:00Z
ZonedDateTime dayStartZdt
= zdt.toLocalDate().atStartOfDay(zdt.getZone());

// 2019-02-28T23:59:59.999999999Z
ZonedDateTime dayEndZdt = zdt.toLocalDate()
.atTime(LocalTime.MAX).atZone(zdt.getZone());
Because of the numerous issues with java.util.Date and Calendar, it is advisable to avoid trying to implement a solution to this problem with them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset