Introduction (Business Case)
With the release of the new Java SE 8, a new Date and Time API has been introduced to fix common problems that developers have encountered among time. The previous API had mutable classes (when you have a reference to an instance of an object, the contents of that instance can be altered), not thread-safe and used zero-based indexes for months; facts that led developers to other date/time libraries like Joda-Time in order to get a better API.
The new API, JSR-310, is designed to replace the existing Date and Calendar classes, incorporating ideas from Joda-Time. Each of the new core Java classes for date, time, date and time combined, time zones, instants, duration, and clocks, has a specific purpose and has explicitly defined behavior. It also provides support for the international ISO-8601 time standard and supports the frequently used Japanese, Minguo, Hijrah and Thai Buddhist calendars. This article is intended to give a brief overview over the new Date and Time API and provides simple and ready to use examples.
Creating Objects
First, let’s start by creating objects. The java.time package has separate classes to represent a date without time, a time-of-day without date, a year, a month, a day-of-week, a year and month, a month and day-of-month, a date and time without time-zone, a full date and time complete with time-zone and an instantaneous point in time. The following examples use the Instant, LocalDate, LocalTime and LocalDateTime classes in order to get an instance of time, date or an instance of date and time.
The Instant class is one of the core new classes introduced in the date/time package. An Instant instance represents the start of a nanosecond on the timeline and it’s useful for working with time viewed from a machine perspective:
// Current timestamp Instant timestamp = Instant.now(); System.out.println("Current timestamp: " + timestamp); // Create Instant from specified clock Instant fromClock = Instant.now(Clock.systemUTC()); System.out.println("Instant from specified clock: " + fromClock); // Create Instant from a String Instant fromString = Instant.parse("1995-10-23T10:12:35Z"); System.out.println("Instant from a String: " + fromString); // java.util.Date into an Instant Instant instant = Instant.ofEpochMilli(new Date().getTime()); System.out.println("java.util.Date into an Instant: " + instant);
The output will be:
Current timestamp: 2014-05-09T10:58:27.657Z Instant from specified clock: 2014-05-09T10:58:27.757Z Instant from a String: 1995-10-23T10:12:35Z java.util.Date into an Instant: 2014-05-09T10:58:27.764Z
An instance of the LocalDate class represents a year-month-day in the ISO-8601 calendar system and is useful for representing a date without a time zone:
// Current date LocalDate date = LocalDate.now(); System.out.println("Current date: " + date); // Create LocalDate from a String LocalDate dateFromString = LocalDate.parse("2014-05-05"); System.out.println("LocalDate from a String: " + dateFromString); // Create LocalDate from default time-zone clock LocalDate date1 = LocalDate.now(Clock.systemDefaultZone()); System.out.println("LocalDate from default time-zone clock: " + date1); // Create LocalDate by providing input arguments LocalDate birthday = LocalDate.of(2014, Month.JULY, 16); System.out.println("LocalDate by providing input arguments: " + birthday); // Current date in "Asia/Tokyo" LocalDate todayTokyo = LocalDate.now(ZoneId.of("Asia/Tokyo")); System.out.println("Current date in 'Asia/Tokyo': " + todayTokyo); // Get an instance of LocalDate from a year and day-of-year LocalDate get99DayOfYear = LocalDate.ofYearDay(2000, 99); System.out.println("LocalDate from a year and day-of-year: " + get99DayOfYear);
The output will be:
Current date: 2014-05-09 LocalDate from a String: 2014-05-05 LocalDate from default time-zone clock: 2014-05-09 LocalDate by providing input arguments: 2014-07-16 Current date in 'Asia/Tokyo': 2014-05-09 LocalDate from a year and day-of-year: 2000-04-08
The LocalTime class is similar to LocalDate and represents a human-based time of day without time zone in the ISO-8601 calendar system:
// Current time LocalTime time = LocalTime.now(); System.out.println("Current time: " + time); // Create LocalTime from a String LocalTime time1 = LocalTime.parse("12:00:53"); System.out.println("LocalTime from a String: " + time1); // Create LocalTime from default time-zone clock LocalTime time2 = LocalTime.now(Clock.systemDefaultZone()); System.out.println("LocalTime from default time-zone clock: " + time2); // Create LocalTime by providing input arguments LocalTime time3 = LocalTime.of(12, 26, 53); System.out.println("LocalTime by providing input arguments: " + time3); // Current time in "Asia/Tokyo" LocalTime timeTokyo = LocalTime.now(ZoneId.of("Asia/Tokyo")); System.out.println("Current time in 'Asia/Tokyo': " + timeTokyo); // Create LocalTime from LocalDateTime LocalTime fromLocalDateTime = LocalTime.from(LocalDateTime.now(ZoneId.of("Asia/Tokyo"))); System.out.println("LocalTime from LocalDateTime: " + fromLocalDateTime); // Get an instance of LocalTime from a second-of-day value LocalTime localTime = LocalTime.ofSecondOfDay(50000); System.out.println("LocalTime from a second-of-day value: " + localTime);
The output will be:
Current time: 13:58:27.797 LocalTime from a String: 12:00:53 LocalTime from default time-zone clock: 13:58:27.797 LocalTime by providing input arguments: 12:26:53 Current time in 'Asia/Tokyo': 19:58:27.797 LocalTime from LocalDateTime: 19:58:27.798 LocalTime from a second-of-day value: 13:53:20
If we want to work both with date and time, then we can use another core class from the date/time package, the LocalDateTime class. It’s a class used to represent date (month-day-year) together with time (hour-minute-second-nanosecond):
// Current date LocalDateTime localDateTime = LocalDateTime.now(); System.out.println("Current date: " + localDateTime); // Current date using LocalDate and LocalTime LocalDateTime localDateTime1 = LocalDateTime.of(LocalDate.now(), LocalTime.now()); System.out.println("Current date using LocalDate and LocalTime: " + localDateTime1); // Create LocalDateTime from a String LocalDateTime localDateTime2 = LocalDateTime.parse("2014-05-05T12:07:45.915"); System.out.println("LocalDateTime from a String: " + localDateTime2); // Create LocalDateTime by providing input arguments LocalDateTime birthday1 = LocalDateTime.of(2014, Month.JULY, 16, 13, 15, 30); System.out.println("LocalDateTime by providing input arguments: " + birthday1); // Current date in "Asia/Tokyo" LocalDateTime localDateTime3 = LocalDateTime.now(ZoneId.of("Asia/Tokyo")); System.out.println("Current date in 'Asia/Tokyo': " + localDateTime3); // Create LocalDateTime from Instant LocalDateTime localDateTime4 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()); System.out.println("LocalDateTime from Instant: " + localDateTime4);
The output will be:
Current date: 2014-05-09T13:58:27.798 Current date using LocalDate and LocalTime: 2014-05-09T13:58:27.798 LocalDateTime from a String: 2014-05-05T12:07:45.915 LocalDateTime by providing input arguments: 2014-07-16T13:15:30 Current date in 'Asia/Tokyo': 2014-05-09T19:58:27.800 LocalDateTime from Instant: 2014-05-09T13:58:27.800
Basic Usage Examples
Going further, we can use the plus and minus methods for adding or subtracting hours, minutes, days, weeks, and months. Note that these methods always return a new instance since Java 8 date/time classes are immutable. The following example shows a few of these methods:
LocalDate localDate = LocalDate.now(); System.out.println("Today is: " + localDate); // Plus and minus operations to an LocalDate System.out.println("Adding 3 weeks to date: " + localDate.plusWeeks(3)); System.out.println("Adding 4 years and 6 months to date: " + localDate.plusYears(4).plusMonths(6)); System.out.println("3 weeks and 9 days before today: " + localDate.minusWeeks(3).minusDays(9)); System.out.println("6 months before today: " + localDate.minusMonths(6)); // Combines this date with a time to create a LocalDateTime LocalDateTime localDateTime = localDate.atTime(LocalTime.now()); // Plus and minus operations to an LocalDateTime System.out.println("Adding 43 minutes and 14 seconds: " + localDateTime.toLocalTime().plusMinutes(43).plusSeconds(14)); System.out.println("Adding the same amount of time using Duration: " + localDateTime.toLocalTime().plus(Duration.ofMinutes(43).plusSeconds(14))); System.out.println("Adding 5 weeks and 3 days using Period: " + localDateTime.toLocalDate().plus(Period.ofWeeks(5).plusDays(3))); Instant timestamp = Instant.now(); System.out.println("Current timestamp: " + timestamp); // Plus and minus operations to an Instant System.out.println("Subtracts 2 hours from an Instant: " + timestamp.minus(Duration.ofHours(2))); System.out.println("Subtracts 1 day from an Instant: " + timestamp.minus(1, ChronoUnit.DAYS)); System.out.println("Adding 2 hours and 30 minutes to an Instant: " + timestamp.plus(Duration.ofHours(2).plusMinutes(30))); System.out.println("Adding 2 days to an Instant: " + timestamp.plus(2, ChronoUnit.DAYS));
The output will be:
Today is: 2014-05-07 Adding 3 weeks to date: 2014-05-28 Adding 4 years and 6 months to date: 2018-11-07 3 weeks and 9 days before today: 2014-04-07 6 months before today: 2013-11-07 Adding 43 minutes and 14 seconds: 17:44:31.506 Adding the same amount of time using Duration: 17:44:31.506 Adding 5 weeks and 3 days using Period: 2014-06-14 Current timestamp: 2014-05-07T14:01:17.507Z Subtracts 2 hours from an Instant: 2014-05-07T12:01:17.507Z Subtracts 1 day from an Instant: 2014-05-06T14:01:17.507Z Adding 2 hours and 30 minutes to an Instant: 2014-05-07T16:31:17.507Z Adding 2 days to an Instant: 2014-05-09T14:01:17.507Z
Working with TemporalAdjusters
The java.time.temporal package is another way of date and time manipulation. A good example is to use the TemporalAdjuster interface that provides methods that can take a Temporal value and return an adjusted value. If we want to find the first day of a month, the last day of the year, the next Sunday or the last Sunday of the year, and other examples like these, the TemporalAdjusters class provides a set of predefined adjusters. The following examples use some of these predefined adjusters:
LocalDate localDate = LocalDate.now(); Locale locale = Locale.getDefault(); Month month = localDate.getMonth(); DayOfWeek dayOfWeek = localDate.getDayOfWeek(); // Working with TemporalAdjusters System.out.println("Today is: " + dayOfWeek.getDisplayName(TextStyle.FULL, locale)); System.out.println("This month is: " + month.getDisplayName(TextStyle.FULL, locale)); System.out.println("First day of the month was: " + localDate.with(TemporalAdjusters.firstDayOfMonth()).getDayOfWeek().name()); System.out.println("Last day of the month will be: " + localDate.with(TemporalAdjusters.lastDayOfMonth()).getDayOfWeek().name()); System.out.println("Past Friday was: " + localDate.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY))); System.out.println("Next Friday is: " + localDate.with(TemporalAdjusters.next(DayOfWeek.FRIDAY))); System.out.println("First Friday this month was: " + localDate.with(TemporalAdjusters.firstInMonth(DayOfWeek.FRIDAY))); System.out.println("Last Friday this month will be: " + localDate.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY))); System.out.println("First day of next month will be: " + localDate.with(TemporalAdjusters.firstDayOfNextMonth()).getDayOfWeek().name()); System.out.println("First day of the year was: " + localDate.with(TemporalAdjusters.firstDayOfYear()).getDayOfWeek().name()); System.out.println("Last day of the year will be: " + localDate.with(TemporalAdjusters.lastDayOfYear()).getDayOfWeek().name());
The output will be:
Today is: Friday This month is: May First day of the month was: THURSDAY Last day of the month will be: SATURDAY Past Friday was: 2014-05-02 Next Friday is: 2014-05-16 First Friday this month was: 2014-05-02 Last Friday this month will be: 2014-05-30 First day of next month will be: SUNDAY First day of the year was: WEDNESDAY Last day of the year will be: WEDNESDAY
Working with ZoneId, ZoneOffset and ZonedDateTime
As we all know, time zones are regions described by an identifier and has the format region/city (Europe/London) and an offset from Greenwich/UTC time (+01:00). The new API has five main classes to help developers in dealing with time zones:
- ZoneId is a class that represents a time zone identifier and it also provides rules for converting between an Instant and a LocalDateTime time.
- ZoneOffset is a class that represents a time zone offset from Greenwich/UTC time .
- ZonedDateTime is a class that represents a full date (year, month, day) and time (hour, minute, second, nanosecond) with a time zone (region/city).
- OffsetDateTime is a class that represents a full date (year, month, day) and time (hour, minute, second, nanosecond) with an offset from Greenwich/UTC time (+/-hours:minutes).
- OffsetTime is a class that represents time (hour, minute, second, nanosecond) with an offset from Greenwich/UTC time (+/-hours:minutes).
Going further, to make use of these classes, the next example passes a region and in return, receives a java.util.Map with the available time zones from the specified region:
public static Map<String, String> getZoneDateTime(String continent) { LocalDateTime dateTime = LocalDateTime.now(); Map<String, String> zoneList = new HashMap<>(); for (String s : ZoneId.getAvailableZoneIds()) { ZoneId zone = ZoneId.of(s); if (zone.getId().contains(continent)) { zoneList.put(zone.getId(), dateTime.atZone(zone).getOffset().getId()); } } return zoneList; } public static void main(String[] args) { Map<String, String> zoneList = getZoneDateTime("Europe"); for (Map.Entry<String, String> entry : zoneList.entrySet()) { String zoneId = entry.getKey(); String offset = entry.getValue(); System.out.println(zoneId + " " + offset); } }
The output will be:
Europe/Ljubljana +02:00 Europe/Kaliningrad +03:00 Europe/Berlin +02:00 Europe/Lisbon +01:00 Europe/Moscow +04:00 Europe/Oslo +02:00 Europe/Chisinau +03:00 Europe/London +01:00 . . . . . . . . . . . . .
In the next example, we suppose that we are in Chicago and we need to get to Tokyo. At least two sub-scenarios can result from this scenario. The first one, we know the departure time for our flight and the duration of the flight, and we want to find out when we will land in Tokyo. In the second one, we know the departure and the arriving time, and we want to find out the duration of the flight:
public static LocalTime getArrivalHour(ZoneId leaving, LocalTime leavingAt, ZoneId arriving, Duration flightDuration) { ZonedDateTime departure = ZonedDateTime.of(LocalDateTime.of(LocalDate.now(), leavingAt), leaving); ZonedDateTime arrival = departure.withZoneSameInstant(arriving).plus(flightDuration); return arrival.toLocalTime(); } public static Duration getFlightDuration(ZoneId leaving, LocalTime leavingAt, ZoneId arriving, LocalTime arrivingAt) { ZonedDateTime departure = ZonedDateTime.of(LocalDateTime.of(LocalDate.now(), leavingAt), leaving); ZonedDateTime arrival = ZonedDateTime.of(LocalDateTime.of(LocalDate.now(), arrivingAt), arriving); return Duration.between(departure, arrival).abs(); } public static void main(String[] args) { ZoneId leaving = ZoneId.of("America/Chicago"); ZoneId arriving = ZoneId.of("Asia/Tokyo"); LocalTime leavingAt = LocalTime.of(12, 00, 00); Duration flightDuration = Duration.ofHours(12).plusMinutes(21); LocalTime arrivingAt = getArrivalHour(leaving, leavingAt, arriving, flightDuration); System.out.println("Flight arrival hour: " + arrivingAt); System.out.println("Flight duration: " + getFlightDuration(leaving, leavingAt, arriving, arrivingAt)); }
The output will be:
Flight arrival hour: 14:21 Flight duration: PT11H39M
Parsing and Formatting Dates
Parsing and formatting strings is important when you need to work with dates and times. The new date/time API has simplified these things and provides methods like parse() and format(). A DateTimeParseException exception can occur at parsing a string and a DateTimeException exception can occur at formatting a string:
public static String parseDate(String date) { String formattedDate = ""; try { LocalDateTime localDate = LocalDateTime.parse(date); formattedDate = formatDate(localDate); } catch (DateTimeParseException e) { return date + " is not parsable!"; } return formattedDate; } public static String formatDate(LocalDateTime date) { String formattedDate = ""; try { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM d yyyy - hh:mm a"); formattedDate = date.format(formatter); } catch (DateTimeException e) { return date + " can't be formatted!"; } return formattedDate; } public static void main(String[] args) { String date = "2014-05-05T12:07:45.915"; System.out.println("Parse and format date: " + parseDate(date)); }
The output will be:
Parse and format date: Mai 5 2014 - 12:07 PM
Date and Time Conversions
Of course, we can’t end this article without talking about possible conversions between dates and times. The following examples convert a java.util.Date to a LocalDate, LocalTime and LocalDateTime and backwards, and also, using the java.time.chrono package, we can convert a ISO-based date to non-ISO-based dates:
// Java SE 7 Date Date date = new Date(); // Convert java.util.Date to LocalDate, LocalTime, LocalDateTime LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); LocalTime localTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime(); LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); System.out.println(" java.util.Date: " + date + "n java.util.Date to LocalDate: " + localDate + "n java.util.Date to LocalTime: " + localTime + "n java.util.Date to LocalDateTime: " + localDateTime); // Convert LocalDate, LocalTime, LocalDateTime to java.util.Date LocalDate newLocalDate = LocalDate.now(); LocalTime newLocalTime = LocalTime.now(); LocalDateTime newLocalDateTime = LocalDateTime.now(); Date fromLocalDate = Date.from(LocalDateTime.of( newLocalDate, LocalTime.now()).toInstant(ZoneOffset.UTC)); Date fromLocalTime = Date.from(LocalDateTime.of( LocalDate.now(), newLocalTime).toInstant(ZoneOffset.UTC)); Date fromLocalDateTime = Date.from(newLocalDateTime.toInstant(ZoneOffset.UTC)); System.out.println(" LocalDate to java.util.Date: " + fromLocalDate + "n LocalTime to java.util.Date: " + fromLocalTime + "n LocalDateTime to java.util.Date: " + fromLocalDateTime); // Converting LocalDateTime to a Non-ISO-Based Date LocalDateTime ldt = LocalDateTime.now(); HijrahDate hdate = HijrahDate.from(ldt); JapaneseDate jdate = JapaneseDate.from(ldt); MinguoDate mdate = MinguoDate.from(ldt); ThaiBuddhistDate tdate = ThaiBuddhistDate.from(ldt); System.out.println(" Today: " + date + "n Hijrah date: " + hdate + "n Japanese date: " + jdate + "n Minguo date: " + mdate + "n ThaiBuddhist date: " + tdate);
The output will be:
Convert java.util.Date to LocalDate, LocalTime, LocalDateTime java.util.Date: Fri May 09 12:41:07 EEST 2014 java.util.Date to LocalDate: 2014-05-09 java.util.Date to LocalTime: 12:41:07.657 java.util.Date to LocalDateTime: 2014-05-09T12:41:07.657 Convert LocalDate, LocalTime, LocalDateTime to java.util.Date LocalDate to java.util.Date: Fri May 09 15:41:07 EEST 2014 LocalTime to java.util.Date: Fri May 09 15:41:07 EEST 2014 LocalDateTime to java.util.Date: Fri May 09 15:41:07 EEST 2014 Converting LocalDateTime to a Non-ISO-Based Date Today: Fri May 09 12:41:07 EEST 2014 Hijrah date: Hijrah-umalqura AH 1435-07-10 Japanese date: Japanese Heisei 26-05-09 Minguo date: Minguo ROC 103-05-09 ThaiBuddhist date: ThaiBuddhist BE 2557-05-09
Conclusion
In this article, we make use of the new Date and Time API released in Java 8 and as we can see, even if it is significantly inspired from Joda-Time, which has been the preferred Java Date and Time API for quite some time, it seems to be a rich API, intuitive and easy to work with.
Download Java-SE-8-examples.zip.