6

I'm trying to grok time-handling, and I've stumbled upon something in Java that has me somewhat baffled. Take this sample code:

public static void main(String[] args)
{
    //Calendar set to 12:00 AM of the current day (Eastern Daylight Time)
    Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT-4"));
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
    /////

    //Calendar set to 12:00 AM of the current day (UTC time)
    Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    utcCal.set(Calendar.HOUR_OF_DAY, 0);
    utcCal.set(Calendar.MINUTE, 0);
    utcCal.set(Calendar.SECOND, 0);
    utcCal.set(Calendar.MILLISECOND, 0);
    /////

    long oneHourMilliseconds = 3600000;
    System.out.println((cal.getTimeInMillis() - utcCal.getTimeInMillis()) / oneHourMilliseconds);
}

I visualize the algorithm for calculating the time represented by cal taking 1 of 2 forms:

  1. Calculate the number of milliseconds from the Epoch, add offset (add -4)
  2. Calculate the number of milliseconds from (Epoch + offset). So # of milliseconds from (Epoch - 4 * oneHourMilliseconds).

Both of these algorithms should yield a result that is 4 hours behind that of utcCal, however running the code returns 4 .

Can someone explain to me why cal, despite being set to a time zone 4 hours behind that of utcCal, ends up having a millisecond value 4 hours after that of utcCal? Shouldn't the code be returning -4?

Kevin
  • 2,617
  • 29
  • 35

3 Answers3

11

It's an unfortunate piece of history. A time zone with an ID of "GMT-4" is what you'd expect to be "UTC+4", i.e. it's 4 hours ahead of UTC.

From the etcetera file from tzdb:

# We use POSIX-style signs in the Zone names and the output abbreviations,
# even though this is the opposite of what many people expect.
# POSIX has positive signs west of Greenwich, but many people expect
# positive signs east of Greenwich.  For example, TZ='Etc/GMT+4' uses
# the abbreviation "GMT+4" and corresponds to 4 hours behind UTC
# (i.e. west of Greenwich) even though many people would expect it to
# mean 4 hours ahead of UTC (i.e. east of Greenwich).

And from this similar explanation:

If you can at all manage it, avoid definitions and use of the GMT timezones...

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
2

The calendar cal is set to 2012-04-18 00:00:00 of the timezone GMT-4.

That moment corresponds to 2012-04-18 04:00:00 in UTC (in other words, when it's 12 AM in the timezone GMT-4, it's 4 AM in UTC).

The calendar utcCal is set to 2012-04-18 00:00:00 of the timezone UTC.

The difference between 2012-04-18 04:00:00 and 2012-04-18 00:00:00 is 4 hours, so you see 4 being printed.

Jesper
  • 202,709
  • 46
  • 318
  • 350
0

The other Answers are correct. In various contexts the +/- of offset-from-UTC values have opposite meanings.

java.time

In the context of modern java.time classes built into Java 8 and later:

  • Ahead of UTC, +
  • Behind UTC, -

So the offset currently used in India (Asia/Kolkata) is +05:30, five and a half hours ahead of UTC. In the America/Regina zone of Canada, the offset currently used is -06:00, six hours behind UTC.

Use zones, not offsets

The big tip here is to always use proper time zone names rather than a mere offset, whenever known.

An offset-from-UTC is simply a number of hours, minutes, and seconds; nothing more, nothing less. A time zone is a history of past, present, and future changes to the offset used by the people of a certain region. So a zone is always preferable to an offset.

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

ZoneId z = ZoneId.of( "Africa/Tunis" ) ; 

A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.

LocalDate ld = LocalDate.now( z ) ;

For four hours ahead of UTC, use a time zone such as Asia/Dubai.

ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( `Asia/Dubai` ) ) ;

zdt.toString(): 2018-03-01T02:39:18.801642+04:00[Asia/Dubai]

For four hours behind UTC, use a time zone such as America/Blanc-Sablon.

ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( `America/Blanc-Sablon` ) ) ;

zdt.toString(): 2018-02-28T18:39:18.801642-04:00[America/Blanc-Sablon]

ZoneOffset

If you are not certain of the intended time zone, and have only an offset-from-UTC, use ZoneOffset class.

ZoneOffset offset = ZoneOffset.ofHoursMinutes( 4 , 30 ) ; // Positive hours is ahead of UTC (east), while negative hours is behind UTC (west). 

For UTC itself (an offset of zero), use the constant ZoneOffset.UTC.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154