2

The GregorianCalendar is being inconsistent:

Calendar cal = new GregorianCalendar(2000, 0, 1);
long testCalOne = cal.getTimeInMillis();
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
long testCalTwo = cal.getTimeInMillis();

Calendar cal2 = new GregorianCalendar(2000, 0, 1);
cal2.setTimeZone(TimeZone.getTimeZone("UTC"));
long testCalThree = cal2.getTimeInMillis();

System.out.println(testCalOne + ", " + testCalTwo + ", " + testCalThree);

Results in

946681200000, 946681200000, 946684800000

This represents 2000-01-01 midnight in GMT+1, GMT+1 and UTC respectively. My timezone is +1 hour relative to UTC.

The problem here is that the getTimeInMillis is supposed to return the amount of milliseconds since 1970-01-01 in UTC. Only testCalThree is correct.

Another problem is that setTimeZone is seemingly not working depending on whether I called getTimeInMillis before.

My goal is to take a Calendar I receive as parameter from other code and get a UTC (java.util) Date for further use.

Juryt
  • 23
  • 1
  • 1
  • 3

3 Answers3

1

tl;dr

myGregCal.toZonedDateTime()
         .toInstant()

…or…

java.util.Date.from(
    myGregCal.toZonedDateTime()
             .toInstant()
)

java.time

My goal is to take a Calendar I receive as parameter from other code and get a UTC (java.util) Date for further use.

The troublesome old date-time classes of Calendar and Date are now legacy, supplanted by the java.time classes. Fortunately you can convert to/from java.time by calling new methods on the old classes.

ZonedDateTime zdt = myGregCal.toZonedDateTime() ;

For UTC value, extract an Instant. That class represents a moment on the timeline in UTC with a resolution of nanoseconds.

Instant instant = zdt.toInstant() ;

To generate a String representing that UTC value in a standard ISO 8601 format, call toString.

String output = instant.toString() ;

If you need other formats in generated strings, convert to the more flexible OffsetDateTime and use DateTimeFormatter. Search Stack Overflow for many examples.

Best to avoid the Date class. But if you must, convert. Like Instant, Date represents a point on the timeline in UTC. But Date is limited to milliseconds resolution. So you risk data loss, lopping off digits from the decimal fraction of a second beyond the 3 digits of milliseconds versus 9 digits of nanoseconds.

java.util.Date utilDate = Date.from( instant ) ;

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 java.time.

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

Where to obtain the java.time classes?

  • Java SE 8 and SE 9 and later
    • Built-in.
    • Part of the standard Java API with a bundled implementation.
    • Java 9 adds some minor features and fixes.
  • Java SE 6 and SE 7
    • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android

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.

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

I think it's a bug in the Calendar implementation. time is cached, i.e. if you've calculated it once it is reused unless something changes.

But somehow setting time zone is not recognized as a relevant change.

You can force re-calculation of time in ms by changing or clearing some field explicitly. So this:

    Calendar cal = new GregorianCalendar(2000, 0, 1);
    long testCalOne = cal.getTimeInMillis();
    cal.clear(Calendar.ZONE_OFFSET);
    cal.setTimeZone(TimeZone.getTimeZone("UTC"));
    long testCalTwo = cal.getTimeInMillis();

    Calendar cal2 = new GregorianCalendar(2000, 0, 1);
    cal2.setTimeZone(TimeZone.getTimeZone("UTC"));
    long testCalThree = cal2.getTimeInMillis();

    System.out.println(testCalOne + ", " + testCalTwo + ", " + testCalThree);

Gives:

946681200000, 946684800000, 946684800000

As you would expect. The only thing I had to do is cal.clear(Calendar.ZONE_OFFSET) before setting time zone.

Anyway, I'd like to repeat the recommendation from comments. If you value your sanity, switch to JodaTime or Java8 date/time.

lexicore
  • 42,748
  • 17
  • 132
  • 221
  • Technically it's still not as expected, as getTimeInMillis is supposed to take the offset into account and return it as UTC. As such, setting the timezone should be irrelevant. However, getTimeInMillis seems bugged, making a timezone change relevant. Thank you for the answer. – Juryt Dec 02 '16 at 10:19
  • How setting a time zone should be irrelevant? `getTimeInMillis()` returns milliseconds between 1970-01-01 00:00:00 UTC and the date in calendar instance. Setting timezone is significant change, because the calendar holds the _local_ date 2000-01-01 00:00:00 in your case. And 2000-01-01 00:00:00 UTC differs from 2000-01-01 00:00:00 UTC+1. – Archie Dec 02 '16 at 16:05
0

Upon thinking further on it, the code may work as intended. setTimeZone may just function differently depending on whether a getter has been called upon the Calendar or not.

In the first case, it changes the timezone, but because the time has already been gotten and therefore initialized, the UTC time is unchanged.

In the second case, it indicates that the initializing date of 2001-1-1 we passed in the constructor was a UTC time and should be parsed as UTC rather than the local timezone.

If so, then the results make sense.

Juryt
  • 23
  • 1
  • 1
  • 3
  • This is nonsense. The moment you invoke a *getter* should be completely irrelevant. I think this is a bug. – lexicore Dec 02 '16 at 16:18
  • Internally, calendar is lazy. It'll only actually calculate its internal fields when needed. However, you can set them rather directly. This leads me to suspect that for this class, when you call a getter is relevant. (However counter-intuitive and clunky.) – Juryt Dec 05 '16 at 13:46