A solução é definir o parâmetro de conexão JDBC
noDatetimeStringSync=true
com useLegacyDatetimeCode=false
. Como bônus, também encontrei sessionVariables=time_zone='-00:00'
alivia a necessidade de set time_zone
explicitamente em cada nova conexão. Há algum código de conversão de fuso horário "inteligente" que é ativado profundamente dentro do
ResultSet.getString()
quando detecta que a coluna é um TIMESTAMP
coluna. Infelizmente, este código inteligente tem um bug:
TimeUtil.fastTimestampCreate(TimeZone tz, int year, int month, int day, int hour, int minute, int seconds, int secondsPart)
retorna um Timestamp
marcado erroneamente no fuso horário padrão da JVM, mesmo quando o tz
parâmetro está definido para outra coisa: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;
}
O retorno
ts
seria perfeitamente válido, exceto quando mais acima na cadeia de chamadas, ele é convertido novamente em uma string usando o toString()
simples método, que renderiza o ts
como uma String representando o que um relógio exibiria no fuso horário padrão da JVM, em vez de uma representação de String da hora em UTC. Em 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();
Configurando
noDatetimeStringSync=true
desativa toda a confusão de análise/desparse e apenas retorna o valor da string como é recebido do banco de dados. Saída de teste:
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
O
useLegacyDatetimeCode=false
ainda é importante porque altera o comportamento de getDefaultTimeZone()
para usar a TZ do servidor de banco de dados. Enquanto procurava isso, também encontrei a documentação para
useJDBCCompliantTimezoneShift
está incorreto, embora não faça diferença:a documentação diz [Isso faz parte do código de data e hora herdado, portanto, a propriedade tem efeito apenas quando "useLegacyDatetimeCode=true." ], mas isso está errado, veja ResultSetImpl.getNativeTimestampViaParseConversion(int, Calendar, TimeZone, boolean)
.