2012-03-22 15 views
7

Después de buscar y leer acerca de cómo manejar la fecha y la hora desde/hacia Java y MySQL, todavía estoy confundido.Aún confundido por Java Timestamps, etc. con MySQL

Digamos que creo un objeto java.util.Date. Ese objeto tiene tiempo en UTC. Cualquier formateo o análisis en otras zonas horarias se puede hacer con, p. java.text.SimpleDateFormat.

Ahora quiero almacenar mi objeto de fecha en la base de datos MySQL en UTC. Pero cuando uso el método setTimestamp() en java.sql.PreparedStatement me confundo un poco. Aquí sigue un ejemplo de código donde pruebo tanto MySQL DATETIME como TIMESTAMP en mi tabla. También inserto las fechas con los métodos setString() y setTimestamp().

java.sql.Connection conn = java.sql.DriverManager.getConnection("jdbc:mysql://localhost/test","user","password"); 
java.sql.Statement st = conn.createStatement(); 

String q = "DROP TABLE IF EXISTS tmp"; 
st.execute(q); 
q = "CREATE TABLE tmp (dt_string TEXT, dt DATETIME, ts TIMESTAMP)"; 
st.execute(q); 

java.sql.PreparedStatement pst = conn.prepareStatement("INSERT INTO tmp SET dt_string=?, dt=?, ts=?"); 

java.text.SimpleDateFormat utc = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
utc.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); 

java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("EST")); 

System.out.println("Default time zone: " + java.util.TimeZone.getDefault().getID()); 
java.util.Date d = new java.util.Date(); 
System.out.println("A date: " + d); 
java.sql.Timestamp t = new java.sql.Timestamp(d.getTime()); 
System.out.println("The timestamp: " + t); 


pst.setString(1, utc.format(d)); 
pst.setString(2, utc.format(d)); 
pst.setString(3, utc.format(t)); 
pst.execute(); 

pst.setTimestamp(2, t); 
pst.setTimestamp(3, t); 
pst.execute(); 

System.out.println("Use calendar: " + utc.getCalendar().getTimeZone()); 
pst.setTimestamp(2, t, utc.getCalendar()); 
pst.setTimestamp(3, t, utc.getCalendar()); 
pst.execute(); 

conn.close(); 

Cuando ejecuto lo anterior, obtengo la siguiente salida, que es la esperada.

Default time zone: EST 
A date: Thu Mar 22 08:49:51 EST 2012 
The timestamp: 2012-03-22 08:49:51.784 
Use calendar: sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null] 

Pero cuando examino la tabla en la base de datos utilizando la herramienta de línea de comandos de MySQL me sale:

mysql> select * from tmp; 
+---------------------+---------------------+---------------------+ 
| dt_string   | dt     | ts     | 
+---------------------+---------------------+---------------------+ 
| 2012-03-22 13:49:51 | 2012-03-22 13:49:51 | 2012-03-22 13:49:51 | 
| 2012-03-22 13:49:51 | 2012-03-22 08:49:51 | 2012-03-22 08:49:51 | 
| 2012-03-22 13:49:51 | 2012-03-22 08:49:51 | 2012-03-22 08:49:51 | 
+---------------------+---------------------+---------------------+ 
3 rows in set (0.00 sec) 

La primera columna es sólo un tipo de texto donde almaceno la UTC fecha formateada.

En la primera fila, guardé las fechas usando el método setString().

En la segunda fila almacené la fecha usando el método setTimestamp(i,t). Supongo que JDBC convierte automáticamente la fecha usando la zona horaria predeterminada (que establecí en EST) antes de que la almacene. Pero TIMESTAMP no siempre debe almacenarse automáticamente en UTC. La documentación de MySQL dice MySQL convierte los valores de TIMESTAMP de la zona horaria actual a UTC para el almacenamiento y de regreso de UTC a la zona horaria actual para la recuperación. (Número 1).

Finalmente, para la tercera fila utilicé el pst.setTimestamp(2, t, utc.getCalendar()); para almacenar la fecha con la esperanza de que el controlador utilice la zona horaria UTC. Pero aparentemente no (Problema 2).

Puedo solucionar fácilmente el problema de almacenar fechas en UTC estableciendo la zona horaria predeterminada en UTC. Pero aún así, me gustaría entender qué está pasando en los dos temas anteriores.

+0

* "de vuelta de UTC a la zona horaria actual para la recuperación" * es lo que ves allí en la salida. –

+0

Gracias, pero ¿podría ampliar esto? – Peter

+0

Las marcas de tiempo se almacenan en UTC internamente, pero cuando las muestra usando el cliente 'mysql', se le presentan en la hora local, como lo indica la documentación de MySQL. Lo mismo para el problema 2. ¿Cuál es el problema aquí? –

Respuesta

0

Los campos de fecha y hora MySql son geniales una vez que los domina.

"SimpleDateFormat" que está utilizando no tiene una zona horaria asociada, por lo que su base de datos asume que está ingresando una fecha en la zona horaria de su servidor. Si se encuentra en una zona horaria de -0500, le agrega 5 horas para convertirla a UTC, y luego cuando la recupera, resta las 5 horas para convertirla a su hora local.

Cuando convirtió su marca de tiempo a utc antes de insertarla, aún no conectó una zona horaria a la cadena. Su base de datos realizó la misma operación en él. Se agregaron 5 horas a 2012-03-22 13:49:51, por lo que su db almacenó 2012-03-22 18:49:51 (aún suponiendo -0500).

La parte realmente divertida de todo esto es que puede establecer la zona horaria en una conexión de base de datos, y todas las fechas que obtenga o inserte cambiarán junto con su conexión.

+0

Pero solo uso SimpleDateFormat para crear una cadena que ingreso en el campo TEXTO en la base de datos. Las inserciones directas de Timestamp en los campos DATETIME o TIMESTAMP no usan SimpleDateFormat. – Peter

1

Finalmente, estoy entendiendo algo, pero utilizando PostgreSQL en su lugar.Yo, básicamente, repetía el código anterior utilizando

Connection conn = DriverManager.getConnection("jdbc:postgresql://localhost"); 

st.execute("CREATE TABLE tmp (ts_string TEXT, ts TIMESTAMP WITH TIME ZONE)"); 
//st.execute("CREATE TABLE tmp (ts_string TEXT, ts TIMESTAMP WITHOUT TIME ZONE)"); 

y escribir las fechas como

pst.setString(1, utc.format(d)); 
pst.setTimestamp(2, t); 
pst.execute(); 

pst.setTimestamp(2, t, utc.getCalendar()); 
pst.execute(); 

Cuando se utiliza TIMESTAMP WITH TIMEZONE Me da la siguiente salida de psql

postgres=# select * from tmp; 
     ts_string  |    ts    
-------------------------+---------------------------- 
2012-03-23 12:48:28.057 | 2012-03-23 13:48:28.057+01 
2012-03-23 12:48:28.057 | 2012-03-23 13:48:28.057+01 
(2 rows) 

porque es hacer el seguimiento de la zona horaria del sistema (servidor), que es CET (+01). Las fechas son correctas aunque se muestran en CET.

Si en lugar de utilizar TIMESTAMP WITHOUT TIME ZONE me sale

postgres=# select * from tmp; 
     ts_string  |   ts   
-------------------------+------------------------ 
2012-03-23 12:49:04.120 | 2012-03-23 07:49:04.12 
2012-03-23 12:49:04.120 | 2012-03-23 12:49:04.12 
(2 rows) 

y en el primer caso se utiliza la zona horaria predeterminada (la cual di a EST), y la fecha es, naturalmente, "mal", al igual que en el Caso MySQL. Sin embargo, en el segundo caso utiliza UTC porque lo pasé como argumento para el método setTimestamp(), y esto es lo que traté de hacer en MySQL. Para mí, parece que el controlador java MySQL simplemente ignora el argumento Calendar en setTimestamp(). Tal vez me perdí algo.

Cuestiones relacionadas