What are JDBC-mysql Driver settings for sane handling of DATETIME and TIMESTAMP in UTC?

后端 未结 1 1505
长情又很酷
长情又很酷 2021-01-23 16:55

Having been burned by mysql timezone and Daylight Savings \"hour from hell\" issues in the past, I decided my next application would store everything in UTC timezone and only in

相关标签:
1条回答
  • 2021-01-23 17:40

    The solution is to set JDBC connection parameter noDatetimeStringSync=true with useLegacyDatetimeCode=false. As a bonus I also found sessionVariables=time_zone='-00:00' alleviates the need to set time_zone explicitly on every new connection.

    There is some "intelligent" timezone conversion code that gets activated deep inside the ResultSet.getString() method when it detects that the column is a TIMESTAMP column.

    Alas, this intelligent code has a bug: TimeUtil.fastTimestampCreate(TimeZone tz, int year, int month, int day, int hour, int minute, int seconds, int secondsPart) returns a Timestamp wrongly tagged to the JVM's default timezone, even when the tz parameter is set to something else:

    final static Timestamp fastTimestampCreate(TimeZone tz, int year, int month, int day, int hour, int minute, int seconds, int secondsPart) {
        Calendar cal = (tz == null) ? new GregorianCalendar() : new GregorianCalendar(tz);
        cal.clear();
    
        // why-oh-why is this different than java.util.date, in the year part, but it still keeps the silly '0' for the start month????
        cal.set(year, month - 1, day, hour, minute, seconds);
    
        long tsAsMillis = cal.getTimeInMillis();
    
        Timestamp ts = new Timestamp(tsAsMillis);
        ts.setNanos(secondsPart);
    
        return ts;
    }
    

    The return ts would be perfectly valid except when further up in the call chain, it is converted back to a string using the bare toString() method, which renders the ts as a String representing what a clock would display in the JVM-default timezone, instead of a String representation of the time in UTC. In ResultSetImpl.getStringInternal(int columnIndex, boolean checkDateTypes):

                    case Types.TIMESTAMP:
                        Timestamp ts = getTimestampFromString(columnIndex, null, stringVal, this.getDefaultTimeZone(), false);
    
                        if (ts == null) {
                            this.wasNullFlag = true;
    
                            return null;
                        }
    
                        this.wasNullFlag = false;
    
                        return ts.toString();
    

    Setting noDatetimeStringSync=true disables the entire parse/unparse mess and just returns the string value as-is received from the database.

    Test output:

    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    0
    

    The useLegacyDatetimeCode=false is still important because it changes the behaviour of getDefaultTimeZone() to use the database server's TZ.

    While chasing this down I also found the documentation for useJDBCCompliantTimezoneShift is incorrect, although it makes no difference: documentation says [This is part of the legacy date-time code, thus the property has an effect only when "useLegacyDatetimeCode=true."], but that's wrong, see ResultSetImpl.getNativeTimestampViaParseConversion(int, Calendar, TimeZone, boolean).

    0 讨论(0)
提交回复
热议问题