Java 8 Date and Time API
This article will guide you on how to use of one of the newest APIs in Java 8 which solved many problems its predecessor APIs introduced. This new API address the shortcomings of the older java.util.Date and java.util.Calendar APIs.
To start, we will be looking at some basic code samples to offer its usage, followed by the issues and bugs which were present in older APIs and to end, we will be looking at a short guide on how to migrate from an older API to a new one.
Overview of the new API
Working with Date API in Java used to be very cumbersome. The old date library provided included three classes: java.util.Date, java.util.Calendar and java.util.Timezone.
These were suitable for just the most trivial tasks. For anything even nearly tricky, the developers had to either go for third-party libraries or write a lot of custom code.
Java 8 introduced a completely new Date Time API (java.util.time.*) that is loosely based on the popular Java library called JodaTime. This new API dramatically simplified date and time processing and fixed many shortcomings of the old date library.
Terminology in Date API
Here, let’s look at some terms in the new API.
- Instant : Represents a point in time. A timestamp.
- LocalDate – Represents a date (in terms of a year, month, day)
- LocalDateTime – Similar to LocalDate, but includes time with nanosecond precision
- OffsetDateTime – Similar to LocalDateTime, but with time zone offset
- LocalTime – Time with nanosecond precision and with no Date information
- ZonedDateTime – Similar to OffsetDateTime, but with a Time zone ID
- OffsetLocalTime – Similar to LocalTime, but with time zone offset
- MonthDay – With Month and day and without year or time
- YearMonth – With Month and year and without day or time
- Duration – Amount of time represented in seconds, minutes and hours. Has nanosecond precision
- Period – Amount of time represented in days, months and years
Date API in Java 8
The following examples cover the most important parts of this new API.
Clock
Clock provides access to the current date and time. Clocks are aware of a timezone and can be used in place of System.currentTimeMillis() to retrieve the current milliseconds. Such an instantaneous point on the time-line is also represented by the class Instant. Instants can be used to create legacy java.util.Date objects.
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
System.out.format("millis : %d \n", millis);
System.out.println("ZoneID : " + clock.getZone());
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // legacy java.util.Date
System.out.format("legacyDate millis : %d", legacyDate.getTime());
Timezones
ZoneId represents each Timezone. They can be accessed by static factory methods. Timezones define the offsets which are necessary to convert between instants and local dates and times. Let’s look at a sample code below:
// prints all available timezone ids
System.out.println(ZoneId.getAvailableZoneIds());
ZoneId zone1 = ZoneId.of("Asia/Kolkata");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
LocalTime
LocalTime represents a time without a timezone, e.g. 1pm or 23:30:16. The following example creates two local times for the time zones defined above. Then we compare both times and calculate the difference in hours and minutes between both times. Here is the fragment:
ZoneId zone1 = ZoneId.of("Asia/Kolkata");
ZoneId zone2 = ZoneId.of("Brazil/East");
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2)); // false
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hoursBetween); //-7
System.out.println(minutesBetween); //-449
LocalTime comes with various factory methods to simplify the creation of new instances, including parsing of time strings.
LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late); // 23:59:59
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.GERMAN);
LocalTime localTime = LocalTime.parse("11:37", germanFormatter);
System.out.println(localTime); // 11:37
LocalDate
LocalDate represents a distinct date, e.g. 2014-03-11. It's immutable and works exactly analog to LocalTime. The sample code below demonstrates how to calculate new dates by adding or subtracting days, months or years. Keep in mind that each manipulation returns a new instance and not just the value.
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);
LocalDate independenceDay = LocalDate.of(2016, Month.DECEMBER, 25);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println("dayOfWeek = " + dayOfWeek); //SUNDAY
Parsing a LocalDate from a String is just as easy as parsing a LocalTime:
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("25.12.2016", germanFormatter);
System.out.println(xmas); // 2016-12-25
LocalDateTime
LocalDateTime class represents a date-time. It combines date and time as seen in the above sections into one instance. LocalDateTime is immutable and works similar to LocalTime and LocalDate. We can utilize methods for retrieving certain fields from a date-time:
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // WEDNESDAY
Month month = sylvester.getMonth();
System.out.println(month); // DECEMBER
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
With the additional information of a timezone it can be converted to an instant. Instants can easily be converted to legacy dates of type java.util.Date.
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
Formatting date-times works just like formatting dates or times. Instead of using pre-defined formats we can create formatters from custom patterns.
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy - HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 - 07:13
Unlike java.text.NumberFormat the new DateTimeFormatter is immutable and thread-safe.
Periods
A Period represents a value such as “2 months and 3 days,” which is a distance on the timeline. This is in contrast to the other classes we’ve looked at so far, which have been points on the timeline.
Period period = Period.of(3, 2, 1);
// You can modify the values of dates using periods
LocalDate newDate = oldDate.plus(period);
ZonedDateTime newDateTime = oldDateTime.minus(period);
// Components of a Period are represented by ChronoUnit values
assertEquals(1, period.get(ChronoUnit.DAYS));
Migrating to the New API
This is one of the most important topics to be discussed here. Let’s look at differences in the new API in comparison to the older API.
Getting current Time
// Old
Date now = new Date();
// New
ZonedDateTime newNow = ZonedDateTime.now();
Representing Specific Time
// Old
Date birthDay = new GregorianCalendar(1990, Calendar.DECEMBER, 15).getTime();
// New
LocalDate newBbirthDay = LocalDate.of(1990, Month.DECEMBER, 15);
Adding and Subtracting Time
// Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.add(Calendar.HOUR_OF_DAY, -6);
Date sixHoursBefore = calendar.getTime();
// New
LocalDateTime newSixHoursBefore = LocalDateTime.now().minusHours(6);
Extracting specific fields
// Old
int oldMonth = new GregorianCalendar().get(Calendar.MONTH);
// New
Month newMonth = LocalDateTime.now().getMonth();
Truncating
// Old
Calendar oldNow = Calendar.getInstance();
oldNow.set(Calendar.MINUTE, 0);
oldNow.set(Calendar.SECOND, 0);
oldNow.set(Calendar.MILLISECOND, 0);
Date truncatedOld = oldNow.getTime();
// New
LocalTime truncatedNew = LocalTime.now().truncatedTo(ChronoUnit.HOURS);
Features of new Date/Time APIs
New API solved many problems and made life easier already. Let’s look at some core features it brings to the table:
- New API is very clear – the API is very clear, concise and easy to understand. It does not have a lot of inconsistencies found in the old library such as the field numbering (in Calendar months are zero-based, but days of the week are one-based).
- Calendar.getInstance() is not very well named: it is hard to tell which instance you are getting without reading the Javadoc. LocalTime.now() is quite self-describing: you get a time and it is now.
- To offset a date, you call an offsetting method (plus) whereas with the Calendar API, you have to manually change the fields of the object (in this example, the hour) which is error prone.
- Another feature of the library is its flexibility – working with multiple representations of time. The old date library included only a single time representation class – java.util.Date, which despite its name, is actually a timestamp. It only stores the number of milliseconds elapsed since the Unix epoch.
- Yet another feature is that all time representations in Java 8 Date Time API are immutable and thus thread-safe. All mutating methods return a new copy instead of modifying the state of the original object.
- The Calendar API is not safe: nothing prevents you from writing cal.set(123, 2) which would throw a not-so-helpful ArrayOutOfBoundsException. The new API uses enums which solves that problem.
- The new API uses immutable objects, which makes it thread safe.
Better than JodaTime
Joda-Time often uses NULL as a default for system timezone, default locale, current timestamp etc. while JSR-310 almost always rejects NULL values.
JSR-310 handles nanosecond precision while Joda-Time is limited to millisecond precision.
JSR-310 offers enums like DayOfWeek or Month while Joda-Time does not offer this because it was mainly developed in years 2002-2004 before Java 5.
Conclusion
In this guide, we walked through the new Date Time API available in Java 8. We looked at its features, compared to the older API and pointed out differences using multiple examples.
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment