Search code examples
javadst

How to manually set the Daylight Saving (DST) shift date in Java


My country changed the the Daylight Saving shift date from "October 21" to "November 4" and we need to apply this in our back-end.

The appropriate solution is to update the Operating System configuration, but we have restrictions to do so (legacy dependencies). We are looking for a workaround.

Is it possible to use code and change the DST shift date programmatically?

GregorianCalendar gc = new GregorianCalendar();
gc.setTimeInMillis(0);
gc.set(2018, Calendar.OCTOBER, 21, 0, 0, 0);
gc.setTimeZone(TimeZone.getTimeZone("Brazil/East"));
XMLGregorianCalendar xml = DatatypeFactory.newInstance().newXMLGregorianCalendar(gc);
System.out.println("XML Date: " + xml.toString());

Output must be -03:00:

XML Date: 2018-10-21T01:00:00.000-02:00

Solution

  • OS irrelevent

    Your operating system configuration is irrelevant. Most Java implementations by default pick up their initial default time zone from the host OS upon launch. But the definition of the time zones is stored within the Java implementation.

    Java time zone updater

    So you need to update the time zone definitions within your Java implementation. Most implementations use the tz database also known as tzdata.

    For the Oracle-branded Java implementation, Oracle provides the Timezone Updater Tool. That landing page has an as-of date of 2018-08, so perhaps your time zone’s changes have been included. But I suggest you investigate more closely to verify.

    For other implementations, check with the vendor. They may have provided an updated version of the JVM to include the fresh tzdata. Or perhaps they too provide an updater tool. Or perhaps you can replace the tzdata file manually.

    Avoid mangling zone with code

    I strongly suggest you avoid trying to make artificial adjustments to the offset yourself in code. You will likely get it wrong. Date-time work in surprisingly tricky and confusing.

    But if you insist, firstly avoid the terrible old legacy date-time classes such as GregorianCalendar & Calendar & Date. These were supplanted years ago by JSR 310. If you must interoperate with old code not yet updated to java.time, do your work in the modern classes and then at the end convert via new methods added to the old classes.

    Use the modern the java.time classes, specifically:

    • Instant (for a moment in UTC)
    • OffsetDateTime (for a moment with an offset-from-UTC of hours-minutes-seconds but no time zone)
    • ZonedDateTime (for a moment in a particular time zone)

    You can search Stack Overflow for many existing examples and explanations using these classes. You should focus on OffsetDateTime, ZoneOffset (rather than ZoneId), and Instant since you must avoid ZonedDateTime if your know your tzdata file to be outdated.

    Same moment, different wall-clock time

    enter image description here

    OffsetDateTime::withOffsetSameInstant​

    OffsetDateTime odt = OffsetDateTime.parse( "2018-10-21T01:00:00.000-02:00" ) ;
    ZoneOffset offset = ZoneOffset.ofHours( -3 ) ;
    OffsetDateTime odt2 = odt.withOffsetSameInstant​( offset ) ;  // Same moment, same point on the timeline, different wall-clock time.
    

    odt.toString(): 2018-10-21T01:00-02:00

    odt2.toString(): 2018-10-21T00:00-03:00

    In that example, both odt and odt2 represent the same simultaneous moment, the same point on the timeline. If you extract an Instant (a value in UTC), your results will be the same moment. Only their wall-clock time is different.

    Instant instant1 = odt.toInstant() ;  // Adjust to UTC.
    Instant instant2 = odt2.toInstant() ;
    boolean sameMoment = instant1.equals( instant2 ) ; 
    

    instant1.toString(): 2018-10-21T03:00:00Z

    instant2.toString(): 2018-10-21T03:00:00Z

    sameMoment = true

    The Z on the end means UTC, an offset-from-UTC of zero, +00:00. The Z is pronounced “Zulu”. Defined by the ISO 8601 standard.

    Different moment, same wall-clock time

    enter image description here

    OffsetDateTime::withOffsetSameLocal​

    In contrast, you may want to force the time-of-day thereby representing a different moment. For that, use withOffsetSameLocal method. Be very aware that you are changing the meaning of data, you are moving to another point on the timeline.

    OffsetDateTime differentMomentButSameTimeOfDay = odt. withOffsetSameLocal( offset ) ;
    

    differentMomentButSameTimeOfDay.toString(): 2018-10-21T01:00-03:00

    Extract the instant to see we have a different moment.

    Instant differentInstant = differentMomentButSameTimeOfDay.toInstant() ;
    

    differentInstant.toString(): 2018-10-21T04:00:00Z

    Notice the 4 AM UTC versus 3 AM UTC seen above. This moment here occurs an hour after the moment above. Two different points on the timeline.

    Do not attempt this work until you fully comprehend the concept of points on the timeline, and changing between points being entirely different than adjusting offsets. Practice extensively before doing real work. Half-hearted guessing will land you in a world of hurt and headache.

    And, as I suggested above, your time would be much better spent installing updated tzdata files rather than hacking these offsets.

    Live code

    See all the code above run live at IdeOne.com.

    Update tzdata everywhere

    For best results, you should be updating the tzdata (or equivalent) in all these various places:

    • Your operating systems
    • Your JVMs
    • Your database engines, such as Postgres
    • Any libraries bundling their own time zone info (ex: Joda-Time)

    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.

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

    The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

    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. Hibernate 5 & JPA 2.2 support java.time.

    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.