Java 8 Date Time API
Introduction
Java 8 brought a completely redefined Date-Time API. One important feature - similarly to other already existing 3rd party API's - is that the great majority of the API objects that represent date and/or time information are immutable, and therefore thread-safe.
Until now, one had to use Calendar objects in order to do simple operations like date arithmetic or even to extract fields from a given date/time representation. Implementing such operations with the new API has become quite simple and intuitive.
The API is designed as what is called a fluent API, meaning that one may chain consecutive method calls over date/time instances in order to produce a final immutable result.
In this article we will go through an overview of the Java 8 Date Time API.
Enums
A couple of interesting enums are DayOfWeek and Month:
DayOfWeek monday = DayOfWeek.MONDAY; // Will print MONDAY System.out.println(monday); // Will print Mon System.out.println(monday.getDisplayName(TextStyle.SHORT, Locale.getDefault())); // Will print Monday System.out.println(monday.getDisplayName(TextStyle.FULL, Locale.getDefault())); // Will print SATURDAY System.out.println(monday.plus(5));
Month april = Month.APRIL; // Will print APRIL System.out.println(april); // Will print Apr System.out.println(april.getDisplayName(TextStyle.SHORT, Locale.getDefault())); // Will print April System.out.println(april.getDisplayName(TextStyle.FULL, Locale.getDefault())); // Will print JULY System.out.println(april.plus(3)); // Will print 29 System.out.println(Month.FEBRUARY.maxLength());
Date
The API provides some classes that contain only date related information, without considering time or timezones.
The LocalDate class represents a date without time information:
// Current date from the system clock LocalDate now = LocalDate.now(); // November 16th 2014 LocalDate date = LocalDate.of(2014, Month.NOVEMBER, 16); // November 22nd 2014 LocalDate nextSaturday = date.with(TemporalAdjusters.next(DayOfWeek.SATURDAY)); // SUNDAY DayOfWeek dayOfWeek = date.getDayOfWeek();
The YearMonth class represents, as the name states, a month + year pair:
// Current year/month from the system clock YearMonth now = YearMonth.now(); // February 2014 YearMonth february2014 = YearMonth.of(2014, Month.FEBRUARY); // Will print 28 System.out.println(february2014.lengthOfMonth()); // February 2016 YearMonth february2016 = YearMonth.of(2016, Month.FEBRUARY); // Will print 29 (leap year) System.out.println(february2016.lengthOfMonth());
The MonthDay class represents a day + month pair:
// Current month/day from the system clock MonthDay now = MonthDay.now(); // February 29th MonthDay monthDay = MonthDay.of(Month.FEBRUARY, 29); // true boolean isValidYear = monthDay.isValidYear(2016); // false isValidYear = monthDay.isValidYear(2014); // February 29th 2016 LocalDate date = monthDay.atYear(2016); // Will adjust to February 28th 2014 date = monthDay.atYear(2014);
The Year class represents an arbitrary year:
// Current year from the system clock Year now = Year.now(); // 2015 Year year = Year.of(2015); // April 15th 2015 LocalDate date = year.atMonth(Month.APRIL).atDay(15); // false boolean leapYear = year.isLeap(); // true leapYear = Year.of(2016).isLeap(); // July 21st 2015 date = year.atMonthDay(MonthDay.of(Month.JULY, 21));
Time
The LocalTime class deals with time information only:
// Current time from the system clock LocalTime now = LocalTime.now(); // Midnight LocalTime midnight = LocalTime.MIDNIGHT; // 10:23:45 LocalTime time = LocalTime.of(10, 23, 45); // 10 int hour = time.get(ChronoField.HOUR_OF_DAY); // 37425000000000 long nanos = time.toNanoOfDay();
Date and Time
The LocalDateTime class represents date together with time information:
// Current date-time from the system clock LocalDateTime now = LocalDateTime.now(); // November 22nd 2014, 10:13:34 LocalDateTime dateTime = LocalDateTime.of(2014, Month.NOVEMBER, 22, 10, 13, 34); // November 28th 2014, 15:13:34 LocalDateTime sixDaysAndFiveHoursAfter = dateTime.plusDays(6).plusHours(5); // 10 int hours = dateTime.get(ChronoField.HOUR_OF_DAY); LocalDate date = LocalDate.of(2014, Month.NOVEMBER, 22); LocalTime time = LocalTime.of(10, 14, 56); // November 22nd 2014, 10:14:56 LocalDateTime other = LocalDateTime.of(date, time); // 1 int compare = other.compareTo(dateTime); // 82 long secondsElapsed = Duration.between(dateTime, other).getSeconds();
Time Zone and Offset
The API also provides classes to deal with time zones and offsets.
The ZonedDateTime class represents a LocalDateTime with a time zone:
// November 20th 2014, 14:30 LocalDateTime dateTime = LocalDateTime.of(2014, Month.NOVEMBER, 20, 14, 30); ZoneId USEastZone = ZoneId.of("US/Eastern"); // November 20th 2014, 14:30 (-05:00) ZonedDateTime USEastDateTime = ZonedDateTime.of(dateTime, USEastZone); ZoneId USPacificZone = ZoneId.of("US/Pacific"); // November 20th 2014, 11:30 (-08:00) ZonedDateTime USPacificDateTime = USEastDateTime .withZoneSameInstant(USPacificZone);
The OffsetDateTime is used in order to represent a LocalDateTime with a specific offset:
// November 20th 2014, 14:30 LocalDateTime dateTime = LocalDateTime.of(2014, Month.NOVEMBER, 20, 14, 30); ZoneOffset offset = ZoneOffset.of("-05:00"); // November 20th 2014, 14:30 (-05:00) OffsetDateTime offsetDate = OffsetDateTime.of(dateTime, offset);
The OffsetTime class is similar to OffsetDateTime but deals with time only, ie. represents a LocalTime with a specific offset.
Instant
An Instant represents the number of nanoseconds since the Epoch (January 1st 1970). Instants which are previous to the Epoch are represented with negative values.
Instant instant = Instant.now(); Instant twoDaysLater = instant.plus(2, ChronoUnit.DAYS); long hoursElapsed = instant.until(twoDaysLater, ChronoUnit.HOURS); LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); ZonedDateTime USEastDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of("US/Eastern")); ZonedDateTime USPacificDateTime = ZonedDateTime.ofInstant(instant, ZoneId.of("US/Pacific"));
Converting from an Instant to a LocalDateTime or ZonedDateTime obviously needs a time zone, since it is the only way to transform an instant of the time line since the Epoch into a meaningful date (the same Epoch instant represents distinct date-time depending on the time zone).
Parsing and Formatting
Parsing and formatting makes use of the usual format string:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime dateTime = LocalDateTime.parse("2014-11-21 10:23:45", formatter);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime dateTime = LocalDateTime.of(2014, Month.NOVEMBER, 20, 14, 30); String formattedDate = dateTime.format(formatter);
Temporal Adjuster
Temporal adjusters allows one to adjust any of the temporal types we have seen previously in this article.
// November 16th 2014 LocalDate date = LocalDate.of(2014, Month.NOVEMBER, 16); // November 1st 2014 LocalDate adjusted = date.with(TemporalAdjusters.firstDayOfMonth()); // January 1st 2014 adjusted = date.with(TemporalAdjusters.firstDayOfYear()); // November 5th 2014 adjusted = date.with(TemporalAdjusters.firstInMonth(DayOfWeek.WEDNESDAY));
One may also define custom temporal adjusters. Since the TemporalAdjuster interface is a functional interface (an interface with a single method), one may define a custom adjuster using a lambda expression (more information in Java 8 Lambda expressions and Java 8 Method References):
TemporalAdjuster adjuster = (temporal) -> temporal .plus(10, ChronoUnit.MONTHS) .minus(Period.ofDays(23)); // November 16th 2014 LocalDate date = LocalDate.of(2014, Month.NOVEMBER, 16); // August 24th 2015 LocalDate adjusted = date.with(adjuster);
Temporal Query
Temporal queries allows one to execute queries against temporal representations:
TemporalQuery<Boolean> leapYearQuery = (temporal) -> Year.of(temporal.get(ChronoField.YEAR)).isLeap(); TemporalQuery<Long> epochSecondsQuery = (temporal) -> LocalDateTime.of( temporal.get(ChronoField.YEAR), temporal.get(ChronoField.MONTH_OF_YEAR), temporal.get(ChronoField.DAY_OF_MONTH), temporal.get(ChronoField.HOUR_OF_DAY), temporal.get(ChronoField.MINUTE_OF_HOUR), temporal.get(ChronoField.SECOND_OF_MINUTE)) .atZone(ZoneId.of("US/Eastern")).toEpochSecond(); // November 16th 2014, 10:23:45 LocalDateTime date = LocalDateTime.of(2014, Month.NOVEMBER, 16, 10, 23, 45); // false boolean leapYear = date.query(leapYearQuery); // 1416151425 Long epochSeconds = date.query(epochSecondsQuery); // November 16th 2016, 10:23:45 date = LocalDateTime.of(2016, Month.NOVEMBER, 16, 10, 23, 45); // true leapYear = date.query(leapYearQuery); // 1479309825 epochSeconds = date.query(epochSecondsQuery);
Period, Duration and ChronoUnit
The following classes may be used to measure intervals of time.
The Duration class is best suitable to measure machine based time, ie. a duration that is represented by seconds or nanoseconds:
LocalDateTime dateTime = LocalDateTime.of(2014, Month.NOVEMBER, 22, 10, 13, 34); LocalDateTime other = LocalDateTime.of(2014, Month.NOVEMBER, 22, 11, 24, 12); // 4238 long secondsElapsed = Duration.between(dateTime, other).getSeconds();
The Period class is best suitable to measure intervals that are represented by date constructs, like years, months and days:
LocalDate date = LocalDate.of(2014, Month.NOVEMBER, 22); LocalDate other = LocalDate.of(2017, Month.APRIL, 17); Period period = Period.between(date, other); // 2 int years = period.getYears(); // 4 int months = period.getMonths(); // 26 int days = period.getDays();
The ChronoUnit class allows one to define the unit under which the interval of time should be calculated:
LocalDate date = LocalDate.of(2014, Month.NOVEMBER, 22); LocalDate other = LocalDate.of(2017, Month.APRIL, 17); // 2 long years = ChronoUnit.YEARS.between(date, other); // 28 long months = ChronoUnit.MONTHS.between(date, other); // 877 long days = ChronoUnit.DAYS.between(date, other);