Java 8 introduced new classes for date and time to resolve the long-standing issues of existing APIs:
Java.util.Calendar. These newly introduced immutable-value classes are easy-to-use, well-documented, and thread-safe.
In this article, we will look at all Java 8 new date and time APIs in detail with examples. Let us start with the issues in the existing
Calendar classes and how the newly introduced classes addressed these shortcomings.
Why Java 8 new Date & Time API?
Java 8 new date and time API was specifically launched to address the following issues with the current date and time API:
- Thread-Safety — Both
java.util.Calendarclasses are not thread-safe. Therefore, developers have to deal with concurrency issues by writing additional codes. The new date and time APIs are immutable and thread-safe.
- Easier to Use & Understand — The old APIs are poorly designed with little to none direct methods for common date operations. The new date and time APIs introduced in Java 8 provides a consistent and easier-to-user ISO centric interface for dealing with dates, times, durations, and periods. There are a lot of utility methods included in the new API for common date and time tasks.
- Better Time Zone Handling — Old APIs do not provide any direct way to handle different timezones. Developers were forced to write additional logic to deal with timezone issues. Java 8 new date and time API separated both local and zoned date times handling into two different categories. Thus, making it easier to handle different timezones without writing extra codes.
Local Date & Time API
As a part of the local date and time API, Java 8 introduced
LocalDateTime classes. As their names suggest, these classes are intended to use for local date and time operations without timezones. You should use them when timezones are no longer required.
LocalDate class represents a date without time in the ISO-8601 format (yyyy-MM-dd). This class does not store or represent a time or time-zone. Instead, it is a description of the date, as used for birthdays and anniversaries.
To create an instance of current local date from the system clock, you can use
LocalDate.now() static method:
LocalDate now = LocalDate.now();
To create an instance of
LocalDate for a specific day, month, and year, you can use
LocalDate localDate = LocalDate.of(2019, 12, 20);
To convert a string representation of ISO-8601 format to date, you can use
LocalDate localDate = LocalDate.parse("2019-12-20");
LocalDate class offers various utility methods that can be used to access different types of information. For example, the following code snippet shows how you can get the current local date and then add or subtract days:
LocalDate tomorrow = LocalDate.now().plusDays(1); LocalDate yesterday = LocalDate.now().minusDays(1);
Just like days, you can also add or minus months and years to an instance of
LocalDate lastMonth = LocalDate.now().minusMonths(1); // OR LocalDate lastMonth = LocalDate.now().minus(1, ChronoUnit.MINUTES);
Here is another example that shows how you can obtain the day of the week and the day of the month from an instance of
// get day of week DayOfWeek day = LocalDate.now().getDayOfWeek(); // get day of month int month = LocalDate.parse("2020-01-12").getDayOfMonth();
To check if the current year is leap year, you can do the following:
boolean isLeap = LocalDate.now().isLeapYear();
You can also compare two local dates by using
isAfter() methods as shown below:
// create local dates LocalDate date1 = LocalDate.parse("2019-08-02"); LocalDate date2 = LocalDate.parse("2018-01-45"); // check if `date1` is before `date2` boolean isBefore = date1.isBefore(date2); // check if `date1` is after `date2` boolean isAfter = date1.isAfter(date2);
You can use
LocalDate to obtain different date boundaries such as the first day of the month, the start of the day (returns a
LocalDateTime instance), and more. Here is an example:
// get start of the day LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); // get first day of the month LocalDate firstDayOfMonth = LocalDate.parse("2019-12-18") .with(TemporalAdjusters.firstDayOfMonth());
LocalTime class represents a time without any date or timezone information in the ISO-8601 calendar system. This class does not store or represent a date or time-zone. Instead, it is a description of the local time as seen on a wall clock.
LocalDate, you can use create an instance of
LocalTime from the system clock, parse a string, or specify time units manually:
// use system clock LocalTime now = LocalTime.now(); // parse a string LocalTime parseTime = LocalTime.parse("02:08"); // specify hour-minute-second - 08:45:20 AM LocalTime specificTime = LocalTime.of(8, 45, 20);
Similarly, you can use also add or minus hours, minutes, seconds, or even nanoseconds to a given
// past hour LocalTime pastHour = LocalTime.now().minusHours(1); // add minutes - can be replaced with `plusMinutes()` LocalTime addMinutes = LocalTime.now().plus(15, ChronoUnit.MINUTES);
LocalTime class provides various utility methods to get different units of the time like hours, minutes, seconds or nanoseconds:
int hour = LocalTime.now().getHour(); int minutes = LocalTime.now().getMinute(); int seconds = LocalTime.now().getSecond(); int nanoseconds = LocalTime.now().getNano();
You can also compare two local times to check if a given local time is after or before another local time as shown below:
// create local times LocalTime time1 = LocalTime.parse("05:15"); LocalTime time2 = LocalTime.parse("21:00"); // is `time1` before `time2`? boolean isBefore = time1.isBefore(time2); // is `time1` after `time2`? boolean isAfter = time1.isAfter(time2);
LocalTime also provides various constants to get maximum, minimum, or noon time of a day as shown below:
// get max time (23:59:59.999999999) LocalTime max = LocalTime.MAX; // get min time (00:00) LocalTime min = LocalTime.MIN; // the time of noon (12:00) LocalTime noon = LocalTime.NOON; // time of midnight (00:00) LocalTime midnight = LocalTime.MIDNIGHT;
LocalDateTime class is used to represent both local date and time without timezone information. It is a description of the date, as used for birthdays, combined with the local time as seen on a wall clock.
LocalDateTime is the most commonly used class of Java 8 new data and time API for handling both date and time together. This class provides a range of utility methods for various date and time operations.
To create a new instance of
LocalDateTime, you can either use the system clock, parse a string, or manually define date and time units as shown below:
// use system clock LocalDateTime now = LocalDateTime.now(); // parse a string LocalDateTime parseDT = LocalDateTime.parse("2019-08-02T15:20"); // specify data and time units LocalDateTime specificDT = LocalDateTime.of(2019, Month.AUGUST, 2, 15, 20);
LocalTime, you can add or subtract different date and time units from
LocalDateTime as shown below:
// yesterday LocalDateTime yesterday = LocalDateTime.now().minusDays(1); // substract one year from a specific date and time LocalDateTime lastYear = LocalDateTime.parse("2019-08-02T15:20") .minus(1, ChronoUnit.YEARS); // add 4 hours to the current date and time LocalDateTime nextHour = LocalDateTime.now().plusHours(4);
LocalDateTime also provides getter methods to extract specific date and time units similar to individual local date and time classes. The following example shows how you can get the month and hour units from an instance of
Month month = LocalDateTime.now().getMonth(); int hour = LocalDateTime.now().getHour();
You can also compare two
LocalDateTime instances to check if a given instance is after or before the other instance:
// create local date and times LocalDateTime dt1 = LocalDateTime.parse("2019-08-08T05:10"); LocalDateTime dt2 = LocalDateTime.parse("2019-10-29T23:45"); // check if `dt1` is after `dt2` boolean isAfter = dt1.isAfter(dt2); // check if `dt1` is before `dt2` boolean isBefore = dt1.isBefore(dt2);
Zoned Date & Time API
ZoneId classed were added to Java 8 new date and time API to deal with situations where you can need the timezone information.
ZoneId is an identifier that is used to identity different timezones. There are around 40 different timezones provided by
ZoneId. Here is how you can create a zone for Paris and Karachi:
ZoneId paris = ZoneId.of("Europe/Paris"); ZoneId karachi = ZoneId.of("Asia/Karachi");
To get all available zones, you can do the following:
Set<String> zones = ZoneId.getAvailableZoneIds();
ZonedDateTime class represents a date-time with a timezone in the ISO-8601 format (e.g. 2019-12-20T10:15:30+05:00 Asia/Karachi). It stores all date and time fields, to a precision of nanoseconds, and a timezone, with a zone offset used to handle ambiguous local date-times.
To obtain the current date-time from the system clock in the default timezone, you can use
ZonedDateTime.now() as shown below:
ZonedDateTime now = ZonedDateTime.now();
An instance of
LocalDateTime can also be converted to a specific zone to create a new
ZonedDateTime zonedDT = ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("Europe/Paris"));
ZonedDateTime class also provides
parse() method to create a zoned date and time instance of a string as shown below:
ZonedDateTime zonedDT = ZonedDateTime.parse("2019-12-20T10:15:30+05:00[Asia/Karachi]");
LocalDateTime, you can also add or subtract different date and time units from
// add 2 months to a specific zoned date and time ZonedDateTime addMonths = ZonedDateTime.parse("2019-12-20T10:15:30+05:00[Asia/Karachi]") .plusMonths(2); // substract one year from now ZonedDateTime lastYear = ZonedDateTime.now().minus(1, ChronoUnit.YEARS);
Similarly, you can also get different date and time units using
ZonedDateTime getter methods:
Month month = ZonedDateTime.now().getMonth(); int year = ZonedDateTime.now().getYear();
You can also compare two
ZonedDateTime instances to check if a given instance is after or before the other instance:
// create zoned date and times ZonedDateTime dt1 = ZonedDateTime.parse("2019-12-20T10:15:30+01:00[Europe/Paris]"); ZonedDateTime dt2 = ZonedDateTime.parse("2017-03-05T02:45:15+00:00[Europe/London]"); // check if `dt1` is after `dt2` boolean isAfter = dt1.isAfter(dt2); // check if `dt1` is before `dt2` boolean isBefore = dt1.isBefore(dt2);
Another way to deal with zoned date and time is by using the
ZonedOffset is an extension of
ZoneId that stores the amount of time that a timezone differs from Greenwich/UTC. This is usually a fixed number of hours and minutes. Here is an example of
ZonedOffset that adds 4 hours to the UTC time:
ZoneOffset offset = ZoneOffset.of("+04:00");
OffsetDateTime class represents a date and time with an offset from UTC/Greenwich in the ISO-8601 calendar system such as 2019-12-03T10:15:30+01:00. This class stores all date and time fields, to a precision of nanoseconds, as well as the offset from UTC/Greenwich.
To create a new instance of
OffsetDateTime, you can either use the system clock, parse a string, or convert local data time to zoned date time with
// use system clock OffsetDateTime now = OffsetDateTime.now(); // use local date and time with `ZonedOffset` ZoneOffset offset = ZoneOffset.of("+04:00"); LocalDateTime localDateTime = LocalDateTime.of(2019, Month.JULY, 17, 11, 30); OffsetDateTime offsetDT = OffsetDateTime.of(localDateTime, offset); // parse a string OffsetDateTime parseOffsetDT = OffsetDateTime.parse("2019-12-03T10:15:30+01:00");
ZonedDateTime, you can use utility methods from
OffsetDateTime to get different units, add or subtract date and time units, and to perform a comparison between two instances.
Instant class is used to represents a specific moment on the time line. It is defined as an offset from the first second of January 1, 1970 UTC/Greenwich (1970-01-01 00:00:00) also called an EPOCH. Time is measured using 86,400 seconds per day, going forward from the origin.
To create an instance of
Instant API, you can either use the system clock or parse UNIX timestamp or a date string:
// use system clock Instant now = Instant.now(); // parse ISO 8601 string Instant parseISO = Instant.parse("2019-08-08T12:58:00Z"); // parse UNIX timestamp - 2019-09-04T01:54:18Z Instant parseUnix = Instant.ofEpochSecond(1567562058); // same time in milliseconds Instant parseMillis = Instant.ofEpochMilli(1567562058000L);
To convert an instance of
Instant to EPOCH seconds or milliseconds, you can do the following:
Instant instant = Instant.parse("2019-12-08T12:58:00Z"); // epoch seconds - 1575809880 long epochSeconds = instant.getEpochSecond(); // epoch milliseconds - 1575809880000 long epochMillis = instant.toEpochMilli();
Instant class also provides several utility methods to make calculations relative to an
// add 10 seconds Instant addSeconds = Instant.now().plusSeconds(10); // minus milliseconds Instant minusMillis = Instant.now().minus(500, ChronoUnit.MILLIS);
Period and Duration API
Java 8 new date and time API introduced two classes (
Duration) to work with the date and time differences.
Period class represents an amount of time in terms of years, months, and days. It is commonly used to modify an existing date object values as well as to get the difference between two dates.
The following example demonstrates how you can use the
Period class to manipulate an instance of
// future local date LocalDate futureDate = LocalDate.now().plus(Period.ofDays(5)); // past local date LocalDate pastDate = LocalDate.now().minus(Period.ofWeeks(1)); // period of specific months and days LocalDate specificDate = LocalDate.now().plus(Period.ofMonths(2).plusDays(15));
Similarly, you can also use the
Period class to get the difference between two dates:
LocalDate firstDate = LocalDate.of(2018, Month.JULY, 5); // 2018-07-05 LocalDate secondDate = LocalDate.of(2019, Month.DECEMBER, 20); // 2019-12-20 // difference between two dates Period period = Period.between(firstDate, secondDate); int days = period.getDays(); // 15 int months = period.getMonths(); // 5 int years = period.getYears(); // 1 boolean isNegative = period.isNegative(); // false boolean isZero = period.isZero(); // false
Alternatively, you can use the
ChronoUnit class to obtain the period between two dates in a specific unit such as days or month or years:
long days = ChronoUnit.DAYS.between(firstDate, secondDate); // 533 long months = ChronoUnit.MONTHS.between(firstDate, secondDate); // 17 long years = ChronoUnit.YEARS.between(firstDate, secondDate); // 1
Duration class represents an amount of time in terms of days, hours, seconds, milliseconds, and nanoseconds. It can be used to modify existing time values as well as to get the difference between two times.
// future local time LocalTime futureTime = LocalTime.now().plus(Duration.ofHours(5)); // past local time LocalTime pastTime = LocalTime.now().minus(Duration.ofDays(1)); // duration of specific days and hours LocalTime specificTime = LocalTime.now().plus(Duration.ofDays(7).plusHours(3));
Here is another example that shows how you can create a
LocalTime of 11:15 AM and then add 40 seconds to make it 11:15:40 AM:
LocalTime time = LocalTime.of(11, 15); // 11:15 // add duration to local time LocalTime newTime = time.plus(Duration.ofSeconds(40)); // 11:15:40
Duration between two times can be obtained either as an instance of
Duration or as a specific unit. Here is an example that uses the
between() method from
Duration to get the difference between two times:
LocalTime firstTime = LocalTime.of(11, 30); // 11:30 LocalTime secondTime = LocalTime.of(22, 35); // 22:35 // get difference between times Duration duration = Duration.between(firstTime, secondTime); long millis = duration.toMillis(); // 39900000 long seconds = duration.getSeconds(); // 39900 long minutes = duration.toMinutes(); // 665 long hours = duration.toHours(); // 11
Here is an another example that uses the
between() method from
ChronoUnit to get the duration between two times in specific units:
long seconds = ChronoUnit.SECONDS.between(firstTime, secondTime); // 39900 long minutes = ChronoUnit.MINUTES.between(firstTime, secondTime); // 665 long hours = ChronoUnit.HOURS.between(firstTime, secondTime); // 11
Chrono Units Enum
ChronoUnit enum was added to Java 8 new date and time API to represent individual date and time units such as day, month, year, week, hour, minutes, and more. It replaces the integer values used in the old API to represent these units.
Here is an example that demonstrates how you can use
ChronoUnit for different tasks:
// the current date LocalDate today = LocalDate.now(); System.out.println("Current Date: " + today); // next week LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS); System.out.println("Next Week: " + nextWeek); // next month LocalDate nextMonth = today.plus(1, ChronoUnit.MONTHS); System.out.println("Next Month: " + nextMonth); // next year LocalDate nextYear = today.plus(1, ChronoUnit.YEARS); System.out.println("Next Year: " + nextYear); // next decade LocalDate nextDecade = today.plus(1, ChronoUnit.DECADES); System.out.println("Next Decade: " + nextDecade); // next century LocalDate nextCentury = today.plus(1, ChronoUnit.CENTURIES); System.out.println("Next Century: " + nextCentury);
The above code will produce the following output:
Current Date: 2019-12-19 Next Week: 2019-12-26 Next Month: 2020-01-19 Next Year: 2020-12-19 Next Decade: 2029-12-19 Next Century: 2119-12-19
Temporal adjusters are highly useful for performing different calculations on date and time. You can use them to modify temporal objects and answer questions like find the next Friday, find the second Tuesday of the month, etc.
Java 8 introduced
TemporalAdjusters class for this purpose. Here is how you can use it:
// the current date LocalDate today = LocalDate.now(); System.out.println("Current date: " + today); // first day of month LocalDate firstDay = today.with(TemporalAdjusters.firstDayOfMonth()); System.out.println("First day of month: " + firstDay); // last day of month LocalDate lastDay = today.with(TemporalAdjusters.lastDayOfMonth()); System.out.println("Last day of month: " + lastDay); // next Tuesday LocalDate nextTuesday = today.with(TemporalAdjusters.next(DayOfWeek.TUESDAY)); System.out.println("Next Tuesday: " + nextTuesday); // first day of next month LocalDate firstDayOfNextMonth = today.with(TemporalAdjusters.firstDayOfNextMonth()); System.out.println("First day of next month: " + firstDayOfNextMonth); // last day of year LocalDate lastDayofYear = today.with(TemporalAdjusters.lastDayOfYear()); System.out.println("Last day of year: " + lastDayofYear); // first day of year LocalDate firstDayOfYear = today.with(TemporalAdjusters.firstDayOfYear()); System.out.println("First day of year: " + firstDayOfYear); // last Friday of month LocalDate lastFridayOfMonth = today.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)); System.out.println("Last Sunday of month: " + lastFridayOfMonth);
You should see something like below printed on the console:
Current date: 2019-12-19 First day of month: 2019-12-01 Last day of month: 2019-12-31 Next Tuesday: 2019-12-24 First day of next month: 2020-01-01 Last day of year: 2019-12-31 First day of year: 2019-01-01 Last Sunday of month: 2019-12-27
Date and Time Formatting
Just like the
parse() method to convert a string to an instance of date and time, Java 8 new date and time API also provides the
format() method for formatting the output.
The below example shows how you can format an instance of
LocalDateTime in ISO date format (yyyy-MM-dd):
LocalDateTime date = LocalDateTime.of(2019, Month.DECEMBER, 18, 9, 45); // ISO date format (yyyy-MM-dd) - 2019-12-18 String isoDate = date.format(DateTimeFormatter.ISO_DATE);
DateTimeFormatter class provides various standard date and time formatting options. Here is an another example of formatting a local date and time to ISO 8601 format:
// ISO date and time format - 2019-12-18T09:45:00 String isoDateTime = date.format(DateTimeFormatter.ISO_DATE_TIME);
Custom formatting patterns can also be provided to
format() method as shown below:
// custom format pattern (MM/dd/yyyy HH:mm a) - 12/18/2019 09:45 AM String customFormat = date.format(DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm a"));
We can also pass in the formatting style like
LONG as part of the formatting pattern as shown below:
// formatting style MEDIUM - Dec 18, 2019 9:45:00 AM String customFormat = date.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) .withLocale(Locale.US));
Java 8 has added
toInstant() method to the legacy
Calendar classes. It can be used to convert a
Calendar instance to a new date and time API instance.
The following example shows how you can use
ofInstant() to convert a legacy
Date object to a
// current date and time Date date = new Date(); System.out.println("Current date: " + date); // get the Instant of current date (in millis) Instant now = date.toInstant(); // convert instant to local date/time LocalDateTime localDT = LocalDateTime.ofInstant(now, ZoneId.systemDefault()); System.out.println("Local date: " + localDT); // convert instant to zoned date/time ZonedDateTime zonedDT = ZonedDateTime.ofInstant(now, ZoneId.systemDefault()); System.out.println("Zoned date: " + zonedDT);
Here is the output of the above code snippet:
Current date: Thu Dec 19 14:37:38 PKT 2019 Local date: 2019-12-19T14:37:38.606 Zoned date: 2019-12-19T14:37:38.606+05:00[Asia/Karachi]
In this in-depth article, we looked at Java 8 new date and time API in detail with examples. Java 8 new date and time APIs are not only thread-safe but also developer-friendly with easier-to-use utility methods and far better zoned date-time handling.
Here is a short summary of the new date and time APIs we have discussed above:
- Local Date & Time API: Local date and time API is intended to use for local date and time operations when you no longer required the timezone information.
LocalDateTimeclasses are a part of the local API.
- Zoned Date & Time API: Java 8 introduced
OffsetDateTimeclasses along with
ZonedOffsetidentifiers for handling date and time with timezone information.
- Instant API: The
instantclass is added to Java 8 new date and time API to represent a specific moment on the timeline.
- Period & Duration API: To work with date and time differences, Java 8 new date and time API introduced
Durationclasses. These classes can be used to represent an amount of time in terms of different units like days, months, years, hours, minutes, etc.
- Chrono Units Enum: The
ChronoUnitenum replaced the legacy integer values that were in old API to represent different date and time units.
- Temporal Adjusters: Temporal adjusters are another useful feature to perform date and time calculations in Java 8 and higher.
- Date and Time Formatting: To format the date and time output, Java 8 new date and time API introduced
- Backward Compatibility: To keep the new date and time API backward compatible, Java 8 has added
toInstant()method to existing
You may be interested in reading the following Java date and time tutorials:
- How to get current date and time in Java
- How to convert a string to date in Java
- How to compare dates in Java
- How to format a date to string in Java
- Calculate difference between two dates in Java
👋 If you enjoy reading my articles and want to support me to continue creating free tutorials, ☕ Buy me a coffee (cost $3) .
Need help to launch a new product? I am available for contract work. Hire me to accomplish your business goals with engineering and design.