El API de tiempo en Java es posiblemente el más vilipendiado, odiado e incomprendido de la plataforma. Pero es lo que hay, y a veces, la única opción que tenemos permitida (no siempre tenemos libertad para elegir librerías), así que voy a escribir un pequeño recetario/recordatorio de aquellas peculiaridades poco intuitivas, que aunque estén perfectamente documentadas, pueden inducir a error si no las tenemos en cuenta.
Primero voy a recordar lo más básico. Para la maquina virtual, la fecha y hora es un long
, que indica el número de milisegundos transcurridos desde las 00:00 GMT del 1 de enero de 1970. Punto. No hay más. Las clases Date
y Calendar
son meros envoltorios con utilidades diversas.
Y ahora sí, vamos con los gotchas.
Meses en Calendar
En la clase Calendar
, los meses empiezan desde cero. Es decir, enero es el mes 0, febrero es el mes 1, marzo el 2... y diciembre es el mes 11. La propia clase nos ofrece unas constantes con los nombres de los meses en inglés (JANUARY
, FEBRUARY
), pero es fácil olvidar este detalle y tener un disgusto. Sobre todo, cuando la clase SimpleDateFormat
sí sigue el criterio intuitivo, y los meses empiezan con uno (enero es 1, febrero es 2, diciembre es 12).
HOUR
vs. HOUR_OF_DAY
Calendar
nos ofrece muchas constantes para referirnos a las distintas partes de la fecha y hora, pero cuidado con ellas. La constante HOUR
se refiere a la hora, pero exclusivamente en formato AM/PM. Eso quiere decir que su valor está comprendido entre 1 y 12. Si hacemos
calendar.set(Calendar.HOUR, 6);
estaremos estableciendo un valor diferente dependiendo de en qué momento del día se ejecute. Si es antes de las 12:00, estaremos indicando que la hora es 6 (cosa que seguramente es lo que queremos hacer), pero si ese código se ejecuta a las 12:00 o después, estaremos estableciendo 18 como hora. Si queremos usar el formato 24H (y especificar como hora un valor entre 0 y 23) debemos usar la constante HOUR_OF_DAY
. Por ejemplo:
calendar.set(Calendar.HOUR_OF_DAY, 6);
DATE
no es lo que parece
Otra constante engañosa: DATE
es equivalente a DAY_OF_MONTH
y representa el día del mes. Haríamos bien en no usarla nunca, pero podríamos encontrarla en código ajeno, y conviene recordar qué significa.
Métodos iguales que no hacen lo mismo
Tanto Date
como Calendar
tienen un método getTime()
, pero ojo, porque devuelven cosas diferentes. El getTime()
de Date
devuelve un long
con el tiempo interno (los famosos milisegundos transcurridos desde las 00:00 GMT del 1 de enero de 1970), mientras que el getTime()
de Calendar
devuelve un objeto Date
. Si queremos el long
con los milisegundos, debemos usar getTimeInMillis()
Horas en SimpleDateFormat
Vamos con SimpleDateFormat
y la forma de especificar un patrón. La letra «h» (minúscula) indica la hora en formato AM/PM, mientras que la «H» la indica en formato 24H. El siguiente formateador:
SimpleDateFormat sdf = new SimpleDateFormat("hh:mm");
Nos devolverá 6:30, tanto si le pasamos un Date
con la hora establecida a las 6:30 como a las 18:30. Si queremos usar el formato 24H (más habitual por estos lares), debemos usar:
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
SimpleDateFormat
tiene otras letras para especificar la hora, pero creo que podemos ignorarlas, ya que nunca las he usado, y no creo que alguien lo haga por accidente («k» para 1-24 y «K» para 0-11).
No hay validaciones
Un comportamiento curioso de Calendar
y SimpleDateFormat
es que no hay limitación en el rango de valores. Es decir, podemos indicar fechas como el 31 de febrero, o el 40 de mayo. Como internamente la fecha en el fondo es un long
con los milisegundos transcurridos desde la referencia 0, el valor incorrecto se convierte automáticamente en uno correcto. Así, el 31 de febrero correspondería al 3 de marzo (o el 2, si es un año bisiesto), y el 40 de mayo al 9 de junio (fecha hasta la que no debemos quitarnos el sayo). Eso quiere decir que no podemos usar estas clases para realizar algún tipo de validación en los rangos de valores de una entrada (como un formulario web, por ejemplo).
XML dateTime
Termino con algo que no es un «gotcha» sino una limitación. No hay forma de especificar un patrón para SimpleDateFormat
que cumpla con el estándar XML, si queremos especificar la zona horaria. Si no especificamos la zona, basta con el siguiente patrón:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
o incluso
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
si queremos llegar hasta el milisegundo. Pero si necesitamos especificar la zona horaria, tenemos un problema. SimpleDateFormat
nos ofrece dos formas de pintar o parsear la zona horaria: «z», que es una representación textual larga y no nos sirve, y «Z» que es más corta y casi nos sirve. El «casi» es porque SimpleDateFormat
representa la zona horaria con como la diferencia con GMT (Greenwich) en formato «+/-HHmm», es decir, el horario peninsular de invierno es «+0100». Pero en el estándar XML, las horas y minutos de diferencia están separados por dos puntos («:»), de forma que el ejemplo anterior se representaría como «+01:00».
Si la zona horaria va a ser siempre GMT, podemos aprovecharnos de que en el estándar XML, dicha zona horaria se representa como «Z», y hacer lo siguiente:
public String toXmlString(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
return sdf.format(date);
}
Pero si debemos usar otras zonas, y no podemos usar alguna librería más adecuada (el no uso de determinadas librerías puede ser un dato del problema), no nos queda más remedio que implementar algo más manual.
No hay comentarios:
Publicar un comentario