1

The goal of this method is to take a utc date, convert it to the timezone specified and return the DateTimeValue object. However we recently found a bug with this method when using it with certain timezones.

private static DateTimeValue toDateTimeValue(Date endDate, TimeZone timeZone) {     
    Calendar cal = Calendar.getInstance();
    cal.setTime(endDate);
    cal.setTimeZone(timeZone);
    cal.set(Calendar.HOUR_OF_DAY, 23); // move to the end of the day
    cal.set(Calendar.MINUTE, 59);
    cal.set(Calendar.SECOND, 59);
    int year = cal.get(Calendar.YEAR);
    int month = cal.get(Calendar.MONTH) + 1;
    int day = cal.get(Calendar.DAY_OF_MONTH);  //ends up being 3 but should be 4
    int hour = cal.get(Calendar.HOUR_OF_DAY);
    int minute = cal.get(Calendar.MINUTE);
    int second = cal.get(Calendar.SECOND);

    return new DateTimeValueImpl(year, month, day, hour, minute, second);
}

Main case illustrating bug:

  • endDate value: Mon Oct 03 21:00:00 UTC 2022
  • timezone value: Europe/Helsinki +3

In the method above the Day value ends up being the 3rd, but it should be the 4th since Oct 03 21:00:00 in UTC is actually Oct 4th in the Helsinki timezone

I did some further testing with this code in place of that method.

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    String utcDate = sdf.format(endDate); 
    System.out.println(utcDate);   //2022-10-03 09:00:00

    sdf.setTimeZone(timeZone);
    String timeZoneDate = sdf.format(endDate);
    System.out.println(timeZoneDate);   //2022-10-04 12:00:00
    
        

So this shows the correct/expected results however this is a string, and I need it as a DateTimeValue.

Why does java.util.calendar not update the date (the day) when we set the timezone to Helsinki?

owen gerig
  • 6,165
  • 6
  • 52
  • 91
  • 2
    Is there a specific reason not to use `java.time` classes? What is `DateTimeValue` / `DateTimeValueImpl`? – OneCricketeer Sep 30 '22 at 18:02
  • ya, we use org.jresearch.ical.values.RRule, the results of this is set to the Until value – owen gerig Sep 30 '22 at 18:17
  • What is a `DateTimeValue`? – Basil Bourque Sep 30 '22 at 18:33
  • As an aside it’s a bad idea and wrong to represent the end of the day as 23:59:59. Represent it as 00:00:00 on the next day and in comparisons remember that this is exclusive (to be on the day in question a time has to be *strictly before* 00:00:00). – Ole V.V. Oct 01 '22 at 10:07
  • 1
    I have reproduced. I cannot explain the behaviour you and I have observed. All I can say is that the `Calendar` class was always a pain to work with, cumbersome, complicated and hard to predict. Fortunately it was replaced by [java.time, the modern Java date and time API,](https://docs.oracle.com/javase/tutorial/datetime/index.html) soon to be 10 years ago. So the recommendation is very clear: Don’t use `Calendar`. Use java.time. – Ole V.V. Oct 01 '22 at 10:20

1 Answers1

3

The java.util date-time API is outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API.

The following demo shows how easily and cleanly you could do/test it by using the modern date-time API.

Demo:

public class Main {
    public static void main(String[] args) {
        ZonedDateTime endDate = ZonedDateTime.of(LocalDate.of(2022, 10, 3), LocalTime.of(21, 0), ZoneOffset.UTC);
        ZonedDateTime zdtDesired = endDate.withZoneSameInstant(ZoneId.of("Europe/Helsinki"));
        System.out.println(zdtDesired);
        System.out.println(zdtDesired.getDayOfMonth());
    }
}

Output:

2022-10-04T00:00+03:00[Europe/Helsinki]
4

How to convert java.util.Date into ZonedDateTime?

You can convert java.util.Date into Instant which can be converted into ZonedDateTime. It means you do not even need to use ZonedDateTime#withZoneSameInstant as shown in the above demo.

public class Main {
    public static void main(String[] args) {
        // In your case, it will be endDate.toInstant()
        Instant instant = new Date().toInstant();
        ZonedDateTime zdtDesired = instant.atZone(ZoneId.of("Europe/Helsinki"));
        System.out.println(zdtDesired);
    }
}

Output:

2022-09-30T21:57:50.487+03:00[Europe/Helsinki]

Learn more about the the modern date-time API from Trail: Date Time.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • ya but i still need it as a DateTimeValue, or the separation of year, month, day, etc so i can instantiate one – owen gerig Sep 30 '22 at 19:12
  • 2
    @owengerig - That's what (`zdtDesired.getDayOfMonth()`) I have shown in my demo. The way I have got Day-Of-Month from the `ZonedDateTime`, you can get the year, month, hour, minute, second etc. – Arvind Kumar Avinash Sep 30 '22 at 20:06
  • 1
    `new DateTimeValueImpl(zdtDesired.getYear(), zdtDesired.getMonth(), zdtDesired.getDayOfMonth(), zdtDesired.getHour(), zdtDesired.getMinute(), zdtDesired.getSecond())`. – Ole V.V. Oct 01 '22 at 10:24