Introducción a Joda Time

5
29725

Índice de contenidos

  1. Introducción
  2. Configurar nuestra aplicación para utilizar Joda Time
  3. Conceptos clave
  4. Trabajar con fechas
    1. Construcción de fechas
    2. Conversión entre fechas de Joda Time y fechas del JDK
    3. Campos de fechas y propiedades
    4. Formateo de fechas
  5. Trabajar con intervalos y períodos
    1. Intervalos
    2. Períodos
    3. Formateo de períodos
  6. Conclusiones
  7. Enlaces útiles

1. Introducción

Joda Time es un API Java que permite trabajar con fechas de una forma más sencilla, potente y eficiente que el API estándar de fechas de Java. Joda Time incluye algunos conceptos como intervalos, duraciones y períodos, que están bastante mal soportados en el API estándar.

Joda Time es bastante «viejo», lleva desarrollándose al menos desde el 2002 y la última actualización es del año pasado, pero muchos desarrolladores lo desconocen y a veces resulta muy útil para realizar ciertas operaciones complejas con fechas (complejas si no utilizamos este API, claro).

Las características principales de Joda Time son:

  • Facilidad de uso, con métodos de acceso directos a los campos de una fecha.
  • Facilidad de extensión. Extender la clase Calendar del JDK puede resultar muy complicado si necesitásemos utilizar un sistema de calendario personalizado. Joda-Time soporta múltiples calendarios (8 actualmente) por medio de un sistema extensible basado en la clase Chronology. A pesar de todo, la mayoría de los mortales probablemente nunca necesitemos extender ninguno de los dos sitemas en nuestro trabajo cotidiano.
  • Funcionalidades avanzadas para el cálculo y formateo de fechas, que en muchos casos son difíciles de imitar con el API estándar de Java.
  • Es muy fácil la conversión de fechas entre Joda Time y el JDK.
  • Una documentación bastante buena del uso general del API y un javadoc detallado que nos permiten utilizar funcionalidades avanzadas.
  • Integración con otros sistemas como Hibernate o los tags de JSP.
  • Es Open Source, bajo la licencia Apache License Version 2.0.

2. Configurar nuestra aplicación para utilizar Joda Time

Para utilizar el API de Joda Time en nuestra aplicación sólamente tendremos que descargarnos el .jar correspondiente (que ocupa unos 2 MB) y añadirlo a nuestro classpath. O, mejor aún, si estamos utilizando Maven para gestionar nuestro proyecto, tendremos que añadir la dependencia siguiente a nuestro «pom.xml»:

<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>1.6</version>
</dependency>

3. Conceptos clave

Concepto
Descripción
Interfaz / Clase Abstracta
Implementaciones
principales
Instante Representa un instante concreto en la línea temporal (una fecha), con precisión de milisegundos.
Un instante se representa internamente como el número de milisegundos transcurridos desde «1970-01-01T00:00Z»
ReadableInstant DateTime
MutableDateTime
Instant
Intervalo Representa un intervalo de tiempo entre dos instantes que utilizan la misma cronología y zona horaria.
Los intervalos son abiertos por la derecha, es decir, incluyen su instante inicial, pero no el final.
ReadableInterval Interval
MutableInterval
Duración Representa un período de tiempo en milisegundos. No dependen de una cronología ni zona horaria. ReadableDuration Duration
Período Representa un período de tiempo en términos de años, meses, semanas, días, horas, minutos, segundos y milisegundos.
Se diferencia de una duración en que no es exacto en términos de milisegundos, por ejemplo, sumar un período de «1 mes» al día «16 de Febrero» devolverá «16 de Marzo», que no es lo mismo que sumar 30 días o 2.592×10⁶ milisegundos.
Hay períodos de un solo campo (Years, Days…) o de varios cualesquiera (Period).
ReadablePeriod Period
MutablePeriod
Years
Months
Days
Hours
Minutes
Parcial Representa una fecha de forma incompleta y sin ninguna zona horaria.
Por ejemplo, un parcial podría representar el día «7 de Julio» (sin especificar el año), las «12:00» o «Enero de 1994».
ReadablePartial LocalDate
LocalTime
LocalDateTime
Partial
Cronología Representa un sistema de calendario/medición del tiempo, como la clase Calendar del JDK.
Para la mayoría de las aplicaciones, no necesitaremos utilizar estas clases, y nos valdrá con utilizar la implementación por defecto: ISOChronology.
Chronology ISOChronology
GregorianChronology
JulianChronology
Zona horaria Una zona horaria se aplica con el patrón Decorator sobre una cronología. DateTimeZone (clase abstracta) No se usan directamente, sino a través de métodos factoría de DateTimeZone

4. Trabajar con fechas

Para todos los ejemplos que siguen a partir de ahora, supondremos que nuestro Locale es «es-ES».

4.1. Construcción de fechas

// Fecha y hora actuales
DateTime date = new DateTime();
		
// Especificar una fecha en formato ISO
date = new DateTime("2010-06-25"); // horas, minutos, segundos y milisegundos a 0
date = new DateTime("2010-06-25T13:30:00"); // milisegundos a 0
	
// Especificar indicando año, mes, día, horas, minutos, segundos y milisegundos
date = new DateTime(2010, 6, 25, 13, 30, 0, 0);

// Especificar zona horaria
date = new DateTime(DateTimeZone.forID("Europe/London"));
		
// Especificar cronología
date = new DateTime(BuddhistChronology.getInstance());

4.2. Conversión entre fechas de Joda Time y fechas del JDK

// de Joda a JDK
DateTime dt = new DateTime();
java.util.Date     date     = dt.toDate();
java.util.Calendar calendar = dt.toCalendar(Locale.US);

// de JDK a Joda
dt = new DateTime(date);
dt = new DateTime(calendar);

4.3. Campos de fechas y propiedades

Joda Time separa la representación de un instante de tiempo (DateTime) del cálculo de los campos de calendario. Es decir, nosotros tendremos una fecha (representada por un número de milisegundos transcurridos desde «1970-01-01T00:00Z») y, cuando queramos obtener, por ejemplo, el día de la semana, se realizará el cálculo en función de la cronología y zona horaria para obtener el valor correspondiente.

DateTime dt = new DateTime();
		
// Obtener el día de la semana (lunes, martes...)
int dayOfWeek = dt.getDayOfWeek();
		
// Obtener el número de minutos transcurridos en el día
int minuteOfDay = dt.getMinuteOfDay();
		
// Obtener la semana del año
int weekOfYear = dt.getWeekOfWeekyear();

La clase DateTimeConstants contiene una serie de constantes enteras con los valores de los días de la semana, los meses, etc.

Además de obtener directamente los valores  para los  diferentes campos de una fecha, la clase DateTime dispone de métodos para recuperar estos campos como una  propiedad (de tipo DateTime.Property). Estas propiedades permiten realizar operaciones adicionales sobre los campos de la fecha. En la documentación oficial de Joda Time se encuentra lalista completa de los campos disponibles para una fecha.

DateTime dt = new DateTime();
		
// Obtener la propiedad correspondiente al día de la semana
Property dayOfWeek = dt.dayOfWeek();
		
// Obtener el día de la semana como una cadena localizada según la zona horaria ("lunes", "martes"...)
String sDayOfWeek = dayOfWeek.getAsText();
		
// Obtener la fecha correspondiente al lunes de la semana actual (las semanas comienzan en lunes)
DateTime lunes = dayOfWeek.setCopy(DateTimeConstants.MONDAY);

Casi todas las clases de Joda Time son «inmutables». Por ejemplo, cuando modifiquemos los campos de una fecha, realmente estaremos obteniendo una nueva fecha con el valor para ese campo modificado. En el ejemplo anterior, el método setCopy obtiene una «copia» de la fecha con el valor modificado, pero no modifica para nada la fecha original.

No siempre será necesario utilizar la clase DateTime.Property para realizar operaciones sobre una fecha. Las operaciones más frecuentes disponen de métodos directos en la clase DateTime:

// Sumar dos meses a una fecha
DateTime dosMesesDespues = dt.plusMonths(2);
		
// Obtener la fecha correspondiente al lunes de la semana actual
DateTime lunes = dt.withDayOfWeek(DateTimeConstants.MONDAY);

4.4. Formateo de fechas

La clase DateTimeFormatter permite convertir cadenas en fechas y viceversa, utilizando una representación específica de las mismas.

// Crear un formatter con una representación específica
DateTimeFormatter fmt = DateTimeFormat.forPattern("dd-MMMM-yyyy");
		
// Obtener un formatter localizado
DateTimeFormatter americanFmt = fmt.withLocale(Locale.US);
		
// Obtener una fecha a partir de su representación
DateTime dt = fmt.parseDateTime("25-junio-2010");
		
// Escribir una fecha con el formato especificado
System.out.println(fmt.print(dt)); // escribe "25-junio-2010"
System.out.println(americanFmt.print(dt)); // escribe "25-June-2010"

// Métodos de acceso directo
System.out.println(dt.toString("dd-MMMM-yyyy")); // escribe "25-junio-2010"
System.out.println(dt.toString("dd-MMMM-yyyy", Locale.US)); // escribe "25-June-2010"

También es posible crear formatos utilizando la clase DateTimeFormatterBuilder, aunque para la mayoría de los casos, no será necesario. Esta clase funciona de forma similar a PeriodFormatterBuilder, que se utiliza para crear «formateadores» para los períodos, de la cual se muestra un ejemplo al final del siguiente apartado.

5. Trabajar con intervalos y períodos

5.1. Intervalos

// Crear un intervalo entre el 1 de Enero y la fecha actual
DateTime inicio = new DateTime(2010, 1, 1, 0, 0, 0, 0);
DateTime fin = new DateTime();
Interval interval = new Interval(inicio, fin);

// Recuperar el inicio y fin del intervalo
DateTime i = interval.getStart();
DateTime f = interval.getEnd();
		
// Comprobar si una fecha determinada está dentro del intervalo
boolean ok = interval.contains(new DateTime("2010-04-13"));
		
// Conversión a duración o período
Duration duration = interval.toDuration();
Period period = interval.toPeriod();

5.2. Períodos

// ----- Períodos de un único campo -----
// Años desde el 16/03/1976
Years years34 = Years.yearsBetween(new DateTime("1976-03-16"), new DateTime());

// Obtener el número de meses desde un intervalo
Interval interval = new Interval(new DateTime("2010-01-01"), new DateTime("2010-12-01"));
Months months11 = Months.monthsIn(interval);

// Constante predefinida
Days days1 = Days.ONE;

// Especificar un número de días
Days days15 = Days.days(15);

// ----- Períodos de varios campos -----
// 1 año, 6 meses, 2 semanas, 3 días y 12 horas
Period period = new Period(1, 6, 2, 3, 12, 0, 0, 0);

// Años, meses, semanas, días, horas, minutos, segundos y milisegundos desde el 01/01/2000
Period period2 = new Period(new DateTime("2000-01-01"),	new DateTime());

// Años, meses y días desde el 01/01/2000
Period period3 = new Period(new DateTime("2000-01-01"),	new DateTime(),	PeriodType.yearMonthDay());

Como puede verse en los últimos ejemplos, cuando creamos un período sin especificar su tipo, se considera el tipo estándar, que incluye los valores para todos los campos. Si queremos un período que no incluya algunos campos (como las semanas, horas, etc.) debemos especificarlo de manera explícita.

5.3. Formateo de períodos

Para terminar vamos a ver un ejemplo de cómo utilizar un PeriodFormatter para convertir una cadena del tipo «HH:mm:ss» a un período que contendrá las horas, minutos y segundos correspondientes, y viceversa. Para crear el Formatter utilizaremos los métodos de la factoría PeriodFormatterBuilder, que permite construir formatos bastante complejos.

// Crear el PeriodFormatter
PeriodFormatter durationFormatter = new PeriodFormatterBuilder()
	.minimumPrintedDigits(2) // Número de dígitos que se mostrarán en la salida para los campos siguientes
	.printZeroAlways() // Indica que deben mostrarse los campos siguientes aunque su valor sea 0
	.appendHours()
	.appendSeparator(":")
	.appendMinutes()
	.appendSeparator(":")
	.appendSeconds()
	.toFormatter();

// Obtener un período a partir de un String
Period period = durationFormatter.parsePeriod("07:12:38");
		
// Obtener el String que representa el período
String sPeriod = durationFormatter.print(period);

En la construcción del PeriodFormatter, se han utilizado las funciones «minimunPrintedDigits» y «printZeroAlways» al principio. Estos valores se utilizarán para cada campoque se añada después, aunque podrían modificarse para un campo concreto. Por ejemplo, si quisiéramos que para los minutos sólo se imprimiese un dígito y no apareciesen los segundos si su valor fuese cero, podríamos modificar el código anterior como sigue:

PeriodFormatter durationFormatter = new PeriodFormatterBuilder()
	.minimumPrintedDigits(2) // Número de dígitos que se mostrarán en la salida para los campos siguientes
	.printZeroAlways() // Indica que deben mostrarse los campos siguientes aunque su valor sea 0
	.appendHours()
	.appendSeparator(":")
	.minimumPrintedDigits(1) // Para los minutos queremos mostrar sólamente 1 dígito
	.appendMinutes()
	.appendSeparator(":") // Si los segundos son 0, tampoco se mostrará este separador
	.minimumPrintedDigits(2) // Pero para los segundos queremos mostrar de nuevo 2
	.printZeroNever() // Si los segundos son 0, no se mostrarán
	.appendSeconds()
	.toFormatter();

6. Conclusiones

A la vista de lo expuesto en el tutorial, se pueden extraer las siguientes conclusiones sobre la manipulación de fechas en Java:

  • Si nuestra aplicación va a utilizar sólamente cálculos comunes con fechas, no hay demasiada diferencia entre utilizar la JDK o Joda Time, por lo cual es más que recomendable utilizar el API estándar.
  • En el caso de que tengamos que realizar cálculos o conversiones más complejas, Joda Time nos ofrece un API muy potente capaz de cubrir probablemente todas nuestras necesidades.
  • La mayoría de los frameworks utilizados en Java (JSF, Spring…) utilizan las clases del JDK para trabajar con fechas. Si optamos por utilizar Joda Time en nuestras aplicaciones, no deberíamos tener muchos problemas, ya que la conversión entre ambos tipos resulta muy sencilla.
  • Joda Time puede hacer que nuestro código sea más sencillo, legible y fácil de entender.
  • Si necesitamos trabajar con conceptos como duraciones o períodos, entonces es casi obligatorio el uso de Joda Time, ya que éstos conceptos no están ni mucho menos tan bien soportados en el JDK.

Y eso es todo. Espero que este tutorial os resulte útil la próxima vez que tengáis que manipular fechas o duraciones en vuestras aplicaciones.

Un saludo.

7. Enlaces útiles

5 COMENTARIOS

  1. Gracias por tu comentario, Ana.

    Ahora con Java 8, Joda Time ha pasado a ser historia… (salvo en proyectos que no puedan usar la última versión de Java).

    Te recomiendo echarle un vistazo al nuevo API de fechas en Java 8.

    También hay un curso excelente para actualizar a Java 8 impartido por el gran David Gómez: https://www.autentia.com/novedades-java-8/

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad