The New Date and Time API - Java SE 8 for the Really Impatient (2014)

Java SE 8 for the Really Impatient (2014)

Chapter 5. The New Date and Time API

Topics in This Chapter

Image 5.1 The Time Line

Image 5.2 Local Dates

Image 5.3 Date Adjusters

Image 5.4 Local Time

Image 5.5 Zoned Time

Image 5.6 Formatting and Parsing

Image 5.7 Interoperating with Legacy Code

Image Exercises

Time flies like an arrow, and we can easily set a starting point and count forward and backwards in seconds. So why is it so hard to deal with time? The problem is humans. All would be easy if we could just tell each other: “Meet me at 1371409200, and don’t be late!” But we want time to relate to daylight and the seasons. That’s where things get complicated. Java 1.0 had a Date class that was, in hindsight, unbelievably naïve, and had most of its methods deprecated in Java 1.1 when a Calendar class was introduced. Its API wasn’t stellar, its instances were mutable, and it didn’t deal with issues such as leap seconds. The third time is a charm, and the java.time API that is introduced in Java 8 has remedied the flaws of the past and should serve us for quite some time. In this chapter, you will learn what makes time computations so vexing, and how the new Date and Time API solves these issues.

The key points of this chapter are:

• All java.time objects are immutable.

• An Instant is a point on the time line (similar to a Date).

• In Java time, each day has exactly 86,400 seconds (i.e., no leap seconds).

• A Duration is the difference between two instants.

• LocalDateTime has no time zone information.

• TemporalAdjuster methods handle common calendar computations, such as finding the first Tuesday of a month.

• ZonedDateTime is a point in time in a given time zone (similar to GregorianCalendar).

• Use a Period, not a Duration, when advancing zoned time, in order to account for daylight savings time changes.

• Use DateTimeFormatter to format and parse dates and times.

5.1. The Time Line

Historically, the fundamental time unit, the second, was derived from Earth’s rotation around its axis. There are 24 hours or 24 × 60 × 60 = 86400 seconds in a full revolution, so it seems just a question of astronomical measurements to precisely define a second. Unfortunately, Earth wobbles slightly, and a more precise definition was needed. In 1967, a new precise definition of a second, matching the historical definition, was derived from an intrinsic property of atoms of caesium-133. Since then, a network of atomic clocks keeps the official time.

Ever so often, the official time keepers synchronize the absolute time with the rotation of Earth. At first, the official seconds were slightly adjusted, but starting in 1972, “leap seconds” were occasionally inserted. (In theory, a second might need to be removed once in a while, but that has not yet happened.) There is talk of changing the system again. Clearly, leap seconds are a pain, and many computer systems instead use “smoothing” where time is artificially slowed down or sped up just before the leap second, keeping 86,400 seconds per day. This works because the local time on a computer isn’t all that precise, and computers are used to synchronizing themselves with an external time service.

The Java Date and Time API specification requires that Java uses a time scale that

• Has 86,400 seconds per day

• Exactly matches the official time at noon each day

• Closely matches it elsewhere, in a precisely defined way

That gives Java the flexibility to adjust to future changes in the official time.

In Java, an Instant represents a point on the time line. An origin, called the epoch, is arbitrarily set at midnight of January 1, 1970 at the prime meridian that passes through the Greenwich Royal Observatory in London. This is the same convention used in the Unix/POSIX time. Starting from that origin, time is measured in 86,400 seconds per day, forwards and backwards, in nanosecond precision. The Instant values go back as far as a billion years (Instant.MIN). That’s not quite enough to express the age of the universe (around 13.5 billion years), but it should be enough for all practical purposes. After all, a billion years ago, the earth was covered in ice and populated by microsocopic ancestors of today’s plants and animals. The largest value, Instant.MAX, is December 31 of the year 1,000,000,000. The static method call Instant.now() gives the current instant. You can compare two instants with the equals and compareTo methods in the usual way, so you can use instants as timestamps.

To find out the difference between two instants, use the static method Duration.between. For example, here is how you can measure the running time of an algorithm:

Instant start = Instant.now();
runAlgorithm();
Instant end = Instant.now();
Duration timeElapsed = Duration.between(start, end);
long millis = timeElapsed.toMillis();

A Duration is the amount of time between two instants. You can get the length of a Duration in conventional units by calling toNanos, toMillis, toSeconds, toMinutes, toHours, or toDays.

Durations require more than a long value for their internal storage. The number of seconds is stored in a long, and the number of nanoseconds in an additional int. If you want to make computations in nanosecond accuracy, and you actually need the entire range of a Duration, then you can use one of the methods in Table 5–1. Otherwise, you can just call toNanos and do your calculations with long values.

Image

Table 5–1 Arithmetic Operations for Time Instants and Durations


Image NOTE

It takes almost 300 years of nanoseconds to overflow a long.


For example, if you want to check whether an algorithm is at least ten times faster than another, you can compute

Duration timeElapsed2 = Duration.between(start2, end2);
boolean overTenTimesFaster =
timeElapsed.multipliedBy(10).minus(timeElapsed2).isNegative();
// Or timeElapsed.toNanos() * 10 < timeElapsed2.toNanos()


Image NOTE

The Instant and Duration classes are immutable, and all methods, such as multipliedBy or minus, return a new instance.


5.2. Local Dates

Now let us turn from absolute time to human time. There are two kinds of human time in the new Java API, local date/time and zoned time. Local date/time has a date and/or time of day, but no associated time zone information. A local date is, for example, June 14, 1903 (the day on which Alonzo Church, inventor of the lambda calculus, was born). Since that date has neither a time of day nor time zone information, it does not correspond to a precise instant of time. In contrast, July 16, 1969, 09:32:00 EDT (the launch of Apollo 11) is a zoned date/time, representing a precise instant on the time line.

There are many calculations where time zones are not required, and in some cases they can even be a hindrance. Suppose you schedule a meeting every week at 10:00. If you add 7 days (that is, 7 × 24 × 60 × 60 seconds) to the last zoned time, and you happen to cross the daylight savings time boundary, the meeting will be an hour too early or too late!

For that reason, the API designers recommend that you do not use zoned time unless you really want to represent absolute time instances. Birthdays, holidays, schedule times, and so on are usually best represented as local dates or times.

A LocalDate is a date, with a year, month, and day of the month. To construct one, you can use the now or of static methods:

LocalDate today = LocalDate.now(); // Today's date
LocalDate alonzosBirthday = LocalDate.of(1903, 6, 14);
alonzosBirthday = LocalDate.of(1903, Month.JUNE, 14);
// Uses the Month enumeration

Unlike the irregular conventions in Unix and java.util.Date, where months are zero-based and years are counted from 1900, you supply the usual numbers for the month of year. Alternatively, you can use the Month enumeration.

Table 5–2 shows the most useful methods for working with LocalDate objects.

Image

Image

Table 5–2 LocalDate Methods

For example, Programmer’s Day is the 256th day of the year. Here is how you can easily compute it:

LocalDate programmersDay = LocalDate.of(2014, 1, 1).plusDays(255);
// September 13, but in a leap year it would be September 12

Recall that the difference between two time instants is a Duration. The equivalent for local dates is a Period, which expresses a number of elapsed years, months, or days. You can call birthday.plus(Period.ofYears(1)), to get the birthday next year. Of course, you can also just call birthday.plusYears(1). But birthday.plus(Duration.ofDays(365)) won’t produce the correct result in a leap year.

The until method yields the difference between two local dates. For example,

independenceDay.until(christmas)

yields a period of 5 months and 21 days. That is actually not terribly useful because the number of days per month varies. To find the number of days, use

independenceDay.until(christmas, ChronoUnit.DAYS) // 174 days


Image CAUTION

Some methods in Table 5–2 could potentially create nonexistent dates. For example, adding one month to January 31 should not yield February 31. Instead of throwing an exception, these methods return the last valid day of the month. For example,

LocalDate.of(2016, 1, 31).plusMonths(1)

and

LocalDate.of(2016, 3, 31).minusMonths(1)

yield February 29, 2016.


The getDayOfWeek yields the weekday, as a value of the DayOfWeek enumeration. DayOfWeek.MONDAY has the numerical value 1, and DayOfWeek.SUNDAY has the value 7. For example,

LocalDate.of(1900, 1, 1).getDayOfWeek().getValue()

yields 1. The DayOfWeek enumeration has convenience methods plus and minus to compute weekdays modulo 7. For example, DayOfWeek.SATURDAY.plus(3) yields DayOfWeek.TUESDAY.


Image NOTE

The weekend days actually come at the end of the week. This is different from java.util.Calendar, where Sunday has value 1 and Saturday value 7.


In addition to LocalDate, there are also classes MonthDay, YearMonth, and Year to describe partial dates. For example, December 25 (with the year unspecified) can be represented as a MonthDay.

5.3. Date Adjusters

For scheduling applications, you often need to compute dates such as “the first Tuesday of every month.” The TemporalAdjusters class provides a number of static methods for common adjustments. You pass the result of an adjustment method to the with method. For example, the first Tuesday of a month can be computed like this:

LocalDate firstTuesday = LocalDate.of(year, month, 1).with(
TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY));

As always, the with method returns a new LocalDate object without modifying the original. Table 5–3 shows the available adjusters.

Image

Table 5–3 Date Adjusters in the TemporalAdjusters Class

You can also roll your own adjuster by implementing the TemporalAdjuster interface. Here is an adjuster for computing the next weekday.

TemporalAdjuster NEXT_WORKDAY = w -> {
LocalDate result = (LocalDate) w;
do {
result = result.plusDays(1);
} while (result.getDayOfWeek().getValue() >= 6);
return result;
};

LocalDate backToWork = today.with(NEXT_WORKDAY);

Note that the parameter of the lambda expression has type Temporal, and it must be cast to LocalDate. You can avoid this cast with the ofDateAdjuster method that expects a lambda of type UnaryOperator<LocalDate>.

TemporalAdjuster NEXT_WORKDAY = TemporalAdjusters.ofDateAdjuster(w -> {
LocalDate result = w; // No cast
do {
result = result.plusDays(1);
} while (result.getDayOfWeek().getValue() >= 6);
return result;
});

5.4. Local Time

A LocalTime represents a time of day, such as 15:30:00. You can create an instance with the now or of methods:

LocalTime rightNow = LocalTime.now();
LocalTime bedtime = LocalTime.of(22, 30); // or LocalTime.of(22, 30, 0)

Table 5–4 shows common operations with local times. The plus and minus operations wrap around a 24-hour day. For example,

LocalTime wakeup = bedtime.plusHours(8); // wakeup is 6:30:00

Image

Table 5–4 LocalTime Methods


Image NOTE

LocalTime doesn’t concern itself with AM/PM. That silliness is left to a formatter—see Section 5.6, “Formatting and Parsing,” on page 112.


There is a LocalDateTime class, representing a date and time. That class is suitable for storing points in time in a fixed time zone, for example, for a schedule of classes or events. However, if you need to make calculations that span the daylight savings time, or if you need to deal with users in different time zones, you should use the ZonedDateTime class that we discuss next.

5.5. Zoned Time

Time zones, perhaps because they are an entirely human creation, are even messier than the complications caused by the earth’s irregular rotation. In a rational world, we’d all follow the clock in Greenwich, and some of us would eat our lunch at 02:00, others at 22:00. Our stomachs would figure it out. This is actually done in China, which spans four conventional time zones. Elsewhere, we have time zones with irregular and shifting boundaries, and, to make matters worse, the daylight savings time.

As capricious as the time zones may appear to the enlightened, they are a fact of life. When you implement a calendar application, it needs to work for people who fly from one country to another. When you have a conference call at 10:00 in New York, but happen to be in Berlin, you expect to be alerted at the correct local time.

The Internet Assigned Numbers Authority (IANA) keeps a database of all known time zones around the world (https://www.iana.org/time-zones), which is updated several times per year. The bulk of the updates deals with the changing rules for daylight savings time. Java uses the IANA database.

Each time zone has an ID, such as America/New_York or Europe/Berlin. To find out all available time zones, call ZoneId.getAvailableIds. At the time of this writing, there were almost 600 IDs.

Given a time zone ID, the static method ZoneId.of(id) yields a ZoneId object. You can use that object to turn a LocalDateTime object into a ZonedDateTime object by calling local.atZone(zoneId), or you can construct a ZonedDateTime by calling the static methodZonedDateTime.of(year, month, day, hour, minute, second, nano, zoneId). For example,

ZonedDateTime apollo11launch = ZonedDateTime.of(1969, 7, 16, 9, 32, 0, 0,
ZoneId.of("America/New_York"));
// 1969-07-16T09:32-04:00[America/New_York]

This is a specific instant in time. Call apollo11launch.toInstant to get the Instant. Conversely, if you have an instant in time, call instant.atZone(ZoneId.of("UTC")) to get the ZonedDateTime at the Greenwich Royal Observatory, or use another ZoneId to get it elsewhere on the planet.


Image NOTE

UTC stands for “coordinated universal time,” and the acronym is a compromise between the aforementioned English and the French “temps universel coordiné,” having the distinction of being incorrect in either language. UTC is the time at the Greenwich Royal Observatory, without daylight savings time.


Many of the methods of ZonedDateTime are the same as those of LocalDateTime (see Table 5–5). Most are straightforward, but daylight savings time introduces some complications.

Image

Image

Table 5–5 ZonedDateTime Methods

When daylight savings time starts, clocks advance by an hour. What happens when you construct a time that falls into the skipped hour? For example, in 2013, Central Europe switched to daylight savings time on March 31 at 2:00. If you try to construct nonexistent time March 31 2:30, you actually get 3:30.

ZonedDateTime skipped = ZonedDateTime.of(
LocalDate.of(2013, 3, 31),
LocalTime.of(2, 30),
ZoneId.of("Europe/Berlin"));
// Constructs March 31 3:30

Conversely, when daylight time ends, clocks are set back by an hour, and there are two instants with the same local time! When you construct a time within that span, you get the earlier of the two.

ZonedDateTime ambiguous = ZonedDateTime.of(
LocalDate.of(2013, 10, 27), // End of daylight savings time
LocalTime.of(2, 30),
ZoneId.of("Europe/Berlin"));
// 2013-10-27T02:30+02:00[Europe/Berlin]
ZonedDateTime anHourLater = ambiguous.plusHours(1);
// 2013-10-27T02:30+01:00[Europe/Berlin]

An hour later, the time has the same hours and minutes, but the zone offset has changed.

You also need to pay attention when adjusting a date across daylight savings time boundaries. For example, if you set a meeting for next week, don’t add a duration of seven days:

ZonedDateTime nextMeeting = meeting.plus(Duration.ofDays(7));
// Caution! Won't work with daylight savings time

Instead, use the Period class.

ZonedDateTime nextMeeting = meeting.plus(Period.ofDays(7)); // OK


Image CAUTION

There is also an OffsetDateTime class that represents times with an offset from UTC, but without time zone rules. That class is intended for specialized applications that specifically require the absence of those rules, such as certain network protocols. For human time, useZonedDateTime.


5.6. Formatting and Parsing

The DateTimeFormatter class provides three kinds of formatters to print a date/time value:

• Predefined standard formatters (see Table 5–6)

Image

Image

Table 5–6 Predefined Formatters

• Locale-specific formatters

• Formatters with custom patterns

To use one of the standard formatters, simply call its format method:

String formatted = DateTimeFormatter.ISO_DATE_TIME.format(apollo11launch);
// 1969-07-16T09:32:00-05:00[America/New_York]

The standard formatters are mostly intended for machine-readable timestamps. To present dates and times to human readers, use a locale-specific formatter. There are four styles, SHORT, MEDIUM, LONG, and FULL, for both date and time—see Table 5–7.

Image

Table 5–7 Locale-Specific Formatting Styles

The static methods ofLocalizedDate, ofLocalizedTime, and ofLocalizedDateTime create such a formatter. For example:

DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
String formatted = formatter.format(apollo11launch);
// July 16, 1969 9:32:00 AM EDT

These methods use the default locale. To change to a different locale, simply use the withLocale method.

formatted = formatter.withLocale(Locale.FRENCH).format(apollo11launch);
// 16 juillet 1969 09:32:00 EDT


Image NOTE

The java.time.format.DateTimeFormatter class is intended as a replacement for java.util.DateFormat. If you need an instance of the latter for backwards compatibility, call formatter.toFormat().


Finally, you can roll your own date format by specifying a pattern. For example,

formatter = DateTimeFormatter.ofPattern("E yyyy-MM-dd HH:mm");

formats a date in the form Wed 1969-07-16 09:32. Each letter denotes a different time field, and the number of times the letter is repeated selects a particular format, according to rules that are arcane and seem to have organically grown over time. Table 5–8 shows the most useful pattern elements.

Image

Table 5–8 Commonly Used Formatting Symbols for Date/Time Formats

To parse a date/time value from a string, use one of the static parse methods. For example,

LocalDate churchsBirthday = LocalDate.parse("1903-06-14");
ZonedDateTime apollo11launch =
ZonedDateTime.parse("1969-07-16 03:32:00-0400",
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssxx"));

The first call uses the standard ISO_LOCAL_DATE formatter, the second one a custom formatter.

5.7. Interoperating with Legacy Code

As a brand-new creation, the Java Date and Time API will have to interoperate with existing classes, in particularicular, the ubiquitous java.util.Date, java.util.GregorianCalendar, and java.sql.Date/Time/Timestamp.

The Instant class is a close analog to java.util.Date. In Java 8, that class has two added methods: the toInstant method that converts a Date to an Instant, and the static from method that converts in the other direction.

Similarly, ZonedDateTime is a close analog to java.util.GregorianCalendar, and that class has gained conversion methods in Java 8. The toZonedDateTime method converts a GregorianCalendar to a ZonedDateTime, and the static from method does the opposite conversion.

Another set of conversions is available for the date and time classes in the java.sql package. You can also pass a DateTimeFormatter to legacy code that uses java.text.Format. Table 5–9 summarizes these conversions.

Image

Table 5–9 Conversions between java.time Classes and Legacy Classes

Exercises

1. Compute Programmer’s Day without using plusDays.

2. What happens when you add one year to LocalDate.of(2000, 2, 29)? Four years? Four times one year?

3. Implement a method next that takes a Predicate<LocalDate> and returns an adjuster yielding the next date fulfilling the predicate. For example,

today.with(next(w -> getDayOfWeek().getValue() < 6))

computes the next workday.

4. Write an equivalent of the Unix cal program that displays a calendar for a month. For example, java Cal 3 2013 should display

1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

indicating that March 1 is a Friday. (Show the weekend at the end of the week.)

5. Write a program that prints how many days you have been alive.

6. List all Friday the 13th in the twentieth century.

7. Implement a TimeInterval class that represents an interval of time, suitable for calendar events (such as a meeting on a given date from 10:00 to 11:00). Provide a method to check whether two intervals overlap.

8. Obtain the offsets of today’s date in all supported time zones for the current time instant, turning ZoneId.getAvailableIds into a stream and using stream operations.

9. Again using stream operations, find all time zones whose offsets aren’t full hours.

10. Your flight from Los Angeles to Frankfurt leaves at 3:05 pm local time and takes 10 hours and 50 minutes. When does it arrive? Write a program that can handle calculations like this.

11. Your return flight leaves Frankfurt at 14:05 and arrives in Los Angeles at 16:40. How long is the flight? Write a program that can handle calculations like this.

12. Write a program that solves the problem described at the beginning of Section 5.5, “Zoned Time,” on page 109. Read a set of appointments in different time zones and alert the user which ones are due within the next hour in local time.