java8 - Date-Time package
- Date-Time Package
- Features of the new Date and Time API in Java8
- Important packages in the new Data and Time API
- Instant
- LocalDate, LocalTime, and LocalDateTime APIs
- OffsetDateTime
- ZonedDateTime
- Java LocalDateTime vs ZonedDateTime
- OffsetDateTime vs LocalDateTime
- OffsetDateTime vs ZonedDateTime
- Should we use LocalDateTime or OffsetDateTime or ZonedDateTime in API contracts?
- Reading material
Date-Time Package
a new set of packages that provide a comprehensive date-time model.
Date Time API | New improved joda-time inspired APIs to overcome the drawbacks in previous versions |
Features of the new Date and Time API in Java8
- Immutable classes and Thread-safe
- Timezone support
- Fluent methods for object creation and arithmetic
- Addresses I18N issue for earlier APIs
- Influenced by popular joda-time package
- All packages are based on the ISO-8601 calendar system
Important packages in the new Data and Time API
- java.time
- dates
- times
- Instants
- durations
- time-zones
- periods
- Java.time.format
- Java.time.temporal
- java.time.zone
Source: https://docs.oracle.com/javase/8/docs/api/java/time/OffsetDateTime.html
OffsetDateTime, ZonedDateTime and Instant all store an instant on the time-line to nanosecond precision. Instant is the simplest, simply representing the instant. OffsetDateTime adds to the instant the offset from UTC/Greenwich, which allows the local date-time to be obtained. ZonedDateTime adds full time-zone rules.
Instant
LocalDate, LocalTime, and LocalDateTime APIs
LocalDate
- Date with no time component
- Default format - yyyy-MM-dd (2020-02-20)
- LocalDate today = LocalDate.now(); // gives today’s date
- LocalDate aDate = LocalDate.of(2011, 12, 30); //(year, month, date)
LocalTime
- Time with no date with nanosecond precision
- Default format - hh:mm:ss:zzz (12:06:03.015) nanosecond is optional
- LocalTime now = LocalTime.now(); // gives time now
- LocalTime aTime2 = LocalTime.of(18, 20, 30); // (hours, min, sec)
LocalDateTime
- Holds both Date and Time
- Default format - yyyy-MM-dd-HH-mm-ss.zzz (2020-02-20T12:06:03.015)
- LocalDateTime timestamp = LocalDateTime.now(); // gives timestamp now
- //(year, month, date, hours, min, sec)
- LocalDateTime dt1 = LocalDateTime.of(2011, 12, 30, 18, 20, 30);
OffsetDateTime
OffsetDateTime
represents a date-time with an offset. This class stores all date and time fields, to a precision of nanoseconds, as well as the offset from UTC/Greenwich. For example, the value 2nd December 2018 at 15:35.40.123456789 +03:00
can be stored in an OffsetDateTime.
OffsetDateTime should be used when writing date to database. Why?
Reason 1
Dates with local time offsets always represent the same instants in time, and therefore have a stable ordering. By contrast, the meaning of dates with full timezone information is unstable in the face of adjustments to the rules for the respective timezones. (And these do happen; e.g. for date-time values in the future.) So if you store and then retrieve a ZonedDateTime the implementation has a problem:
- It can store the computed offset … and the retrieved object may then have an offset that is inconsistent with the current rules for the zone-id.
- It can discard the computed offset … and the retrieved object then represents a different point in the absolute / universal timeline than the one that was stored.
If you use Java object serialization, the Java 9 implementation takes the first approach. This is arguably the “more correct” way to handle this, but this doesn’t appear to be documented. (JDBC drivers and ORM bindings are presumably making similar decisions, and are hopefully getting it right.)
But if you are writing an application that manually stores date/time values, or that rely on java.sql.DateTime, then dealing with the complications of a zone-id is … probably something to be avoided. Hence the advice.
Note that dates whose meaning / ordering is unstable over time may be problematic for an application. And since changes to zone rules are an edge case, the problems are liable to emerge at unexpected times.
Reason 2
A (possible) second reason for the advice is that the construction of a ZonedDateTime is ambiguous at the certain points. For example in the period in time when you are “putting the clocks back”, combining a local time and a zone-id can give you two different offsets. The ZonedDateTime will consistently pick one over the other … but this isn’t always the correct choice.
Now, this could be a problem for any applications that construct ZonedDateTime values that way. But from the perspective of someone building an enterprise application is a bigger problem when the (possibly incorrect) ZonedDateTime values are persistent and used later.
ZonedDateTime
ZonedDateTime
represents a date-time with a time-zone. An example is 2007-12-03T10:15:30+01:00 Europe/Paris
Java LocalDateTime vs ZonedDateTime
LocalDateTime will give you local date time shown on your system clock but it will not capture any timezone information.
LocalDateTime localDateTime = LocalDateTime.now();
Convert LocalDateTime to ZonedDateTime
We need to add zone id information to the LocalDateTime object, to get the ZonedDateTime object.
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Australia/Sydney"));
LocalDateTime localDateTime = LocalDateTime.of(2022, 1, 1, 0, 30, 22);
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Africa/Lagos")).withZoneSameInstant(ZoneId.of("Canada/Atlantic"));
Convert Zoneddatetime to LocalDateTime
ZonedDateTime zonedDateTime = ZonedDateTime.of(2011, 2, 12, 6, 14, 1, 58086000, ZoneId.of("Asia/Tokyo"));
LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
assertEquals("2011-02-12T06:14:01.058086+09:00[Asia/Tokyo]", zonedDateTime.toString());
OffsetDateTime vs LocalDateTime
Convert OffsetDateTime to LocalDateTime
public void convertToLocalDateTime() {
OffsetDateTime offsetDateTime = OffsetDateTime.now();
LocalDateTime localDateTime = offsetDateTime.toLocalDateTime();
LocalDate localDate=offsetDateTime.toLocalDate();
LocalTime localTime=offsetDateTime.toLocalTime();
// This totally ignores time zones as toLocalDateTime of OffsetDateTime just strips time zone information.
// If the OffsetTimeZone is GMT and local time zone is GMT+2, 15:19:47 will still be 15:19:47 in LocalDateTime, not 17:19:47, as would be what you’d want most of the time.
// It’s more of a cast than a convert.
// Instead of offsetDateTime.toLocalDateTime();, use offsetDateTime..atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
LocalDateTime localDateTime = offsetDateTime.atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
}
OffsetDateTime vs ZonedDateTime
The difference between OffsetDateTime
and ZonedDateTime
is that the latter includes the rules that cover daylight saving time adjustments and various other anomalies.
Stated simply:
Time Zone = ( Offset-From-UTC + Rules-For-Anomalies )
As ZonedDateTime
and an OffsetDateTime
refer to the same instant, you might end up using them interchangeably. However, they are not interchangeable. One important thing to remember is that writing a ZonedDateTime
to an ANSI SQL database will lose information because ANSI SQL only supports OffsetDateTimes
.
Also, ZonedDateTime
is now more usable as an alternative to OffsetDateTime
if you want to keep it simple.
So if you need to store data and time, use OffsetDateTime
and to present date and time to users, use the ZonedDataTime
.
Instant instant = Instant.now();
Clock clock = Clock.fixed(instant, ZoneId.of("America/New_York"));
OffsetDateTime offsetDateTime = OffsetDateTime.now(clock);
ZonedDateTime zonedDateTime = ZonedDateTime.now(clock);
System.out.println(offsetDateTime); // 2019-01-03T19:10:16.806-05:00
System.out.println(zonedDateTime); // 2019-01-03T19:10:16.806-05:00[America/New_York]
System.out.println();
OffsetDateTime offsetPlusSixMonths = offsetDateTime.plusMonths(6);
ZonedDateTime zonedDateTimePlusSixMonths = zonedDateTime.plusMonths(6);
System.out.println(offsetPlusSixMonths); // 2019-07-03T19:10:16.806-05:00
System.out.println(zonedDateTimePlusSixMonths); // 2019-07-03T19:10:16.806-04:00[America/New_York]
System.out.println(zonedDateTimePlusSixMonths.toEpochSecond() - offsetPlusSixMonths.toEpochSecond()); // -3600
System.out.println();
System.out.println(zonedDateTimePlusSixMonths.toLocalDateTime()); // 2019-07-03T19:10:16.806
System.out.println(offsetPlusSixMonths.toLocalDateTime()); // 2019-07-03T19:10:16.806
Use ZonedDateTime only if you want to factor in Daylight saving, typically there will be one hour difference, as you can see the example above, the offset of ZonedDateTime change from -5:00 to -04:00, in most case, your business logic might end up with bug.
When we format message for users or read their input we use ZonedDateTime & ZoneId. When we store a time event to DB we convert to UTC, OffsetDateTime is basically an instant in time. UTC dates are easy comparable (avoiding TZ info lookups).
There is debate about whether using OffsetDateTime is the correct one to be used while saving in database.
The opposite way: the correct one to use is usually either Instant or ZonedDateTime. IMO OffsetDateTime only exist because many existing systems (wrongly) treated “time offset” to be sufficient information about timezones (i.e. they communicate/store/handle just an offset when they should be communicating a time zone name).
Convert OffsetDateTime To ZonedDateTime
Approach 1
- OffsetDateTime.now() gets the current date-time in the default time-zone with an offset.
- Then offsetDateTime.toZonedDateTime() converts the current date-time with offset to a ZonedDateTime using the offset as the zone ID.
public ZonedDateTime convertToZonedDateTime() {
OffsetDateTime offsetDateTime = OffsetDateTime.now();
ZonedDateTime zonedDateTime1 = offsetDateTime.toZonedDateTime();
System.out.println(zonedDateTime1);
return zonedDateTime1;
}
Approach 2
Convert OffsetDateTime to atZone Same Instant.
- Obtain the zone id that represents the time-zone of Asia/Kolkata.
- Next, the method atZoneSameInstant(zoneId) combines the current date and time with the zone ID to create a new instance of ZonedDateTime. This method ensures that the result has the same instant.
ZoneId zoneId = ZoneId.of("Asia/Kolkata");
public ZonedDateTime convertToAtZoneSameInstant() {
OffsetDateTime offsetDateTime = OffsetDateTime.now();
ZonedDateTime zonedDateTime2 = offsetDateTime.atZoneSameInstant(zoneId);
System.out.println(zonedDateTime2);
return zonedDateTime2;
}
Approach 3
Call the atZoneSimilarLocal(zoneId) method of OffsetDateTime to perform the conversion.
The atZoneSimilarLocal(zoneId)
method is similar to the atZoneSameInstant(zoneId)
method. The difference is that instead of adjusting the time/date to the target zone, his method keeps the same local date and time.
public ZonedDateTime convertToAtZoneSimilarLocal() {
OffsetDateTime offsetDateTime = OffsetDateTime.now();
ZonedDateTime zonedDateTime3 = offsetDateTime.atZoneSimilarLocal(zoneId);
System.out.println(zonedDateTime3);
return zonedDateTime3;
}
Should we use LocalDateTime or OffsetDateTime or ZonedDateTime in API contracts?
Look at the differences between them above. It looks like ZonedDateTime might be a better fit for it.