La soluzione è impostare il parametro di connessione JDBC noDatetimeStringSync=true
con useLegacyDatetimeCode=false
. Come bonus ho trovato anche sessionVariables=time_zone='-00:00'
allevia la necessità di set time_zone
esplicitamente ad ogni nuova connessione.
Esiste un codice di conversione del fuso orario "intelligente" che viene attivato all'interno di ResultSet.getString()
metodo quando rileva che la colonna è un TIMESTAMP
colonna.
Purtroppo, questo codice intelligente ha un bug:TimeUtil.fastTimestampCreate(TimeZone tz, int year, int month, int day, int hour, int minute, int seconds, int secondsPart)
restituisce un Timestamp
erroneamente contrassegnato con il fuso orario predefinito della JVM, anche quando tz
parametro è impostato su qualcos'altro:
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;
}
Il ritorno ts
sarebbe perfettamente valido tranne quando più in alto nella catena di chiamate, viene riconvertito in una stringa usando il semplice toString()
metodo, che rende il ts
come una stringa che rappresenta ciò che un orologio visualizzerebbe nel fuso orario predefinito della JVM, invece di una rappresentazione String dell'ora 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();
Impostazione di noDatetimeStringSync=true
disabilita l'intero parse/unparse mess e restituisce solo il valore della stringa così come viene ricevuto dal database.
Uscita di prova:
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
Il useLegacyDatetimeCode=false
è ancora importante perché cambia il comportamento di getDefaultTimeZone()
per utilizzare la TZ del server di database.
Durante la ricerca ho anche trovato la documentazione per useJDBCCompliantTimezoneShift
non è corretto, anche se non fa alcuna differenza:la documentazione dice [Questo fa parte del codice data-ora legacy, quindi la proprietà ha effetto solo quando "useLegacyDatetimeCode=true." ], ma è sbagliato, vedi ResultSetImpl.getNativeTimestampViaParseConversion(int, Calendar, TimeZone, boolean)
.