The Local* classes that we looked at last time all ignored the complexity introduced by timezones. A timezone is a set of rules, corresponding to a region where the standard time is the same, there are about 40 of them. Each timezone has an offset from UTC, so it's time moves in sync with UTC but by some difference. Unfortunately the defined offset from UTC can vary due to changes in the rules. Timezones have two identifiers: abbreviated, eg "PLT", and longer, eg: "Asia/Karachi".
A time-zone offset is the period of time that a time-zone differs from Greenwich/UTC. Offsets can be resolved from a timezone at a particular point in time. For example in summer Paris is Greenwich/UTC + one hour. In winter it's Greenwich/UTC + two hours. You don't need to be aware of all the details, since JSR-310 abstracts these away for you, but you do need to understand the basic concept that there isn't a one-to-one mapping between offsets and timezones. When designing your application you should consider what scenarios are appropriate for using timezones and when offsets are appropriate.
There are a number of classes representing parts of the timezone system. These classes are all field-based classes, which is to say they can be queried for subfields, such as a Day, a Second or a ZoneOffset.
- ZonedDateTime - A date and time with a fully qualified timezone. This can resolve an offset at any point in time. The general rule of thumb is that if you want to represent a date and time without relying the context of a specific server, then you should use ZonedDateTime.
- OffsetTime - A time with a resolved offset.
- OffsetDate - A date with a resolved offset.
- OffsetDateTime - A date and time with a resolved offset. This is useful for serialising data into a database, which is discussed below, and also should be used as the serialisation format for logging timestamps if you have servers in different timezones.
There are also classes for modelling specific concepts within the domain of TimeZones:
- ZoneId - an identifier for a region. Each ZoneId corresponds to some rules that define the timezone for that location. When designing your software if you consider throwing around a String such as "PLT" or "Asia/Karachi" then you should use this domain class instead. For example storing a user's preference for their timezone.
- ZoneOffset - the period of time representing a difference between Greenwich/UTC and a timezone. This can be resolved for specific a ZoneId at a specific moment in time.
The API conventions that were outlined for Local* classes are also present in the field based timezone classes. These include:
Object creation using factory methods starting with 'of', 'from' or 'now'.
// You can specify the zone id when creating a zoned date time ZoneId id = ZoneId.of("Europe/Paris"); ZonedDateTime zoned = ZonedDateTime.of(dateTime, id); zoned = ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Paris]"); ZoneOffset offset = ZoneOffset.of("+2:00"); // Extract useful components ZoneId.from(zoned); OffsetDate.from(zoned); OffsetTime time = OffsetTime.now();
Values accessed by get* methods, and new objects created by calling with* or plus* methods on an existing object.
// changes offset, whilst keeping the same point on the timeline OffsetTime sameTimeDifferentOffset = time.withOffsetSameInstant(offset); // changes the offset, and updates the point on the timeline OffsetTime changeTimeWithNewOffset = time.withOffsetSameLocal(offset); // Can also create new object with altered fields as before changeTimeWithNewOffset .withHours(3) .plusSeconds(2) .minusMinutes(4);
A well defined ordering comparison based on the timeline.
A powerful Adjuster mechanism in order to model business logic based manipulations.
ZonedDateTime zdt; zdt = zoned.with(firstDayOfYear()); zdt = zoned.with(lastInMonth(TUESDAY)); zdt = zoned.with(time);
Databases and JDBC
ANSI SQL provides two types that are related to timezones - the misleading named "TIME WITH TIME ZONE" and "TIMESTAMP WITH TIME ZONE". Neither of these types actually store a timezone! They both store the offset. This is important because it means that you can't resolve the exact timezone after persistence. This means developers need to consider whether the appropriate choice is to store just the offset or whether they should also store the timezone as well.
Existing Timezone classes
There is an existing timezone class in Java - java.util.TimeZone - but this isn't used by JSR-310 since all 310 classes are immutable, and timezone is mutable. There are also subtle incompatible issues to be aware of, specifically that the previous timezone implementation only uses 32bit data for storing timezones. This limits the supported values to between 1901-12-13T20:45:52Z and 2038-01-19T03:14:07Z. Obviously JSR-310 isn't limited by the restriction, and supports a wider range of timezones. The result is that outside of the aforementioned dates compatibility between the existing timezone classes and JSR-310 isn't guaranteed, but if you always use the new classes they will provide the correct timezone information for a wider range of dates than was previously possible.
I've looked at how timezones can be used, and when to consider OffsetDateTime and ZonedDateTime. In Part 3 I plan to talk about Periods, which are a way of representing the different between two dates or times.