Java 8 Fecha – LocalDate, LocalDateTime, Instant

La API de Fecha y Hora es una de las características más importantes del lanzamiento de Java 8. Java carecía de un enfoque consistente para la fecha y la hora desde el principio y la API de Fecha y Hora de Java 8 es una adición bienvenida a las API principales de Java.

¿Por qué necesitamos una nueva API de Fecha y Hora en Java?

Antes de comenzar a examinar la API de Fecha y Hora de Java 8, veamos por qué necesitamos una nueva API para esto. Ha habido varios problemas con las clases existentes relacionadas con la fecha y la hora en Java, algunos de ellos son:

  • Las clases de Fecha y Hora de Java no están definidas de manera consistente, tenemos la clase Fecha en tanto en el paquete java.util como en el paquete java.sql. Además, las clases de formato y análisis están definidas en el paquete java.text.
  • java.util.Date contiene tanto valores de fecha como de hora, mientras que java.sql.Date contiene solo valores de fecha. Tener esto en el paquete java.sql no tiene ningún sentido. Además, ambas clases tienen el mismo nombre, lo cual es un diseño muy deficiente en sí mismo.
  • No hay clases claramente definidas para tiempo, marca de tiempo, formato y análisis. Tenemos la clase abstracta java.text.DateFormat para necesidades de análisis y formato. Normalmente, se utiliza la clase SimpleDateFormat para análisis y formato.
  • Todas las clases de Fecha son mutables, por lo que no son seguras para subprocesos. Es uno de los mayores problemas con las clases de Fecha y Calendario de Java.
  • La clase Date no proporciona internacionalización, no hay soporte de zona horaria. Por lo tanto, se introdujeron las clases java.util.Calendar y java.util.TimeZone, pero también tienen todos los problemas mencionados anteriormente.

Hay algunos otros problemas con los métodos definidos en las clases Date y Calendar, pero los problemas anteriores dejan claro que se necesitaba una API de fecha y hora sólida en Java. Es por eso que Joda Time jugó un papel clave como reemplazo de calidad para los requisitos de fecha y hora en Java.

Principios de diseño de Java 8 Date Time

La API de fecha y hora de Java 8 es una implementación de JSR-310. Está diseñada para superar todas las fallas en las implementaciones de fecha y hora heredadas. Algunos de los principios de diseño de la nueva API de fecha y hora son:

  1. Inmutabilidad: Todas las clases en la nueva API de fecha y hora son inmutables y adecuadas para entornos multihilo.

  2. Separación de preocupaciones: La nueva API separa claramente entre fecha y hora legibles para humanos y tiempo de máquina (marca de tiempo Unix). Define clases separadas para Fecha, Hora, FechaHora, Marca de tiempo, Zona horaria, etc.

  3. Claridad: Los métodos están claramente definidos y realizan la misma acción en todas las clases. Por ejemplo, para obtener la instancia actual tenemos el método now(). Hay métodos format() y parse() definidos en todas estas clases en lugar de tener una clase separada para ellos.

    Todas las clases utilizan el Patrón de Fábrica y el Patrón de Estrategia para un mejor manejo. Una vez que hayas utilizado los métodos en una de las clases, trabajar con otras clases no será difícil.

  4. Operaciones de utilidad: Todas las nuevas clases de la API de Fecha y Hora vienen con métodos para realizar tareas comunes, como sumar, restar, formatear, analizar, obtener la parte separada en fecha/hora, etc.

  5. Extensible: La nueva API de Fecha y Hora funciona en el sistema de calendario ISO-8601 pero también podemos usarlo con otros calendarios no ISO.

Paquetes de API de Fecha y Hora

La API de Fecha y Hora de Java 8 consta de los siguientes paquetes.

  1. java.time: Este es el paquete base de la nueva API de Fecha y Hora de Java. Todas las principales clases base son parte de este paquete, como LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration, etc. Todas estas clases son inmutables y seguras para subprocesos. La mayoría de las veces, estas clases serán suficientes para manejar requisitos comunes.
  2. java.time.chrono: Este paquete define APIs genéricas para sistemas de calendario no ISO. Podemos extender la clase AbstractChronology para crear nuestro propio sistema de calendario.
  3. java.time.formato: Este paquete contiene clases utilizadas para formatear y analizar objetos de fecha y hora. La mayoría de las veces no las usaríamos directamente porque las clases principales en el paquete java.time proporcionan métodos de formateo y análisis.
  4. java.time.temporal: Este paquete contiene objetos temporales y podemos usarlo para encontrar fechas o horas específicas relacionadas con los objetos de fecha/hora. Por ejemplo, podemos usarlos para encontrar el primer o último día del mes. Puedes identificar fácilmente estos métodos porque siempre tienen el formato “withXXX”.
  5. Paquete java.time.zone: Este paquete contiene clases para admitir diferentes zonas horarias y sus reglas.

Ejemplos de Clases de API de Fecha y Hora de Java 8

Hemos revisado la mayoría de las partes importantes de la API de fecha y hora de Java. Es hora de analizar las clases más importantes de la API de fecha y hora con ejemplos.

1. LocalDate

LocalDate es una clase inmutable que representa una fecha con el formato predeterminado yyyy-MM-dd. Podemos usar el método now() para obtener la fecha actual. También podemos proporcionar argumentos de entrada para el año, el mes y el día para crear una instancia de LocalDate.

Esta clase proporciona un método sobrecargado para now() donde podemos pasar ZoneId para obtener fechas en una zona horaria específica. Esta clase proporciona la misma funcionalidad que java.sql.Date.

package com.journaldev.java8.time;

import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;

/**
 * Ejemplos de LocalDate
 * @author pankaj
 *
 */
public class LocalDateExample {

	public static void main(String[] args) {
		
		//Fecha actual
		LocalDate today = LocalDate.now();
		System.out.println("Current Date="+today);
		
		//Creando LocalDate proporcionando argumentos de entrada
		LocalDate firstDay_2014 = LocalDate.of(2014, Month.JANUARY, 1);
		System.out.println("Specific Date="+firstDay_2014);
		
		
		//Intenta crear una fecha proporcionando entradas inválidas
		//LocalDate feb29_2014 = LocalDate.of(2014, Month.FEBRUARY, 29);
		//Excepción en el hilo "principal" java.time.DateTimeException: 
		//Fecha inválida '29 de febrero' ya que '2014' no es un año bisiesto
		
		//Fecha actual en "Asia/Kolkata", se puede obtener de la documentación de ZoneId
		LocalDate todayKolkata = LocalDate.now(ZoneId.of("Asia/Kolkata"));
		System.out.println("Current Date in IST="+todayKolkata);

		//java.time.zone.ZoneRulesException: Identificación de zona horaria desconocida: IST
		//LocalDate todayIST = LocalDate.now(ZoneId.of("IST"));
		
		//Obteniendo fecha desde la fecha base es decir 01/01/1970
		LocalDate dateFromBase = LocalDate.ofEpochDay(365);
		System.out.println("365th day from base date= "+dateFromBase);
		
		LocalDate hundredDay2014 = LocalDate.ofYearDay(2014, 100);
		System.out.println("100th day of 2014="+hundredDay2014);
	}

}

La explicación de los métodos de LocalDate se proporciona en los comentarios. Cuando ejecutamos este programa, obtenemos la siguiente salida.

Current Date=2014-04-28
Specific Date=2014-01-01
Current Date in IST=2014-04-29
365th day from base date= 1971-01-01
100th day of 2014=2014-04-10

2. LocalTime

LocalTime es una clase inmutable cuya instancia representa una hora en formato legible para humanos. Su formato predeterminado es hh:mm:ss.zzz. Al igual que LocalDate, esta clase proporciona soporte para zonas horarias y permite crear una instancia pasando como argumentos la hora, el minuto y el segundo.

package com.journaldev.java8.time;

import java.time.LocalTime;
import java.time.ZoneId;

/**
 * Ejemplos de LocalTime
 * @author pankaj
 *
 */
public class LocalTimeExample {

	public static void main(String[] args) {
		
		//Hora actual
		LocalTime time = LocalTime.now();
		System.out.println("Current Time="+time);
		
		//Crear LocalTime proporcionando argumentos de entrada
		LocalTime specificTime = LocalTime.of(12,20,25,40);
		System.out.println("Specific Time of Day="+specificTime);
		
		
		//Intentar crear una hora proporcionando entradas inválidas
		//LocalTime horaInvalida = LocalTime.of(25,20);
		//Excepción en el subproceso "main" java.time.DateTimeException: 
		//Valor no válido para HourOfDay (valores válidos 0 - 23): 25
		
		//Fecha actual en "Asia/Kolkata", se puede obtener de la documentación de ZoneId
		LocalTime timeKolkata = LocalTime.now(ZoneId.of("Asia/Kolkata"));
		System.out.println("Current Time in IST="+timeKolkata);

		//java.time.zone.ZoneRulesException: Identificador de zona horaria desconocido: IST
		//LocalTime hoyIST = LocalTime.now(ZoneId.of("IST"));
		
		//Obteniendo la fecha desde la fecha base, es decir, 01/01/1970
		LocalTime specificSecondTime = LocalTime.ofSecondOfDay(10000);
		System.out.println("10000th second time= "+specificSecondTime);

	}

}

Salida:

Current Time=15:51:45.240
Specific Time of Day=12:20:25.000000040
Current Time in IST=04:21:45.276
10000th second time= 02:46:40

3. LocalDateTime

LocalDateTime es un objeto de fecha y hora inmutable que representa una fecha y hora con el formato predeterminado yyyy-MM-dd-HH-mm-ss.zzz. Proporciona un método de fábrica que toma argumentos de entrada LocalDate y LocalTime para crear una instancia de LocalDateTime.

package com.journaldev.java8.time;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZoneOffset;

public class LocalDateTimeExample {

	public static void main(String[] args) {
		
		//Fecha actual
		LocalDateTime today = LocalDateTime.now();
		System.out.println("Current DateTime="+today);
		
		//Fecha actual usando LocalDate y LocalTime
		today = LocalDateTime.of(LocalDate.now(), LocalTime.now());
		System.out.println("Current DateTime="+today);
		
		//Creando LocalDateTime proporcionando argumentos de entrada
		LocalDateTime specificDate = LocalDateTime.of(2014, Month.JANUARY, 1, 10, 10, 30);
		System.out.println("Specific Date="+specificDate);
		
		
		//Intenta crear una fecha proporcionando entradas no válidas
		//LocalDateTime feb29_2014 = LocalDateTime.of(2014, Month.FEBRUARY, 28, 25,1,1);
		//Excepción en el subproceso "main" java.time.DateTimeException: 
		//Valor no válido para la hora del día (valores válidos 0 - 23): 25

		
		//Fecha actual en "Asia/Kolkata", puedes obtenerla de la documentación de ZoneId
		LocalDateTime todayKolkata = LocalDateTime.now(ZoneId.of("Asia/Kolkata"));
		System.out.println("Current Date in IST="+todayKolkata);

		//java.time.zone.ZoneRulesException: Identificador de zona horaria desconocido: IST
		//LocalDateTime hoyIST = LocalDateTime.now(ZoneId.of("IST"));
		
		//Obteniendo la fecha desde la fecha base es decir 01/01/1970
		LocalDateTime dateFromBase = LocalDateTime.ofEpochSecond(10000, 0, ZoneOffset.UTC);
		System.out.println("10000th second time from 01/01/1970= "+dateFromBase);

	}

}

En los tres ejemplos, hemos visto que si proporcionamos argumentos no válidos para crear Fecha/Hora, entonces lanza java.time.DateTimeException, que es una RuntimeException, por lo que no necesitamos capturarlo explícitamente.

También hemos visto que podemos obtener datos de Fecha/Hora pasando ZoneId, puedes obtener la lista de valores de ZoneId admitidos de su JavaDoc. Cuando ejecutamos la clase anterior, obtenemos la siguiente salida.

Current DateTime=2014-04-28T16:00:49.455
Current DateTime=2014-04-28T16:00:49.493
Specific Date=2014-01-01T10:10:30
Current Date in IST=2014-04-29T04:30:49.493
10000th second time from 01/01/1970= 1970-01-01T02:46:40

4. Instante

La clase Instante se utiliza para trabajar con el formato de tiempo legible por máquina. Instante almacena la fecha y hora en un sello de tiempo Unix.

package com.journaldev.java8.time;

import java.time.Duration;
import java.time.Instant;

public class InstantExample {

	public static void main(String[] args) {
		//Sello de tiempo actual
		Instant timestamp = Instant.now();
		System.out.println("Current Timestamp = "+timestamp);
		
		//Instante a partir del sello de tiempo
		Instant specificTime = Instant.ofEpochMilli(timestamp.toEpochMilli());
		System.out.println("Specific Time = "+specificTime);
		
		//Ejemplo de duración
		Duration thirtyDay = Duration.ofDays(30);
		System.out.println(thirtyDay);
	}

}

Salida:

Current Timestamp = 2014-04-28T23:20:08.489Z
Specific Time = 2014-04-28T23:20:08.489Z
PT720H

Utilidades de API de Fecha de Java 8

La mayoría de las clases principales de principios de Fecha y Hora proporcionan varios métodos de utilidad como más/menos días, semanas, meses, etc. También hay algunos otros métodos de utilidad para ajustar la fecha usando el TemporalAdjuster y para calcular el período entre dos fechas.

package com.journaldev.java8.time;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Period;
import java.time.temporal.TemporalAdjusters;

public class DateAPIUtilities {

	public static void main(String[] args) {
		
		LocalDate today = LocalDate.now();
		
		//Obtener el año y verificar si es bisiesto
		System.out.println("Year "+today.getYear()+" is Leap Year? "+today.isLeapYear());
		
		//Comparar dos LocalDate para antes y después
		System.out.println("Today is before 01/01/2015? "+today.isBefore(LocalDate.of(2015,1,1)));
		
		//Crear LocalDateTime a partir de LocalDate
		System.out.println("Current Time="+today.atTime(LocalTime.now()));
		
		//Operaciones de suma y resta
		System.out.println("10 days after today will be "+today.plusDays(10));
		System.out.println("3 weeks after today will be "+today.plusWeeks(3));
		System.out.println("20 months after today will be "+today.plusMonths(20));

		System.out.println("10 days before today will be "+today.minusDays(10));
		System.out.println("3 weeks before today will be "+today.minusWeeks(3));
		System.out.println("20 months before today will be "+today.minusMonths(20));
		
		//Ajustadores temporales para ajustar las fechas
		System.out.println("First date of this month= "+today.with(TemporalAdjusters.firstDayOfMonth()));
		LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());
		System.out.println("Last date of this year= "+lastDayOfYear);
		
		Period period = today.until(lastDayOfYear);
		System.out.println("Period Format= "+period);
		System.out.println("Months remaining in the year= "+period.getMonths());		
	}
}

Salida:

Year 2014 is Leap Year? false
Today is before 01/01/2015? true
Current Time=2014-04-28T16:23:53.154
10 days after today will be 2014-05-08
3 weeks after today will be 2014-05-19
20 months after today will be 2015-12-28
10 days before today will be 2014-04-18
3 weeks before today will be 2014-04-07
20 months before today will be 2012-08-28
First date of this month= 2014-04-01
Last date of this year= 2014-12-31
Period Format= P8M3D
Months remaining in the year= 8

Java 8 Análisis y Formateo de Fechas

Es muy común formatear la fecha en diferentes formatos y luego analizar una cadena para obtener objetos de Fecha y Hora.

package com.journaldev.java8.time;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateParseFormatExample {

	public static void main(String[] args) {
		
		//Ejemplos de formato
		LocalDate date = LocalDate.now();
		//formato predeterminado
		System.out.println("Default format of LocalDate="+date);
		//formato específico
		System.out.println(date.format(DateTimeFormatter.ofPattern("d::MMM::uuuu")));
		System.out.println(date.format(DateTimeFormatter.BASIC_ISO_DATE));
		
		
		LocalDateTime dateTime = LocalDateTime.now();
		//formato predeterminado
		System.out.println("Default format of LocalDateTime="+dateTime);
		//formato específico
		System.out.println(dateTime.format(DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss")));
		System.out.println(dateTime.format(DateTimeFormatter.BASIC_ISO_DATE));
		
		Instant timestamp = Instant.now();
		//formato predeterminado
		System.out.println("Default format of Instant="+timestamp);
		
		//Ejemplos de análisis
		LocalDateTime dt = LocalDateTime.parse("27::Apr::2014 21::39::48",
				DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss"));
		System.out.println("Default format after parsing = "+dt);
	}

}

Salida:

Default format of LocalDate=2014-04-28
28::Apr::2014
20140428
Default format of LocalDateTime=2014-04-28T16:25:49.341
28::Apr::2014 16::25::49
20140428
Default format of Instant=2014-04-28T23:25:49.342Z
Default format after parsing = 2014-04-27T21:39:48

Soporte de fecha y hora heredado de la API de fechas de Java

Las clases de fecha/hora heredadas se utilizan en casi todas las aplicaciones, por lo que tener compatibilidad con versiones anteriores es imprescindible. Es por eso que existen varios métodos de utilidad a través de los cuales podemos convertir clases heredadas a nuevas clases y viceversa.

package com.journaldev.java8.time;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

public class DateAPILegacySupport {

	public static void main(String[] args) {
		
		//Fecha a Instante
		Instant timestamp = new Date().toInstant();
		//Ahora podemos convertir Instante a LocalDateTime u otras clases similares
		LocalDateTime date = LocalDateTime.ofInstant(timestamp, 
						ZoneId.of(ZoneId.SHORT_IDS.get("PST")));
		System.out.println("Date = "+date);
		
		//Calendario a Instante
		Instant time = Calendar.getInstance().toInstant();
		System.out.println(time);
		//TimeZone a ZoneId
		ZoneId defaultZone = TimeZone.getDefault().toZoneId();
		System.out.println(defaultZone);
		
		//ZonedDateTime a partir de un Calendario específico
		ZonedDateTime gregorianCalendarDateTime = new GregorianCalendar().toZonedDateTime();
		System.out.println(gregorianCalendarDateTime);
		
		//API de fecha a clases heredadas
		Date dt = Date.from(Instant.now());
		System.out.println(dt);
		
		TimeZone tz = TimeZone.getTimeZone(defaultZone);
		System.out.println(tz);
		
		GregorianCalendar gc = GregorianCalendar.from(gregorianCalendarDateTime);
		System.out.println(gc);
		
	}

}

Salida:

Date = 2014-04-28T16:28:54.340
2014-04-28T23:28:54.395Z
America/Los_Angeles
2014-04-28T16:28:54.404-07:00[America/Los_Angeles]
Mon Apr 28 16:28:54 PDT 2014
sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]
java.util.GregorianCalendar[time=1398727734404,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2014,MONTH=3,WEEK_OF_YEAR=18,WEEK_OF_MONTH=5,DAY_OF_MONTH=28,DAY_OF_YEAR=118,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=4,HOUR_OF_DAY=16,MINUTE=28,SECOND=54,MILLISECOND=404,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]

Como puedes ver, los métodos toString() de las clases heredadas TimeZone y GregorianCalendar son demasiado verbosos y no son amigables para el usuario.

Conclusión

I like this new Date Time API a lot. Some of the most used classes will be LocalDate and LocalDateTime. It’s very easy to work with the new classes. And, having similar methods that does a particular job makes it easy to find. It will take some time from moving legacy classes to new Date Time classes, but I believe it will be worth the time and effort.

Source:
https://www.digitalocean.com/community/tutorials/java-8-date-localdate-localdatetime-instant