¿Es java.sql.Timestamp específico de la zona horaria?

81

Tengo que almacenar UTC dateTime en DB.
He convertido el dateTime dado en una zona horaria específica a UTC. para eso seguí el siguiente código.
Mi fecha y hora de entrada es "20121225 10:00:00 Z" La zona horaria es "Asia / Calcuta"
Mi servidor / DB (Oracle) se está ejecutando en la misma zona horaria (IST) "Asia / Calcutta"

Obtenga el objeto Date en esta zona horaria específica

        String date = "20121225 10:00:00 Z";
        String timeZoneId = "Asia/Calcutta";
        TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);

        DateFormat dateFormatLocal = new SimpleDateFormat("yyyyMMdd HH:mm:ss z");
                    //This date object is given time and given timezone
        java.util.Date parsedDate = dateFormatLocal.parse(date + " "  
                         + timeZone.getDisplayName(false, TimeZone.SHORT));

        if (timeZone.inDaylightTime(parsedDate)) {
            // We need to re-parse because we don't know if the date
            // is DST until it is parsed...
            parsedDate = dateFormatLocal.parse(date + " "
                    + timeZone.getDisplayName(true, TimeZone.SHORT));
        }

       //assigning to the java.sql.TimeStamp instace variable
        obj.setTsSchedStartTime(new java.sql.Timestamp(parsedDate.getTime()));

Almacenar en DB

        if (tsSchedStartTime != null) {
            stmt.setTimestamp(11, tsSchedStartTime);
        } else {
            stmt.setNull(11, java.sql.Types.DATE);
        }

SALIDA

DB (Oracle) ha almacenado lo mismo dado que dateTime: "20121225 10:00:00no está en UTC.

Lo he confirmado desde el siguiente sql.

     select to_char(sched_start_time, 'yyyy/mm/dd hh24:mi:ss') from myTable

Mi servidor de base de datos también se ejecuta en la misma zona horaria "Asia / Calcuta"

Me da las siguientes apariencias

  1. Date.getTime() no está en UTC
  2. O bien, la marca de tiempo tiene un impacto en la zona horaria mientras se almacena en la base de datos. ¿Qué estoy haciendo mal aquí?

Una pregunta más:

¿Se timeStamp.toString()imprimirá en la zona horaria local como java.util.datehace? ¿No es UTC?

Kanagavelu Sugumar
fuente
4
Para responder a la pregunta en su título, no, java.sql.Timestamp se basa en UTC. La visualización de la marca de tiempo es específica de la zona horaria.
Gilbert Le Blanc
@Gilbert Le Blanc ¡Gracias !. ¿Podría responderme "new java.sql.Timestamp (parsedDate.getTime ())" devolverá el objeto de marca de tiempo GMT para mi entrada?
Kanagavelu Sugumar
1
@GilbertLeBlanc Aunque java.sql.Timestampestá en UTC, cuando se almacena en un campo TIMESTAMP sin información de zona horaria, usa la fecha / hora equivalente en la zona horaria actual de la máquina virtual
Mark Rotteveel
@Kanagavelu Sugumar: Sí. el método getTime de la clase Date devuelve el número de milisegundos desde el 1 de enero de 1970 a las 00:00:00 GMT.
Gilbert Le Blanc
@MarkRotteveel Mark No entendí su punto de "almacenar en un campo TIMESTAMP sin información de zona horaria, usa la fecha / hora equivalente en la zona horaria actual de la máquina virtual". ¿Podría dar más detalles en su respuesta? Me será más útil.
Kanagavelu Sugumar

Respuestas:

104

Aunque no se especifica explícitamente, los setTimestamp(int parameterIndex, Timestamp x)controladores deben seguir las reglas establecidas por el setTimestamp(int parameterIndex, Timestamp x, Calendar cal)javadoc :

Establece el parámetro designado en el java.sql.Timestampvalor dado , utilizando el Calendarobjeto dado . El controlador utiliza el Calendarobjeto para construir un TIMESTAMPvalor SQL , que luego envía a la base de datos. Con un Calendarobjeto, el conductor puede calcular la marca de tiempo teniendo en cuenta una zona horaria personalizada. Si no Calendarse especifica ningún objeto, el controlador usa la zona horaria predeterminada, que es la de la máquina virtual que ejecuta la aplicación.

Cuando llama con setTimestamp(int parameterIndex, Timestamp x)el controlador JDBC, utiliza la zona horaria de la máquina virtual para calcular la fecha y hora de la marca de tiempo en esa zona horaria. Esta fecha y hora es lo que se almacena en la base de datos, y si la columna de la base de datos no almacena la información de la zona horaria, entonces cualquier información sobre la zona se pierde (lo que significa que depende de las aplicaciones que utilizan la base de datos para utilizar la misma zona horaria consistentemente o idear otro esquema para discernir la zona horaria (es decir, almacenar en una columna separada).

Por ejemplo: su zona horaria local es GMT + 2. Almacena "2012-12-25 10:00:00 UTC". El valor real almacenado en la base de datos es "2012-12-25 12:00:00". Lo recuperas de nuevo: lo recuperas como "2012-12-25 10:00:00 UTC" (pero solo si lo recuperas usando getTimestamp(..)), pero cuando otra aplicación accede a la base de datos en la zona horaria GMT + 0, lo hará recuperar la marca de tiempo como "2012-12-25 12:00:00 UTC".

Si desea almacenarlo en una zona horaria diferente, debe usar el setTimestamp(int parameterIndex, Timestamp x, Calendar cal)con una instancia de Calendario en la zona horaria requerida. Solo asegúrese de usar también el getter equivalente con la misma zona horaria al recuperar valores (si usa un TIMESTAMPsin información de zona horaria en su base de datos).

Entonces, asumiendo que desea almacenar la zona horaria GMT real, debe usar:

Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
stmt.setTimestamp(11, tsSchedStartTime, cal);

Con JDBC 4.2, un controlador compatible debería admitir java.time.LocalDateTime(y java.time.LocalTime) para TIMESTAMP(y TIME) hasta get/set/updateObject. Las java.time.Local*clases no tienen zonas horarias, por lo que no es necesario aplicar ninguna conversión (aunque eso podría abrir un nuevo conjunto de problemas si su código asumiera una zona horaria específica).

Mark Rotteveel
fuente
Quiero almacenar mi tiempo en GMT. Creo que mi marca de tiempo en GMT. ¿Entonces qué debo hacer? ¿Es setTimestamp (int parameterIndex, Timestamp tzGmtObj, Calendar calGmtObj)?
Kanagavelu Sugumar
si "setTimestamp (int parameterIndex, Timestamp x)" usa la zona horaria de la máquina virtual. ¿Cuál es el papel de x en el código?
Kanagavelu Sugumar
1
@KanagaveluSugumar Ese es el valor real de la marca de tiempo para establecer. Un controlador solo usa el objeto Calendario para la información de la zona horaria, ¡no por su valor!
Mark Rotteveel
1
Genial, está funcionando :) Dedico mucho tiempo a demostrar que (Fecha / Marca de tiempo) .getTime () mantiene el tiempo específico de la zona horaria en milisegundos. Ahora entendí que no lo es. Siempre da UTC milisegundos independientemente de la zona horaria. Pero (Date / Timestamp) .toString () es específico de la zona horaria. Muchas gracias.
Kanagavelu Sugumar
4
El toString()encendido java.lang.Date(y java.lang.Timestamp) siempre lo presentará en su zona horaria local.
Mark Rotteveel
35

Creo que la respuesta correcta debería ser java.sql.Timestamp NO es específico de la zona horaria. La marca de tiempo es una combinación de java.util.Date y un valor de nanosegundos separado. No hay información de zona horaria en esta clase. Así, al igual que Date, esta clase simplemente contiene el número de milisegundos desde el 1 de enero de 1970, 00:00:00 GMT + nanos.

En PreparedStatement.setTimestamp (int parameterIndex, Timestamp x, Calendar cal), el controlador utiliza el calendario para cambiar la zona horaria predeterminada. Pero la marca de tiempo todavía tiene milisegundos en GMT.

La API no está clara acerca de cómo se supone que el controlador JDBC debe usar el Calendario. Los proveedores parecen sentirse libres sobre cómo interpretarlo, por ejemplo, la última vez que trabajé con MySQL 5.5 Calendar, el controlador simplemente ignoró Calendar en PreparedStatement.setTimestamp y ResultSet.getTimestamp.

Evgeniy Dorofeev
fuente
Comentario de interés sobre MySQL 5.5
Alex
1
Creo que esto es incorrecto a partir de java 8+. Dentro de la marca de tiempo (porque se extiende desde java.util.Date) hay un atributo de calendario, que tiene un atributo zoneinfo que de hecho contiene la zona horaria. Aún puede obtener la hora UTC con .getTime () porque la marca de tiempo también la almacena, pero TIENE información de zona horaria.
Jorge.V
1
quiere decir que contiene el número de milisegundos desde el 1 de enero de 1970 a las 00:00:00 UTC en lugar de GMT. GMT tiene horario de verano. La época de UNIX es milisegundos desde el 1 de enero de 1970 a las 00:00 UTC.
Kirby
6

Para Mysql, tenemos una limitación. En el controlador Mysql doc , tenemos:

Los siguientes son algunos problemas conocidos y limitaciones para MySQL Connector / J: Cuando Connector / J recupera marcas de tiempo para un día de cambio de horario de verano (DST) usando el método getTimeStamp () en el conjunto de resultados, algunos de los valores devueltos pueden ser incorrectos. Los errores se pueden evitar utilizando las siguientes opciones de conexión al conectarse a una base de datos:

useTimezone=true
useLegacyDatetimeCode=false
serverTimezone=UTC

Entonces, cuando no usamos estos parámetros y llamamos setTimestamp or getTimestampcon calendario o sin calendario, tenemos la marca de tiempo en la zona horaria de jvm.

Ejemplo:

La zona horaria de jvm es GMT + 2. En la base de datos, tenemos una marca de tiempo: 1461100256 = 19/04/16 21: 10: 56,000000000 GMT

Properties props = new Properties();
props.setProperty("user", "root");
props.setProperty("password", "");
props.setProperty("useTimezone", "true");
props.setProperty("useLegacyDatetimeCode", "false");
props.setProperty("serverTimezone", "UTC");
Connection con = DriverManager.getConnection(conString, props);
......
Calendar nowGMT = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
Calendar nowGMTPlus4 = Calendar.getInstance(TimeZone.getTimeZone("GMT+4"));
......
rs.getTimestamp("timestampColumn");//Oracle driver convert date to jvm timezone and Mysql convert date to GMT (specified in the parameter)
rs.getTimestamp("timestampColumn", nowGMT);//convert date to GMT 
rs.getTimestamp("timestampColumn", nowGMTPlus4);//convert date to GMT+4 timezone

El primer método devuelve: 1461100256000 = 19/04/2016 - 21:10:56 GMT

El segundo método devuelve: 1461100256000 = 19/04/2016 - 21:10:56 GMT

El tercer método devuelve: 1461085856000 = 19/04/2016 - 17:10:56 GMT

En lugar de Oracle, cuando usamos las mismas llamadas, tenemos:

El primer método devuelve: 1461093056000 = 19/04/2016 - 19:10:56 GMT

El segundo método devuelve: 1461100256000 = 19/04/2016 - 21:10:56 GMT

El tercer método devuelve: 1461085856000 = 19/04/2016 - 17:10:56 GMT

NB: No es necesario especificar los parámetros para Oracle.

Abdelhafid
fuente
1
Supongo que para Oracle podría establecer la zona horaria de la sesión en UTC para lograr la misma conversión que MySQL para el caso sin calendario. " ALTER SESSION SET TIME_ZONE = 'UTC'".
eckes
5

Es específico de su conductor. Debe proporcionar un parámetro en su programa Java para indicarle la zona horaria que desea utilizar.

java -Duser.timezone="America/New_York" GetCurrentDateTimeZone

Además de esto:

to_char(new_time(sched_start_time, 'CURRENT_TIMEZONE', 'NEW_TIMEZONE'), 'MM/DD/YY HH:MI AM')

También puede ser útil para manejar la conversión correctamente. Tomado de aqui

Woot4Moo
fuente
¿ Querías que siguiera stackoverflow.com/questions/2858182/… o quieres configurar mi aplicación JVM para que se ejecute en GMT?
Kanagavelu Sugumar
@KanagaveluSugumar bueno eso depende. Si desea modificar la marca de tiempo para CADA consulta, configúrelo en el inicio, si solo desea modificar ALGUNOS (es decir, algo menos que CADA), modifique cada consulta de la manera establecida por la publicación a la que hizo referencia.
Woot4Moo
3

La respuesta es que java.sql.Timestampes un desastre y debe evitarse. Úselo en su java.time.LocalDateTimelugar.

Entonces, ¿por qué es un desastre? Desde java.sql.TimestampJavaDoc, a java.sql.Timestampes una "envoltura delgada java.util.Dateque permite que la API de JDBC identifique esto como un valor TIMESTAMP de SQL". Desde java.util.DateJavaDoc, "la Dateclase está destinada a reflejar la hora universal coordinada (UTC)". De la especificación ISO SQL, TIMESTAMP WITHOUT TIME ZONE "es un tipo de datos que es fecha y hora sin zona horaria". TIMESTAMP es un nombre corto para TIMESTAMP SIN HUSO HORARIO. Entonces, un java.sql.Timestamp"refleja" UTC mientras que SQL TIMESTAMP es "sin zona horaria".

Debido a que java.sql.Timestamprefleja UTC, sus métodos aplican conversiones. Esto causa una confusión sin fin. Desde la perspectiva de SQL, no tiene sentido convertir un valor de TIMESTAMP de SQL a otra zona horaria, ya que TIMESTAMP no tiene una zona horaria desde la que convertir. ¿Qué significa convertir 42 a Fahrenheit? No significa nada porque 42 no tiene unidades de temperatura. Es solo un número desnudo. De manera similar, no puede convertir un TIMESTAMP de 2020-07-22T10: 38: 00 a Americas / Los Angeles porque 2020-07-22T10: 30: 00 no está en ninguna zona horaria. No está en UTC o GMT ni nada más. Es una hora de cita desnuda.

java.time.LocalDateTimetambién es una fecha y hora desnuda. No tiene una zona horaria, exactamente como SQL TIMESTAMP. Ninguno de sus métodos aplica ningún tipo de conversión de zona horaria, lo que hace que su comportamiento sea mucho más fácil de predecir y comprender. Así que no lo use java.sql.Timestamp. Utilice java.time.LocalDateTime.

LocalDateTime ldt = rs.getObject(col, LocalDateTime.class);
ps.setObject(param, ldt, JDBCType.TIMESTAMP);
Douglas Surber
fuente
Muy bien dicho. Gracias.
Ole VV
0

Puede utilizar el método siguiente para almacenar la marca de tiempo en la base de datos específica para su zona / identificación de zona deseada.

ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Calcutta")) ;
Timestamp timestamp = Timestamp.valueOf(zdt.toLocalDateTime());

Un error común que la gente comete es LocaleDateTimepara obtener la marca de tiempo de ese instante, lo que descarta cualquier información específica de su zona, incluso si intenta convertirla más tarde. No comprende la Zona.

Tenga en cuenta que Timestampes de la clase java.sql.Timestamp.

Ayush Kumar
fuente