JB Header
Working with Time Zones in Java 8 | ZonedDateTime, ZoneId tutorial with examples
Time Zone handling in Java 8 has improved with the new java.time package. However, to effectively use time zones in code one needs to understand certain fundamental components(or classes) that Java 8 has defined to capture all aspects of time zones. These aspects cover areas such as the standard UTC time, zone offsets, offset conversion rules (including rules around daylight saving - especially during the transition times twice a year) and the actual time zones themselves. The good news is that most of the aspects, such as daylight savings and time zone conversions, are already in-built in Java 8. All that one needs to learn is how to use these in-built classes as per specific requirement at hand.

To begin with, I will first show the individual classes which define a date-time instance with time zones via a diagram detailing how these classes interact. These individual classes from java.time and java.time.zone packages are the ones you will be using to get your application to work across time zones. Next, I will explain the roles that each of these classes plays individually. This will be followed by seeing practical commonly used scenarios showing how to actually incorporate time zones in your code. At the end of this tutorial, the reader should be able to understand the fundamentals around time zone handing in Java 8, and know exactly which class to pick(and which ones to ignore) as per their specific requirement. Classes involved with Time Zones handling in Java 8
Java 8 Time Zones handling Classes
Understanding the role of Java 8 Date/Time classes shown above LocalDateTime: java.time.LocalDateTime is strictly not a part of the set of classes responsible for time zones handling. However, it will be, in most likelihood, the class on top of which time zones will be added on majority of occasions (except those few cases where instances of java.time.Instant are being used). Hence, its importance!

LocalDateTime contains a date and a time; a combination of a LocalDate and a LocalTime. The key aspect of LocalDateTime is that the time is indeed "local", i.e. it belongs to a local time-line with no time zone associated with it. So, to make this time relevant in a global context we will need to add a time zone to it. This will be done using the LocalDateTime.atZone(ZoneId zoneId) method. This method returns an instance of a ZonedDateTime which contains the date and time along with time zone passed to it using zoneId.

Instant: java.time.Instant is the class encapsulating the time elapsed from the standard Java epoch(beginning of time in Java) of 1970-01-01T00:00:00Z. Instant instances do have a time zone associated with them - UTC to be specific. So, an instance of Instant holds a value of date-time with a UTC time-line.

However, you may want to change the time zone of an Instant to something other than UTC for which Instant.atZone() method comes in handy. Instant.atZone(ZoneId zoneId) method returns a ZonedDateTime instance with the time zone converted to the zoneId passed as a parameter to the method.

ZonedDateTime: As we saw above, on invoking the atZone() method of both LocalDateTime and Instant results in an instance of java.time.ZonedDateTime being returned. When working with date-time objects with a time zone, ZonedDateTime is what you must use as your primary class to hold the objects as well as to manipulate them as per your needs.

ZoneId: java.time.ZoneId is the unique identifier of a time zone. As we saw above, converting both LocalDateTime and an Instant requires us to tell the exact 'zone ID' for the resulting ZonedDateTime. Zone ID holds the time zone value which is equivalent to what we say in common parlance as "UTC-8:00" or "America/Los_Angeles" or "PST".

Zone IDs are of 3 main types -
  1. ZoneOffset: These are normalized Zone Ids, denoted by "Z", and show time in ‘UTC+/-’ format. Example: ‘20:30Z’.
  2. Offset Style Zone Ids: These start with 'UTC', 'GMT' or 'UT' and show time with +/- prepended to them. Example: 'GMT+02:00'.
  3. Region Based Ids: These do not start with 'UTC', 'GMT', 'UT' '+' or '-', and are of minimum 2 characters. They have the format ‘{area}/{city}’. Example: "America/Los_Angeles". To reference a complete list of region based IDs supported in Java, please refer the table hereClick to see complete list of worldwide Region Based Zone IDs.
ZoneOffset: ZoneOffset is a type of a zone identifier and represents the offset of a time zone from UTC or Greenwich(GMT). UTC-based ZoneOffset instances provide a standard way for working with time zones; which is perhaps the reason why java designers used ZoneOffset as the standard time zone identifier for instances of java.time.Instant. For example: An Instant value would look like this - '2016-11-29T16:47:37.545Z' where 'Z' at the end indicates that a ZoneOffset of 'UTC+00:00' is being used.

ZoneRules:An instance of java.time.zone.ZoneRules encapsulates the rules for converting a given Date-Time to a specific ZoneId. You can get the ZoneRules for a ZoneId by invoking ZoneId.getRules() method on a ZoneId instance.

All the rules specific to a time zone, such as conversions to/from that zone during 'day light saving', or during the transition times when the daylight saving is put in place or removed, etc are captured in ZoneRules. The good news is that the specific rules for all the time zones in existence today in the IANA Time Zone Database (TZDB) have already been incorporated in java.time.zone.DefaultZoneRulesProvider which is loaded by default.

ZoneRulesProvider: java.time.zone.ZoneRulesProvider is responsible for configuring of time zone rules at Java platform-level or environment level. The ZoneRulesProvider(s) to be used for a JVM instance can be declared via a configuration file or programmatically. However, for most common time zone uses the DefaultZoneRulesProvider, mentioned earlier, will be sufficient.
Scenario 1: Starting with a java.time.Instant value and converting to a desired time zone
Changing time zones of a java.time.Instant instance
package com.javabrahman.java8.time;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
public class InstantTimeZoneHandling {
  public static void main(String args[]) {
    //Starting with an java.time.Instant value
    Instant timeStamp= Instant.now();
    System.out.println("Machine Time Now:" + timeStamp);

    //timeStamp in zone - "America/Los_Angeles"
    ZonedDateTime LAZone= timeStamp.atZone(ZoneId.of("America/Los_Angeles"));
    System.out.println("In Los Angeles(America) Time Zone:"+ LAZone);

    //timeStamp in zone - "GMT+01:00"
    ZonedDateTime timestampAtGMTPlus1= timeStamp.atZone(ZoneId.of("GMT+01:00"));
    System.out.println("In 'GMT+01:00' Time Zone:"+ timestampAtGMTPlus1);
 OUTPUT of the above code
Machine Time Now :   2016-11-29T14:23:25.551Z
In 'Los Angeles(America)' Time Zone :   2016-11-29T06:23:25.551-08:00[America/Los_Angeles]
In 'GMT+01:00' Time Zone :   2016-11-29T15:23:25.551+01:00[GMT+01:00] 
Explanation of the code
  • InstantTimeZoneHandling’s main() method starts off with getting the current machine timestampRead quick coding tip on getting current machine timestamp using Instant.now() using Instant.now() and assigns it to a variable named timeStamp.
  • Instant objects are by default in UTC time zone. Printing the value of timestamp gives us 2016-11-29T14:23:25.551Z. 'Z' here denotes the UTC+00:00 time zone.
  • We then use the Instant.atZone() method to convert timeStamp’s time zone to the "America/Los Angeles" time zone using ZoneId.of() method like this - timeStamp.atZone(ZoneId.of("America/Los_Angeles")).
  • ZoneId.of() method returns a 'proper' ZoneId instance which is standard in the java.time package. This ZoneId is then fed as input to atZone() method.
  • The atZone() method returns a ZonedDateTime instance which contains timeStamp’s time converted to its equivalent time in "America/Los_Angeles" time zone which is 2016-11-29T06:23:25.551-08:00[America/Los_Angeles], which is 8 hours behind UTC time.
  • Similarly, using the "GMT+01:00" time zone we get the timeStamp’s equivalent time in a ZonedDateTime instance which is 2016-11-29T15:23:25.551+01:00[GMT+01:00]
Scenario 2: Adding a time zone to LocalDateTime
Adding time zone to LocalDateTime
package com.javabrahman.java8.time;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
public class LocalDateTimeWithTimeZones {
  public static void main(String args[]) {
    //Starting with an java.time.LocalDateTime value of '2016-11-28T09:30'
    LocalDateTime localDateTime= LocalDateTime.of(2016, 11, 28, 9, 30);
    System.out.println("LocalDateTime is:"+ localDateTime);
    //Adding "America/Los_Angeles" as the Time Zone to localDateTime
    ZonedDateTime LAZonedDateTime= localDateTime.atZone(ZoneId.of("America/Los_Angeles"));
    System.out.println("In Los Angeles(America) Time Zone:"+ LAZonedDateTime);
 OUTPUT of the above code
LocalDateTime is :   2016-11-28T09:30
In Los Angeles(America) Time Zone :   2016-11-28T09:30-08:00[America/Los_Angeles] 
Explanation of the code
  • The main() method of LocalDateTimeWithTimeZones class starts with defining a LocalDateTime instance with value 2016-11-28T09:30 using the LocalDateTime.of() method. At this moment the localDateTime variable does not have a time zone stored in it.
  • Next the time zone for "America/Los_Angeles" is added to localDateTime instance by using the LocalDateTime.atZone() method, which uses ZoneId.of() method similar to the way we saw in previous Scenario 1.
  • The atZone() method adds the "America/Los_Angeles" time zone to localDateTime and returns a ZonedDateTime instance named LAZonedDateTime.
  • The value of LAZonedDateTime is then printed as 2016-11-28T09:30-08:00[America/Los_Angeles]. The printed value shows that the time zone name[America/Los_Angeles] and the offset from UTC '-08:00' have indeed been added in LAZonedDateTime.
  • Note - in the next Scenario 3, we will not write the code from scratch and use the ZonedDateTime variable - LAZonedDateTime we have defined just now.
Scenario 3: Converting ZonedDateTime to its equivalent in a different time zone
Converting ZonedDateTime to its equivalent in a different time zone
  //LAZonedDateTime's equivalent in "UTC+00:00" Time Zone 
  ZonedDateTime LADateTimeToUTC= LAZonedDateTime.withZoneSameInstant(ZoneId.of("UTC+00:00"));
  System.out.println("Converted to 'UTC' Time Zone:"+ LADateTimeToUTC);

  //LAZonedDateTime's equivalent in "GMT+01:00" Time Zone 
  ZonedDateTime LADateTimeToGMTPlus1= LAZonedDateTime.withZoneSameInstant(ZoneId.of("GMT+01:00"));
  System.out.println("Converted to 'GMT+01:00' Time Zone:"+ LADateTimeToGMTPlus1);
 OUTPUT of the above code
Converted to 'UTC' Time Zone :   2016-11-28T17:30Z[UTC]
Converted to 'GMT+01:00' Time Zone :   2016-11-28T18:30+01:00[GMT+01:00] 
Explanation of the code
  • The above code snippet is in continuation of the code we saw in Scenario 2, with LAZonedDateTime containing the value 2016-11-28T09:30-08:00[America/Los_Angeles].
  • First LAZonedDateTime is converted to UTC+00:00 time zone using ZonedDateTime.withZoneSameInstant() method.
  • ZonedDateTime.withZoneSameInstant() method keeps the time stored in ZonedDateTime instance as the same and converts it to its equivalent in the required time zone passed as a parameter to it.This method returns back a ZonedDateTime instance with the changed time zone.
  • So, 2016-11-28T09:30-08:00[America/Los_Angeles] is converted to its equivalent time in UTC+00:00 time zone which is 2016-11-28T17:30Z[UTC].
  • Similarly, next 2016-11-28T09:30-08:00[America/Los_Angeles] is converted to its equivalent time in GMT+01:00 time zone which is 2016-11-28T17:30Z2016-11-28T18:30+01:00[GMT+01:00].
Summary In the above tutorial we had a look at the primary classes used for time zone handling in Java 8. We understood how the classes interact with each other and the roles played by each of the classes. We then looked at the 3 most common scenarios for time zone handling and saw code examples showing how to program for them.