Clase Calendar de Java

De ChuWiki

Veamos algunos ejemplos sencillos con la clase Calendar de Java.


Fecha/Hora actual[editar]

Calendar es una clase abstracta, por lo que no podemos hacer un new de ella. La forma de obtener una instancia es llamando al método getInstance(), que nos devolverá alguna clase hija de Calendar inicializada con la fecha/hora actual.

Calendar today = Calendar.getInstance();
System.out.println("Today is " + today.getTime());

La clase hija que realmente nos va a devolver este método es GregorianCalendar, correspondiente al calendario estándar para el mundo occidental.

Para mostrar la fecha/hora por pantalla, podríamos intentar directamente

System.out.println("Today is " + today);

pero nos daría una salida que no es muy legible

today is java.util.GregorianCalendar[time=1394883514531,areFieldsSet=true,areAllFieldsSet=true,
lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/Paris",
offset=3600000,dstSavings=3600000,useDaylight=true,transitions=184,
lastRule=java.util.SimpleTimeZone[id=Europe/Paris,offset=3600000,dstSavings=3600000,
useDaylight=true,startYear=0,startMode=2,
startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,
endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]],firstDayOfWeek=2,
minimalDaysInFirstWeek=4,ERA=1,YEAR=2014,MONTH=2,WEEK_OF_YEAR=11,WEEK_OF_MONTH=2,
DAY_OF_MONTH=15,DAY_OF_YEAR=74,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=3,AM_PM=1,HOUR=0,
HOUR_OF_DAY=12,MINUTE=38,SECOND=34,MILLISECOND=531,ZONE_OFFSET=3600000,DST_OFFSET=0]

El método getTime() devuelve el Calendar convertido a Date, que nos daría una salida legible por pantalla al pasarlo a System.out.println(), ese es el motivo por el que lo hemos usado

today is Sat Mar 15 12:41:10 CET 2014

Fijar una fecha/hora[editar]

Para obtener un Calendar en una fecha hora concreta tenemos dos opciones. Una de ellas, sabiendo que lo que realmente vamos a obtener es un GregorianCalendar, es hacer un new GregorianCalendar(...) pasando como parámetros el año, mes, día, hora, minuto, segundo. No es necesario pasar todo, ya que GregorianCalendar tiene varios constructores con menos parámetros

Calendar sameDate = new GregorianCalendar(2010, Calendar.FEBRUARY, 22, 23, 11, 44);
System.out.println("Some Date : " + sameDate.getTime());

Es importante tener en cuenta un detalle. Para Calendar los meses van de 0 a 11, es decir, 0 es Enero y 11 es Diciembre. Por ello, en el parámetro correspondiente al mes, si queremos meter Febrero, debemos meter un 1, en vez de un 2 que es los que nos dictaría la costumbre. Para evitar estas confusiones, siempre es bueno usar las constantes que nos define la clase Calendar, como Calendar.FEBRUARY en el ejemplo.

La otra opción para obtener un Calendar en una fecha/hora concreta es llamar a su método set() para fijar los campos que queramos cambiar. El método set() admite dos parámetros, uno para identificar el campo concreto a cambiar (año, mes, día, hora, minuto, segundo, milésimas de segundo) y un segundo parámetro que sería el valor a poner. El siguiente código muestra bastantes de las posibilidades

Calendar sameDate = Calendar.getInstance();

sameDate.set(Calendar.YEAR, 2010);
// Month. 0 is January, 11 is November
sameDate.set(Calendar.MONTH, Calendar.AUGUST);
sameDate.set(Calendar.DAY_OF_MONTH, 23);

// Either 12-hour clock plus AM/PM
sameDate.set(Calendar.HOUR, 10);
sameDate.set(Calendar.AM_PM, Calendar.PM);
// or 24-hour clock
sameDate.set(Calendar.HOUR_OF_DAY, 22);

sameDate.set(Calendar.MINUTE, 36);
sameDate.set(Calendar.SECOND, 22);
sameDate.set(Calendar.MILLISECOND, 123);

System.out.println("Some Date : " + sameDate.getTime());

Calendar define constantes para todos los nombres de los posibles campos y son estas constantes las que pasamos como primer parámetro. Adviértase que para el mes nuevamente hemos usado las constantes definidas como Calendar.AUGUST, en vez de directamente un número de mes ( 7 para Agosto ), que puede llevar a confusión.

Aparte de los campos evidentes, vemos por ejemplo que la hora se puede fijar de dos formas:

  • Pasando una hora de 0 a 11 y pasando el valor AM/PM
  • Pasando una hora de 0 a 24.

No los mostramos en el ejemplo, pero hay más campos, como día de la semana, semana del año, semana del mes, etc. El día podría fijarse con cualquiera de estas combinaciones (ver API de Calendar).

  • YEAR + MONTH + DAY_OF_MONTH
  • YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
  • YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
  • YEAR + DAY_OF_YEAR
  • YEAR + DAY_OF_WEEK + WEEK_OF_YEAR

Fijando los valores de cualquiera de esas combinaciones quedaría perfectamente determinada la fecha. En el ejemplo no hemos metido tantas variantes, nos hemos ido a la más sencilla que es meter YEAR + MONTH + DAY_OF_MONTH


Leer los campos de la fecha/hora[editar]

Calendar tiene un método get() al que se pasa como parámetro la constante que indica el campo que queremos obtener, es decir, las mismas constantes que se usan en set(). Aparte de los campos evidentes, tenemos campos que puede ser útiles, como el día de la semana (Lunes a Domingo), el número de semana del año, el número de semana del mes, etc. Un ejemplo más o menos completo puede ser el siguiente

Locale locale = Locale.getDefault();
// Locale locale = Locale.GERMAN;
Calendar today = Calendar.getInstance();

System.out.println("Year : " + today.get(Calendar.YEAR));

System.out.println("Month (0 is January): " + today.get(Calendar.MONTH));

System.out.println("Month (String): "
      + today.getDisplayName(Calendar.MONTH, Calendar.SHORT, locale));

System.out.println("Day of Month : " + today.get(Calendar.DAY_OF_MONTH));

System.out.println("Day of Week (0 is Sunday): "
      + today.get(Calendar.DAY_OF_WEEK));

System.out
      .println("Day of Week (String): "
            + today.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG,
                  locale));

System.out.println("Week of Year : " + today.get(Calendar.WEEK_OF_YEAR));

System.out
      .println("Week of Month : " + today.get(Calendar.WEEK_OF_MONTH));

System.out.println("Day of Year : " + today.get(Calendar.DAY_OF_YEAR));

System.out.println("24-hour clock : " + today.get(Calendar.HOUR_OF_DAY));

System.out.println("12-hour clock : " + today.get(Calendar.HOUR));

System.out.println("AM/PM : " + today.get(Calendar.AM_PM));

System.out.println("AM/PM : "
      + today.getDisplayName(Calendar.AM_PM, Calendar.LONG, locale));

System.out.println("Minutes : " + today.get(Calendar.MINUTE));

System.out.println("Seconds : " + today.get(Calendar.SECOND));

System.out.println("MiliSeconds : " + today.get(Calendar.MILLISECOND));

Hay campos que no requieren explicación detallada, como año, día del mes, minuto, etc. Pero para otros conviene explicar un poco más.

Por ejemplo, el día de la semana, el mes o si es am/pm nos devuelven un número entero que quizás no nos guste demasiado. El 0 corresponde a Domingo y el 6 a Sábado, el 0 corresponde a Enero y el 11 a Diciembre, el 0 corresponde a am y el 1 a pm. Para obtener un texto más legible y no decirle al usuario, por ejemplo, que está en el mes 0, Calendar tiene un método getDisplayName() que nos devuelve un texto legible para mes, día de la semana o AM/PM. Este método admite tres parámetros

  • Campo del que queremos la cadena visible, Calendar.MONTH, Calendar.DAY_OF_WEEK, Calendar.AM_PM,...
  • Si queremos una representación larga o corta. Por ejemplo, para Enero podrían ser sólo tres letras Ene o bien Enero con todas sus letras. Para indicar esto debemos pasar como segundo parámetro una de las constantes Calendar.SHORT o Calendar.LONG
  • El Locale en el que queremos el texto. Este Locale de alguna forma es en qué idioma lo queremos. En mi caso, el Locale por defecto es español y el mes me devolvería Enero, pero si usamos un Locale en Alemán, nos devolvería Januar

El Locale por defecto del sistema operativo se puede obtener con

Locale locale = Locale.getDefault();

y para otros idiomas, pueden usarse las constantes definidas en Locale

Locale locale = Locale.GERMAN;


Sumar y restar fechas[editar]

Calendar tiene un método add() que permite sumar y restar campos a una fecha concreta. Este método admite dos parámetros:

  • El campo (año, mes, día, hora, minuto, segundo), identificado por una de las constantes ya conocidas, al que queremos sumar o resta un valor
  • Valor a sumar o restar. Si el valor es positivo, se suma, si el valor es negativo, se resta.

Veamos un poco de código sencillo que no requiere explicación

Calendar today = Calendar.getInstance();
today.add(Calendar.DAY_OF_MONTH, 20);
System.out.println("Today plus 20 days : " + today.getTime());

today = Calendar.getInstance();
today.add(Calendar.DAY_OF_MONTH, -20);
System.out.println("Today minus 20 days : " + today.getTime());


Comparar fechas[editar]

Calendar permite comparar fechas, indicándonos si una es anterior o posterior a otra. Los métodos son before() y after() para saber si nuestra fecha es anterior o posterior a otra que nos pasen. Adicionalmente, el método compareTo() devuelve un número negativo, cero o positivo según nuestra fecha sea anterior, igual o posterior a la que nos pasen.

El método compareTo() es útil para ordenar Calendar que estén en un array por medio de clases como Arrays.sort(). Para nuestro código, posiblemente nos sea más fácil usar los métodos after() y before().

Veamos un trozo de código sencillo

Calendar today = Calendar.getInstance();
Calendar after = Calendar.getInstance();
after.add(Calendar.HOUR_OF_DAY, 2);

Calendar before = Calendar.getInstance();
before.add(Calendar.HOUR_OF_DAY, -5);

System.out.println("Today is after today+2hours " + today.after(after));
System.out.println("Today is before today+2hours " + today.before(after));
System.out.println("Today is after today-5hours " + today.after(before));
System.out.println("Today is before today-5hours " + today.before(before));

En today obtenemos la fecha/hora actual. Sumamos un par de horas para obtener una fecha/hora after posterior y restamos 5 días para obtener una fecha/hora before anterior a la actual. Luego simplemente hacemos llamadas a today.after() y today.before() sacando el resultado (un boolean) por pantalla. La salida de este código es

Today is after today+2hours false
Today is before today+2hours true
Today is after today-5hours true
Today is before today-5hours false


Días entre dos fechas[editar]

Si queremos calcular la diferencia entre dos fechas concretas, desgraciadamente Java no nos ofrece métodos útiles para hacerlo. Hay librerías externas, como Joda Time que sí tienen estos métodos, pero si no queremos cargar con librerías externas, podemos hacer una aproximación de la siguiente forma.

Calendar tiene un método getTimeInMilis() que nos devuelve el número de milisegundos que han pasado desde el 1 de Enero de 1970 a las 00:00:00 hasta la fecha/hora representada por nuestra instancia de Calendar. Si tenemos dos fecha/hora como Calendar, la diferencia entre ellas en milisegundos se puede calcular fácilmente

Calendar aDay = Calendar.getInstance();
aDay.set(Calendar.MONTH, Calendar.MARCH);

Calendar otherDay = Calendar.getInstance();
otherDay.set(Calendar.MONTH, Calendar.FEBRUARY);
      
long milisec = aDay.getTimeInMillis()-otherDay.getTimeInMillis();

A partir de aquí es fácil convertir esos milisegundos de diferencia a cualquier otra unidad que nos interese, como número de días, de horas, etc. Por ejemplo, para pasar los milisegundos a días debemos

  • dividir por 1000 para pasar los milisegundos a segundos
  • después dividir por 60 para pasar los segundos a minutos
  • después dividir por 60 para pasar los minutos a horas
  • después dividir por 24 para pasar las horas a días

El siguiente código nos daria el número de días entre ambas fechas

long milisec = aDay.getTimeInMillis()-otherDay.getTimeInMillis();
long days = milisec/1000/60/60/24;
System.out.println("Days : "+days);


Zonas horarias[editar]

A la hora de instanciar un Calendar, podemos pasar una zona horaria TimeZone. Esto nos permite obtener la hora en los distintos sitios del mundo. Por ejemplo, mi TimeZone por defecto es España, para comparar con la hora en Canadá podemos hacer el siguiente código

Calendar spanishToday = Calendar.getInstance(TimeZone.getDefault());
Calendar canadianToday = Calendar.getInstance(TimeZone.getTimeZone("Canada/Central"));
      
System.out.println("Spanish hour "+spanishToday.get(Calendar.HOUR_OF_DAY));
System.out.println("Canadian hour "+canadianToday.get(Calendar.HOUR_OF_DAY));

que pude dar algo como esto

Spanish hour 13
Canadian hour 7

Las TimeZone disponibles se pueden obtener con el siguiente código

// Available TimeZones
String [] timeZones = TimeZone.getAvailableIDs();
System.out.println("Available Timezones "+Arrays.toString(timeZones));

o bien se puede hacer una a medida componiendo una cadena así

Calendar aToday= Calendar.getInstance(TimeZone.getTimeZone("GMT+8:00"));
Calendar anotherToday= Calendar.getInstance(TimeZone.getTimeZone("GMT-4:00"));

Enlaces[editar]