0

JVM version is 1.7. Timezone is GMT+3, offset 180 minutes. 1500411600000 corresponds to 7/19/2017, 12:00:00 AM (I've verified this online).

I'm executing the following code to adjust time of a Date instance:

final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Date date = new Date(1500411600000L);
calendar.setTime(date);
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
date = calendar.getTime();

I expect date to become 7/19/2017, 11:59:59 PM but instead of this I get 7/19/2017, 2:59:59 AM. 3 hours difference - exactly as much as my timezone is different from UTC/GMT, so I suppose that some unnoticed conversion happens here.

debug - date evaluation

Can you please help me to find timezone agnostic code for adjusting time in date?

naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
  • 4
    "Verifying" with the site you linked, i get a totally different date/time: `7/18/2017, 11:00:00 PM` – XtremeBaumer Dec 18 '17 at 11:56
  • 2
    Try jodatime http://www.joda.org/joda-time/ – ThomasEdwin Dec 18 '17 at 11:58
  • 2
    Why are you struggling with the long outdated `Calendar` class? Joda-Time is better (@ThomasEdwin), but in maintenance mode, and [`java.time`, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/) is still better and much nicer to work with. – Ole V.V. Dec 18 '17 at 12:40
  • It probably hasn’t got anything to do with `Calendar`. The `toString` method of the even more outdated `Date` class grabs your JVM’s time zone and renders its time in this time zone, very confusing. Inside the `Date` you have the correct and desired point in time. See [All about java.util.Date](https://codeblog.jonskeet.uk/2017/04/23/all-about-java-util-date/). – Ole V.V. Dec 18 '17 at 12:57
  • The millisecond value 1500411600000 corresponds to 2017-07-18T21:00:00Z or 2017-07-19T00:00+03:00. – Ole V.V. Dec 18 '17 at 13:01

5 Answers5

2

You are correct that at offset UTC+3 your millisecond value, 1500411600000, corresponds to July 19, 2017 at midnight (start of day). At other offsets it corresponds to other times of day either July 18 or 19.

java.time

Assuming that it is no coincidence that you have got midnight in your own time zone, that the value is really supposed to represent a date, not a time, I recommend you use LocalDate from java.time to represent it:

    ZoneId yourTimeZone = ZoneId.of("Europe/Riga");
    LocalDate date = Instant.ofEpochMilli(1500411600000L)
            .atZone(yourTimeZone)
            .toLocalDate();
    System.out.println(date);

This prints the expected

2017-07-19

Please either substitute your correct time zone in case it doesn’t happen to be Europe/Riga, or use a ZoneOffset instead: .atOffset(ZoneOffset.ofHoursMinutes(3, 0)) (the other lines are the same).

I suspect you don’t really want the end of the day even though in your question you are trying to set it. If this is for determining whether some point in time is before the end of the day, compare it to the start of the following day and require that it is strictly before. This saves you the trouble with the odd-looking minutes, seconds and fractions of second.

    ZonedDateTime startOfNextDay = date.plusDays(1).atStartOfDay(yourTimeZone);

java.time came out in 2014 as a replacement for both the poorly designed date and time classes from Java 1.0 and 1.1 and for Joda-Time, from which much inspiration was drawn. I warmly recommend you use it.

What you tried in the question

I believe your code from the question is also clearer when expressed with java.time:

    OffsetDateTime endOfDay = Instant.ofEpochMilli(1500411600000L)
            .atOffset(ZoneOffset.UTC)
            .with(LocalTime.MAX);
    System.out.println(endOfDay);

This prints

2017-07-18T23:59:59.999999999Z

(July 18 at the end of day in UTC; Z at the end denotes UTC). Except for the number of decimals, this is also the result you got. You may have been fooled by the fact that your Date instance is printed something like Wed Jul 19 02:59:59 EEST 2017 (the time zone abbreviation depending on your JVM’s time zone setting). Date.toString() grabs your JVM’s time zone setting and converts the date-time to this time zone for the generated string only; the Date instance itself is not modified and only holds a point on the time line, no time zone.

Question: can I use java.time with my Java version?

Yes you can. You just need to use at least Java 6.

For learning to use java.time, see the Oracle tutorial or find other resoureces on the net.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
1

You're using Calendar.getInstance(TimeZone.getTimeZone("UTC")), but you have to use the timezone you're in. As you stated GMT+3

Lino
  • 19,604
  • 6
  • 47
  • 65
1

Please refer to this thread here which explains the issue regarding Date and timezones.

How to set time zone of a java.util.Date?

The Date object will have the correct adjusted time but when it is displayed, the output will use your local timezone. You can forcefully set the timezone of your JVM using the following code but this may have unintended consequences in other parts of your code.

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

In an ideal world you would use the Java 8 date classes or Joda time library classes both of which provide some simple date manipulation methods.

Java 8 date classes

AdamPillingTech
  • 456
  • 2
  • 7
1

Use clear. It seems a historical "bug" to me, a time zoned Calendar, where setTime does not alter the zone.

final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Date date = new Date(1500411600000L);
calendar.clear(); // To reset _all_ fields, incl. the time zone offset ZONE_OFFSET.
calendar.setTime(date);
calendar.set(Calendar.HOUR_OF_DAY, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
date = calendar.getTime();

Of course this might be the right argument to switch to the new java time API.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • I agree that it's a perfect reason to upgrade. But regarding your solution, in my opinion, `Calendar.getInstance(TimeZone.getTimeZone("UTC")) + Calendar.clear()` is equivalent to `Calendar.getInstance()`, isn't it? – naXa stands with Ukraine Dec 18 '17 at 12:24
  • @naXa `getInstance` does `createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));` and `clear` resets all set-Fields, also ZONE_OFFSET. I am not sure whether you are right or wrong. – Joop Eggen Dec 18 '17 at 12:46
1

The problem is bigger than described in my question. It stems from incorrect managing Date/Time for user's timezone. In my application timestamp was sent in user's timezone and then evaluated to date in server's timezone, but timezone difference was not taken into account. I tried to fix this and faced the issue described in the question.

I listened to the @ThomasEdwin's advice to use Joda Time and I'm happy to share this solution:

long userTimezoneOffset = 180;  // it's a parameter submitted by client app
Date date = new Date(1500411600000L);  // it's another parameter submitted by client app

final DateTimeZone zone = DateTimeZone.forOffsetMillis((int) TimeUnit.MINUTES.toMillis(userTimezoneOffset));
final DateTimeZone serverZone = DateTimeZone.getDefault();

MutableDateTime dateTime = new MutableDateTime(date, zone);
dateTime.setHourOfDay(23);
dateTime.setMinuteOfHour(59);
dateTime.setSecondOfMinute(59);
dateTime.setMillisOfSecond(999);
dateTime.setZoneRetainFields(serverZone);
date = dateTime.toDate();
// now date.toString() returns expected result

Also I found -Duser.timezone JVM parameter to be quite useful when debugging this issue. See here for a list of supported timezone IDs.

naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
  • I repeat, Joda-Time is a big step forward. Joda-Time is also in maintenance mode; no major further development is expected. – Ole V.V. Dec 18 '17 at 15:11