By Pierre Templier – @ptemplier
There are three time management APIs in Java 8:
Before Java 8
This new API intends to resolve issues that plagued the two first ones:
- Years are stored in two digits starting in 1900, which leads to cumbersome +1900/-1900 operations to use these values
- Month starts at 0, which forces developers to add +1 to convert them to human-readable numbers
- The Date and Calendar objects are mutable. To avoid unwanted modifications, one has to make defensive copies via clone()
- Unintuitive naming: Date is a date and time, not a date
- Many deprecated methods and constructors because of bugs
- APIs are hard to use and not intuitive
- Not thread-safe: cause of concurrency bugs
- Incomplete concepts: no duration, period, interval hour (hours-minutes-seconds), date (year-month-day), etc.
- Poor internal presentation of dates as ‘instant’. A date&time can correspond to multiple instants during a daylight saving time change or it may not exist (when going from 3am to 2am or 2 to 3), which leads to complicated calculations
- Too flexible. Each class can accept a calendar different from the gregorian calendar. java.time uses an global ISO calendar system (gregorian) by default.
- Accepts null without throwing an error for most methods, which makes code error-prone
java.time aims to have a simpler and more robust design than Joda-Time. This API is also available in Java 7 through a backport available at the central maven. The fluid API allows the manipulation of immutable and thread-safe objects in order to avoid errors. The time management classes are organized according to two views of time: machine time and human time. To make the transition less painful, it is easy to convert java.util.Date and java.util.GregorianCalendar objects to and from the new API.
A precise point in time.
The Instant class represents the number of nanoseconds since Epoch (January 1st 1970). It is very similar to java.util.Date.
Duration between two instants or duration in days/hours/minutes/seconds/milliseconds/nanoseconds.
Duration.ofDays(5); Duration.of(5, ChronoUnit.DAYS); Duration.between(Instant.parse("2011-07-28T00:00:00.000Z"), Instant.parse("2014-03-18T00:00:00.000Z")).getSeconds();
Use Timezone to create ZonedDateTime and OffsetDateTime from an instant through the ZoneId and ZoneOffset classes.
ZonedDateTime and OffsetDateTime are the equivalent of GregorianCalendar.
ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("Europe/Paris")); OffsetDateTime.ofInstant(Instant.now(), ZoneId.of("GMT+1")); OffsetDateTime.ofInstant(Instant.now(), ZoneOffset.of("+01:00"));
Date and time (without the timezone concept).
LocalDateTime date = LocalDateTime.of(LocalDate.of(2014, Month.MARCH, 18), LocalTime.MIDNIGHT); // formatage date.format(DateTimeFormatter.ISO_DATE); date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")); //parsing LocalDateTime.parse("18/03/2014 00:00", DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")); // manipulation date.withHour(16).plusWeeks(3); // Set time at 16:00 and we add 3 weeks date.with(TemporalAdjusters.firstDayOfNextMonth()); // the first day of the next month date.with(Temporals.nextWorkingDay()); // TemporalAdjuster in threeten-extra (not included in the jdk)
Partial dates and times
An incomplete LocalDateTime: 03/18 (March 18th), 12:00 (noon).
LocalDate.of(2014, Month.MARCH, 18); LocalTime.of(12, 0); // same as LocalTime.NOON DayOfWeek.of(2);// DayOfWeek.TUESDAY Month.of(3); // same as Month.MARCH MonthDay.of(Month.MARCH, 18); YearMonth.of(2014, Month.MARCH); Year.of(2014);
“Human” representation of a duration, for instance 1 month (whether it has 28 or 31 days).
Period.ofMonths(3); // period between Java 7 and Java 8 Period.between(LocalDate.of(2011, Month.JULY, 28), LocalDate.of(2014, Month.MARCH, 18)); // 2 years, 7 months and 18 days
java.time, Date, GregorianCalendar interoperability
It is easy to convert an object to and from the new API.
Date date = new Date(); date.toInstant(); Date.from(Instant.now()); GregorianCalendar gregorianCalendar = new GregorianCalendar(TimeZone.getTimeZone("Europe/Paris")); gregorianCalendar.toZonedDateTime(); gregorianCalendar.toInstant(); GregorianCalendar.from(ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("Europe/Paris")));
As the code samples demonstrate, this new API corrects issues with previous APIs. It is easy to use thanks to its fluid syntax and intuitive concepts.
Some of the Joda-time features that have not made it into java.time are now in the threeten-extra project (available at maven central), and can be included in your projects.
One can hope this new API will be quickly integrated in the java frameworks developers use every day.