The Date and Time API - Core Java for the Impatient (2013)

Core Java for the Impatient (2013)

Chapter 12. The Date and Time API

Topics in This Chapter

Image 12.1 The Time Line

Image 12.2 Local Dates

Image 12.3 Date Adjusters

Image 12.4 Local Time

Image 12.5 Zoned Time

Image 12.6 Formatting and Parsing

Image 12.7 Interoperating with Legacy Code

Image Exercises

Time flies like an arrow, and we can easily set a starting point and count forwards 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, 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 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 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 (that is, 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.

12.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, to 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 microscopic 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:

Click here to view code image

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 to nanosecond accuracy, and you actually need the entire range of a Duration, you can use one of the methods in Table 12–1. Otherwise, you can just call toNanos and do your calculations with long values.

Image

Table 12–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

Click here to view code image

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.


12.2 Local Dates

Now let us turn from absolute time to human time. There are two kinds of human time in the 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. An example of a local date is 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:

Click here to view code image

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 12–2 shows the most useful methods for working with LocalDate objects.

Image

Image

Table 12–2 LocalDate Methods

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

Click here to view code image

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,

Click here to view code image

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

Click here to view code image

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


Image Caution

Some methods in Table 12–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,

Click here to view code image

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

and

Click here to view code image

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,

Click here to view code image

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.

12.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:

Click here to view code image

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 12–3 shows the available adjusters.

Image

Table 12–3 Date Adjusters in the TemporalAdjusters Class

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

Click here to view code image

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

Click here to view code image

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

12.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:

Click here to view code image

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

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

Click here to view code image

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

Image

Table 12–4 LocalTime Methods


Image Note

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


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.

12.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 method ZonedDateTime.of(year, month, day, hour, minute, second, nano, zoneId). For example,

Click here to view code image

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 12–5). Most are straightforward, but daylight savings time introduces some complications.

Image

Image

Table 12–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.

Click here to view code image

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.

Click here to view code image

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:

Click here to view code image

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

Instead, use the Period class.

Click here to view code image

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, use ZonedDateTime.


12.6 Formatting and Parsing

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

• Predefined standard formatters (see Table 12–6)

Image

Table 12–6 Predefined Formatters

• Locale-specific formatters

• Formatters with custom patterns

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

Click here to view code image

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 12–7.

Image

Table 12–7 Locale-Specific Formatting Styles

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

Click here to view code image

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.

Click here to view code image

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

The DayOfWeek and Month enumerations have methods getDisplayName for giving the names of weekdays and months in different locales and formats.

Click here to view code image

for (DayOfWeek w : DayOfWeek.values())
System.out.print(w.getDisplayName(TextStyle.SHORT, Locale.ENGLISH) + " ");
// Prints Mon Tue Wed Thu Fri Sat Sun

See Chapter 13 for more information about locales.


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,

Click here to view code image

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 12–8 shows the most useful pattern elements.

Image

Table 12–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,

Click here to view code image

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.

12.7 Interoperating with Legacy Code

As a brand-new creation, the Java Date and Time API will have to interoperate with existing classes, in particular, 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 12–9 summarizes these conversions.

Image

Table 12–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,

Click here to view code image

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.getAvailableZoneIds 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 12.5, “Zoned Time,” on p. 387. Read a set of appointments in different time zones and alert the user which ones are due within the next hour in local time.