javadatetimejava-calendar

java.util.Calendar not updating the timezone properly


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:

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?


Solution

  • 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.