0

We receive a GregorianCalendar object from a 3rd party library. We need to turn that into a Date for use in another 3rd party library. And we're on Java 1.6 so we don't have the new time/instant classes available.

The problem is calling Calendar.getTime() gives a different date, offset by (I think) our timezone. So the next day by 8 hours.

How can we do this without this shift?

Update: We get the date from an OData call. The date being returned is an employee birthdate (Northwind) and therefore shouldn't have a time. But it's returned as a GregorianCalendar object with a time of 1992-05-01 00:00:00. GMT timezone it appears.

And the getTime() is returning a Date of "Thu Apr 30 18:00:00 MDT 1992" - I'm in the Mountain Time Zone.

The problem is I need to get from the calendar object a Date object of 1992-05-01, not 1992-04-30. And preferably with the time offset matching too.

David Thielen
  • 28,723
  • 34
  • 119
  • 193
  • 1
    `Date` represents an instant. It doesn't have a time zone. – shmosel Jul 09 '18 at 22:37
  • So `calendar.get(Calendar.DATE)` doesn't work? ‒ provided `calendar` is your `GregorianCalendar` instance. – x80486 Jul 09 '18 at 22:42
  • `Date#toString` is a "human readable" representation of the amount of time since the unix epoch that the instance of `Date` represents. The `Date` object itself doesn't have a concept of a "time zone" or "offset" which are calculations applied "after the fact" – MadProgrammer Jul 09 '18 at 22:48
  • Well, Calendar.getTime(), as said by the Java documentation, "returns the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this Date object." So, are you saying you're somehow deriving the date from this, or are you using some other method? Also, which time zone would you like it represented in terms of such that the "shift" does not happen? – Gigi Bayte 2 Jul 09 '18 at 22:50
  • @GigiBayte2 The `java.util.Date` object returned by [`Calendar.getTime`](https://docs.oracle.com/javase/6/docs/api/java/util/Calendar.html#getTime()) represents a moment in UTC, but its [`Date::toString`](https://docs.oracle.com/javase/6/docs/api/java/util/Calendar.html#toString()) method confusingly returns a `String` *after* dynamically applying the JVM’s current default time zone. Thus the confusion behind this Question and many others on Stack Overflow. – Basil Bourque Jul 09 '18 at 23:49

2 Answers2

2

Get get the Date value in your default time zone, call setTimeZone().

GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.clear();
cal.set(1992,4,1); // 1992-05-01 00:00:00 GMT

// "Fix" time zone
cal.setTimeZone(TimeZone.getDefault());

System.out.println(cal.getTime());

Output

Fri May 01 00:00:00 EDT 1992

Andreas
  • 154,647
  • 11
  • 152
  • 247
0

tl;dr

No shift

java.util.Date date = myGregorianCalendar.getTime() ;  // Same moment, same point on the timeline. `Date` is in UTC, `GregorianCalendar` may be in some other time zone.
String output = date.toString() ;  // This new string is a lie, dynamically applying the JVM’s current time zone while the `Date` is actually in UTC, always, by definition.

There is no shift. Calling GregorianCalendar.getTime produced a java.util.Date. The Date object is always in UTC, by definition. Unfortunately the Date::toString method lies, injecting the JVM’s current default time zone while producing a String.

Be clear that the Date and String are two separate distinct objects. One holds a moment in UTC, the other is a textual representation of that moment after being adjusted into some time zone.

The GregorianCalendar, the Date, and the String all represent the same moment, same point on the timeline, but different wall-clock time.

Use java.time for clarity

Date-time handling is much easier and clear if you use modern java.time classes rather than awful mess that is the legacy classes Date, Calendar, and GregorianCalendar.

java.time

The GregorianCalendar class is one of the troublesome old date-time classes supplanted by the java.time classes built into Java 8 and later. Much of the java.time functionality is back-ported to Java 6 and Java 7 in the ThreeTen-Backport project.

Convert from legacy class to modern java.time using new methods added to the old classes, specifically GregorianCalendar::toZonedDateTime. If using the back-port, use the DateTimeUtils class.

ZonedDateTime zdt = DateTimeUtils.toZonedDateTime( myCalendar ) ;

A ZonedDateTime object is the replacement for GregorianCalendar. This class is conceptually the combination of a Instant (a moment in UTC) with an assigned time zone, a ZoneId object.

If you want the same moment as seen in UTC, extract the Instant.

Instant instant = zdt.toInstant() ;

You can convert back to a java.util.Date from an Instant, for compatibility with old code not yet updated to java.time.

java.util.Date date = DateTimeUtils.toDate( instant ) ;  // Convert from modern `Instant` class to legacy `Date` class.

If you want just the date portion, without the time-of-day and without the time zone, create a LocalDate object.

LocalDate ld = zdt.toLocalDate() ;

The problem is calling Calendar.getTime() gives a different date, offset by (I think) our timezone. So the next day by 8 hours.

How can we do this without this shift?

And the getTime() is returning a Date of "Thu Apr 30 18:00:00 MDT 1992" - I'm in the Mountain Time Zone.

What you are seeing is an illusion. The GregorianCalendar::getTime method returns to you a java.util.Date object. Then you implicitly called toString on that Date object. That java.util.Date::toString method has an unfortunate behavior of applying your JVM’s current default time zone while generating a string to represent its value. The value of the Date is actually UTC, always UTC, by definition. That toString method creates the illusion that the Date harbors a time zone when in fact it does not.


Actually, the java.util.Date class does harbor a time zone, but deep within its source code. Used for stuff like the equals method implementation. But the class has no getter or setter, so it seems invisible to us. And in the context of your Question, is irrelevant.

Confusing? Yes. This is one of many reasons to avoid these terrible old date-time classes. Use only java.time classes instead.


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
  • If they *"receive a `GregorianCalendar` object from a **3rd party library**"* and they need *"a `Date` for use in another **3rd party library**"*, then adding ThreeTen-Backport is way overkill just for that conversion. Sorry, not a useful answer. -1 – Andreas Jul 09 '18 at 23:21
  • 1
    @Andreas (a) Using the *java.time* classes makes the problem, and solution, more clear and obvious. (b) This is not likely their only spot of date-time handling. – Basil Bourque Jul 09 '18 at 23:29
  • @Andreas We're going to upgrade to Java 1.8 on the new version, but for this issue, I need to fix this in one place in the old version. So yes & thank you. – David Thielen Jul 10 '18 at 01:45
  • @DavidThielen FYI… The idea behind *ThreeTen-Backport* is that the API is virtually the same as *java.time*. You can start using the *java.time* functionality now via the back-port, in Java 6 & 7. When you then move to Java 8 or later, you need do little more than change your `import` statements. – Basil Bourque Jul 10 '18 at 03:41
  • Since you are going to upgrade to Java 8 anyway, I would certainly consider using the ThreeTen Backport until then advantageous, so you have embarked on the new way of doing things and carry less old-fashioned code over. Up to you, of course. – Ole V.V. Jul 10 '18 at 08:00
  • And a very well explained answer, BTW. – Ole V.V. Jul 10 '18 at 08:03