javamysqltimezonemysql-connectordst

Why can't I get the Timestamp of the date '1914-11-08 00:00:00' in timezone 'Europe/Amsterdam'?


Recently i upgraded my mysql-connector from 5.1.47 to 8.0.33. After that, i met an error:

Caused by: java.sql.SQLException: HOUR_OF_DAY: 0 -> 1
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:130) ~[mysql-connector-j-8.0.33.jar:8.0.33]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:98) ~[mysql-connector-j-8.0.33.jar:8.0.33]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:90) ~[mysql-connector-j-8.0.33.jar:8.0.33]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:64) ~[mysql-connector-j-8.0.33.jar:8.0.33]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:74) ~[mysql-connector-j-8.0.33.jar:8.0.33]
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:85) ~[mysql-connector-j-8.0.33.jar:8.0.33]
    at com.mysql.cj.jdbc.result.ResultSetImpl.getTimestamp(ResultSetImpl.java:947) ~[mysql-connector-j-8.0.33.jar:8.0.33]
    at com.mysql.cj.jdbc.result.ResultSetImpl.getTimestamp(ResultSetImpl.java:985) ~[mysql-connector-j-8.0.33.jar:8.0.33]
    at com.zaxxer.hikari.pool.HikariProxyResultSet.getTimestamp(HikariProxyResultSet.java) ~[HikariCP-4.0.3.jar:na]
    at org.hibernate.type.descriptor.sql.TimestampTypeDescriptor$2.doExtract(TimestampTypeDescriptor.java:84) ~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
    at org.hibernate.type.descriptor.sql.BasicExtractor.extract(BasicExtractor.java:47) ~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
    at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:257) ~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
    at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:253) ~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
    at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:243) ~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
    at org.hibernate.type.AbstractStandardBasicType.hydrate(AbstractStandardBasicType.java:329) ~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
    at org.hibernate.persister.entity.AbstractEntityPersister.hydrate(AbstractEntityPersister.java:3214) ~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
    at org.hibernate.persister.entity.Loadable.hydrate(Loadable.java:94) ~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
    at org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl.loadFromResultSet(EntityReferenceInitializerImpl.java:342) ~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
    ... 113 common frames omitted

By investigating this error, I got that the new mysql driver would do some converting and my dataset contains the date '1916-05-01 00:00 00'. According to wikipedia, the first nation-wide daylight saving time implementations were by the German and Austro-Hungarian Empires, both starting on 30 April 1916. So it may be thinking that it can't convert '1916-05-01 00:00 00' to timestamp since '1916-05-01 00:00 00' is 'non-existent', only '1916-05-01 01:00 00'. I decided to find out all of these 'non-existent' dates in timezone 'Europe/Amsterdam'. Here is how i did:

public static void main(String[] args) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Calendar start = Calendar.getInstance();
        Calendar end = Calendar.getInstance();
        start.setTime(sdf.parse("1900-01-01"));
        end.setTime(sdf.parse("2025-01-01"));
        List<String> errorItems = new ArrayList<>();
        while (!start.after(end)) {
            try {
                Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("Europe/Amsterdam"), Locale.US);
                calendar.setLenient(false);
                calendar.set(start.get(Calendar.YEAR), start.get(Calendar.MONTH), start.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
                Timestamp ts = new Timestamp(calendar.getTimeInMillis());
                System.out.println(ts);
            } catch (IllegalArgumentException e) {
                errorItems.add(sdf.format(start.getTime()));
            }
            start.add(Calendar.DAY_OF_YEAR, 1);
        }
        System.out.println("error date: " + errorItems);
    }

The result is: [1914-11-08, 1916-05-01]

The '1916-05-01' I've already understood, but why '1914-11-08'? The DST even did not exist in 1914. Does anyone have any idea?

I'd like to know why I can't get the Timestamp of the date '1914-11-08 00:00:00' in timezone 'Europe/Amsterdam'.


Solution

  • About two years ago the maintainers of the IANA time zone database decided that since Brussels and Amsterdam have identical time zone rules starting from 1970, that they would "link" Europe/Amsterdam to Europe/Brussels. This effectively erased the pre-1970 history of time zone rules for Europe/Amsterdam, and substitutes in the pre-1970 rules Europe/Brussels.

    For Europe/Brussels the local date 1914-11-08 00:00:00 does not exist because at that time, local time jumped ahead by one hour to 1914-11-08 01:00:00. This change was not true for Amsterdam.

    Your upgrade from 5.1.47 to 8.0.33 apparently picked up this change in the IANA tz database.


    PS: If you don't like this change to the IANA tz database, don't take it out on me. I'm just the messenger.