Manejo de fechas en Java II

Leer Manejo de Fechas en Java (I)

Por Carlos Zuluaga Mayo 1 de 2007

Operaciones

No todo debe ser tan complejo y abstracto como lo es con las clases que vimos anteriormente. Java posee otras clases que nos facilitan realizar operaciones como suma, resta, obtener un día y hora exactos, entre otras.
Estas funcionalidades las obtenemos de dos clases en particular; una es abstracta: java.util.Calendar (a partir de ahora me referiré a ella como Calendar) y permite obtener campos enteros como día, mes y año de objetos de tipo java.util.Date o que hereden de él. La otra java.util.GregorianCalendar (a partir de ahora simplemente GregorianCalendar) es una implementación del calendario gregoriano que es usado en casi todo el mundo (es el que conocemos).

Calendar
Según la documentación del API de java, la clase Calendar es una clase abstracta base para convertir entre un objeto de tipo Date (java.util.Date) y un conjunto de campos enteros como YEAR (año), MONTH (mes), DAY (día), HOUR (hora), etc. Una subclase de Calendar representa una fecha de acuerdo a las reglas de un calendario específico. La plataforma provee una subclase concreta de Calendar: GregorianCalendar. Futuras subclases podrían representar varios tipos de calendarios lunares usados en diferentes lugares del mundo.
Esto puede haber sonado un tanto abstracto pero no nos confundamos. La clase Calendar tiene mucho del comportamiento que esperaríamos de la clase java.util.Date, es decir, cuando obtenemos una instancia de la clase Calendar obtenemos un instante de tiempo específico con gran precisión similar a lo que obtenemos con la clase date. Sí es cierto, podemos decir aquí que los milisegundos juegan un papel fundamental en esta clase; pero el verdadero sentido de la clase Calendar no es obtener un instante de tiempo sino extraerle datos.
Recordemos que la clase java.util.Date tiene métodos que permiten obtener el año, mes y día, pero estos métodos están obsoletos precisamente por que para eso existe Calendar y de hecho cuando usamos el método getYear() de la clase java.util.Date esta recurre a las funcionalidades que posee la clase Calendar.
Hay que mencionar también que obtener un instante de tiempo específico y diferente del actual es supremamente sencillo con esta clase, indicándole simplemente el día, mes y año con que se desea trabajar, o se puede especificar aún más dando hora, minuto y segundo deseado. Veamos:
El método getInstance() de la clase nos devuelve una subclase de Calendar con el tiempo ajustado a la hora actual, y usamos el método set(args…) para forzarlo a tomar la fecha deseada:

Calendar ahoraCal = Calendar.getInstance();
System.out.println(ahoraCal.getClass());
ahoraCal.set(2004,1,7);
System.out.println(ahoraCal.getTime());
ahoraCal.set(2004,1,7,7,0,0);
System.out.println(ahoraCal.getTime());

La primera línea devuelve una instancia de GregorianCalendar con la fecha y hora actual y el método getTime() retorna un objeto de tipo java.util.Date que se muestra en consola posteriormente.
En la tercera línea se usa el método set para ajustar la fecha al "7 de enero de 2004" y en la quinta ponemos la misma fecha pero esta vez ajustando también la hora, minutos y seg qundos, comoueda: "7 de la noche del 7 de enero de 2004".
La salida en consola será (note que tiene el formato de java.util.Date):

class java.util.GregorianCalendar
Sat Feb 07 19:55:47 GMT-05:00 2004
Sat Feb 07 07:00:00 GMT-05:00 2004

Extraer los datos
La clase Calendar tiene un único método get para obtener todos sus datos para lo cual se ayuda de una serie de atributos constantes que permiten obtener o ajustar un atributo determinado de la fecha; los más importantes son (al lado los valores que representan):
-YEAR: Año.
-MONTH: Mes.
-DATE, DAY_OF_MONTH: Día del mes.
-DAY_OF_WEEK: Día de la semana entre 1 (MONDAY) y 7 (SATURDAY).
-HOUR: Hora antes o después del medio día (en intervalos de 12 horas).
-HOUR_OF_DAY: Lo hora absoluta del día (en intervalos de 24 horas).
-MINUTE: El minuto dentro de la hora.
-SECOND: El segundo dentro del minuto.

Están también los atributos que representan los meses, como: JANUARY, MARCH, JUNE, DECEMBER, etc. que van desde 0 (JANUARY) hasta 11 (DECEMBER).
También hay atributos que representan los días: SUNDAY, TUESDAY, SATURDAY, etc. estos empiezan con 1 (SUNDAY) y van hasta 7 (SATURDAY).
Hay más atributos pero con estos es suficiente para los objetivos de este artículo, si deseas concerlos todos recurre a la documentación del lenguaje.
Para extraer algún atributo se usa el método get pasándole como parámetro alguna de las constantes que vimos anteriormente y el método devuelve siempre un dato de tipo int; así para obtener el año , mes, día y hora, usaríamos el siguiente código:

System.out.println("ANYO: "+ahoraCal.get(Calendar.YEAR));
System.out.println("MES: "+ahoraCal.get(Calendar.MONTH));
System.out.println("DIA: "+ahoraCal.get(Calendar.DATE));
System.out.println("HORA: "+ahoraCal.get(Calendar.HOUR));
if (ahoraCal.get(Calendar.MONTH) == Calendar.JUNE){
 System.out.println("ES JUNIO");
}else{
 System.out.println("NO ES JUNIO");
}

Si el objeto tiene la fecha equivalente a 5:30 p.m. del 22 de junio de 2004 se ve la siguiente salida:

ANYO: 2004
MES: 5
DIA: 22
HORA: 5
ES JUNIO

Modificar un atributo
Modificar un atributo de Calendar es tan sencillo como obtenerlo, solamente es necesario usar el método set(int atributo, int valor), en donde atributo es una de las constante mencionadas anteriormente y valor es la cantidad que se le quiere asignar. Por ejemplo: ahoraCal.set(Calendar.MONTH,Calendar.JANUARY) o ahoraCal.set(Calendar.YEAR, 1980) ajustarían la fecha almacenada en el objeto ahoraCal a enero o al año 1980 sin modificar ninguno de los otros atributos.
Aclaremos esto con un ejemplo. Mi cumpleaños es en octubre 27 :-), y deseo saber que día lo celebraré en el 2010; para eso obtengo una instancia de Calendar (que siempre devuelve un objeto del tipo GregorianCalendar) y la ajusto hasta el 27 de octubre de 2010, luego obtengo el nombre del dia, veamos:

Calendar cumpleCal = Calendar.getInstance();
cumpleCal.set(2010,9,27); //La hora no me interesa y recuerda que los meses van de 0 a 11
int dia = cumpleCal.get(Calendar.DAY_OF_WEEK);
System.out.println(dia); //Día 4 = WEDNESDAY = MIÉRCOLES

La salida es: 4, lo que quiere decir que en el 2010 el 27 de octubre será un día miércoles (así que probablemente lo celebre el viernes 29 :-));

Clase GregorianCalendar y operaciones

Realizar operaciones como sumar o restar días no es algo que dependa directamente de Calendar sino más bien de una subclase de esta que implemente algún tipo de calendario usado, pues no todos los calendarios tienen 12 meses ni años de 365 días como el que nosotros (en casi todo occidente) usamos.
Este calendario usado en occidente, llamado gregoriano fue adoptado por primera vez en 1582 por el imperio romano (o aproximadamente) y posteriormente se fue adoptando en muchos otros países, por ejemplo 1752 en Gran Bretaña y 1918 en Rusia.
A grandes rasgos sabemos que el calendario gregoriano consta de años que son definidos por cada traslación (vuelta de la tierra alrededor del sol), cada año tiene doce meses de los cuales 7 tienen 31 días, 4 tienen 30 días y 1 tiene 28 días excepto en años bisiestos que tiene 29. Estos años bisiestos se implantaron para corregir el desfase que tenemos cada cuatro años (un año real dura 365 días y 6 horas aproximandamente), regla completa para los años bisiestos según en calendario gregoriano es la siguiente:
"Un año es bisiesto si es divisible por 4, a menos que sea divisible por 100 y no por 400".
Todo esto quizá suene un poco tonto, pero es absolutamente necesario tenerlo claro para entender el funcionamiento de la clase GregorianCalendar.
GregorianCalendar es una subclase de Calendar y es la implementación directa del calendario gregoriano (de hecho es la única implementación de un calendario en jdk1.4) tal y como lo conocemos hoy día. Es con esta clase con la que podemos sumar 2 ó 3 días a una fecha sin preocuparnos por desbordamientos o recalcular meses o años, pues ella lo hace automáticamente tomando en cuenta las reglas en los párrafos anteriores.
De igual forma podemos obtener información como el día de la semana o la semana del año que fue una fecha determinada.

Los métodos roll() y add()
Anteriormente vimos los métodos set() y get() de la clase Calendar para obtener fechas y los datos de esas fechas, ahora veremos los métodos add() y roll() que nos permiten avanzar un tiempo exacto sobre los datos obtenidos anteriormente.
El método add(CONSTATE, valor) suma algebraicamente valor a una fecha; el valor a sumar asume el significado dado por CONSTANTE, que es una de las definidas para la clase y que se mencionaron en la sección anterior (MONTH, YEAR, SECOND, etc).
Por ejemplo agreguemos 3 días y 2 meses a la fecha actual:

Calendar hoy = Calendar.getInstance();
hoy.add(Calendar.DATE, 3);
hoy.add(Calendar.MONTH, 2);
System.out.println(hoy.getTime());

Ahora restemos 5 años y 50 días (Prúebalo en tu PC):

Calendar hoy = Calendar.getInstance();
hoy.add(Calendar.YEAR, -5);
hoy.add(Calendar.DATE, -50);
System.out.println(hoy.getTime());

¿Qué, restar 50 días?
Pues sí, resulta que la clase GregorianCalendar es “inteligente” al momento de hacer estos cálculos y se basa en ciertas reglas (2 según la documentación) para realizar estas operaciones; tal y como nosotros lo haríamos al sumar 50 días, cuando lleguemos al final del mes actual empezamos a contar a partir del mes siguiente y lo mismo si cambiara el año, es decir, cuando llegue al límite superior o inferior de uno de estos intervalos se empezará a afectar el intervalo siguiente según la lógica de la operación.
Para aclarar lo anterior debemos conocer el orden de los campos que se han mencionado en el artículo (solo pondré los más significativos); aquí van en orden descendente:
YEAR
MONTH
DATE, DAY_OF_MONTH
HOUR
MINUTE
SECOND

Cuando estamos sumando los campos se afectan de abajo hacia arriba (SECOND – YEAR) y restando el orden es inverso (YEAR – SECOND), así que al momento de restar 50 días se decrementa el atributo DAY_OF_MONT (o DATE) hasta 1, luego se decrementa en 1 el atributo MONTH y DAY_OF_MONTH inicia en el número de días del mes; si antes de decrementar MONTH está en su límite inferior (JANUARY) YEAR también debe ser recalculado (disminuye en 1) y MONTH regresa a su límite superior al igual que DATE.
Que enredo!
Es cierto, todo esto puede parecer un laberinto (especialmente por que no me explico bien), pero estoy seguro que con un poco de código se despejará el panorama.

Para todos los ejemplos partiremos del 10 de noviembre de 1998 a las 7:00am.

Calendar cal = Calendar.getInstance();
cal.set(1998,10,10,7,0,0);
 
cal.add(Calendar.SECOND,52);
cal.add(Calendar.HOUR, 2);
cal.add(Calendar.MONTH, -3);
cal.add(Calendar.YEAR,1);
//Así la fecha seía 1999/8/10 09:00:52 (Agosto 10 de 1999)
System.out.println(cal.getTime());

Aquí ningún atributo llegó a su límite superior o inferior y por tanto la nueva fecha se calcula muy fácilmente.

Para ver como incide una sola operación en varios atributos, sumaremos a la fecha 70 minutos y 22 días; como el límite superior de los minutos (60) es sobrepasado, se debe incrementar el atributo inmediatamente superior (HOUR), igual que al sobrepasar el límite de días para el mes (noviembre tiene 30 días) se incrementa el año. Así finalmente tendremos el 2 de diciembre de 1998 a las 8:10 am.

cal.add(Calendar.MINUTE, 70);
cal.add(Calendar.DATE, 22);
//Así la fecha seía 1998/12/2 08:10:00 (Diciembre 2 de 1998)
System.out.println(cal.getTime());

Método roll(CONSTATE, valor)
Esté método tiene un funcionamiento similar a add() excepto por que no modifica ningún otro atributo, es decir, cuando el atributo dado por CONSTANTE llega a su límite inferior o superior cambia al límite contrario pero sin modificar sus atributos siguientes. Seguramente suena un tanto extraño este funcionamiento, pues con el método set() podemos asignar un valor directamente; según la documentación este método se hizo para facilitar la funcionalidad en las interfaces de usuario donde hay que especificar una fecha y tienen botones para incrementar o decrementar alguno de los datos (creo que todos los hemos visto).
Si en el ejemplo anterior en vez de add() usamos roll() ya no obtendremos Diciembre 2 de 1998 a 8:10 am, sino Noviembre 2 de 1998 a las 7:10 am.
Note que solo cambian los valores que se especificaron directamente.

cal.roll(Calendar.MINUTE, 70);
cal.roll(Calendar.DATE, 22);
//Así la fecha seía 1998/11/2 07:10:00 (Noviembre 2 de 1998)
System.out.println(cal.getTime());

En verdad el uso de roll no es muy común excepto pues en ambientes gráficos, los métodos que en verdad deben ser considerados, estudiados y probados son set(), get() y add().

Esto es todo por esta entrega, en el próximo artículo veremos como formatear una fecha para mostrarla de forma comprensible, el parseo y algunos detalles no tan comunes de las clases Calendar y GregorianCalendar.

Si no se indica lo contrario, el contenido de esta página se ofrece bajo Creative Commons Attribution-ShareAlike 3.0 License