¿Cómo analizar / formatear fechas con LocalDateTime? (Java 8)


341

Java 8 agregó una nueva API java.time para trabajar con fechas y horas ( JSR 310 ).

Tengo fecha y hora como cadena (por ejemplo "2014-04-08 12:30"). ¿Cómo puedo obtener una LocalDateTimeinstancia de la cadena dada?

Después de que termine de trabajar con el LocalDateTimeobjeto: ¿Cómo puedo convertir la LocalDateTimeinstancia a una cadena con el mismo formato que se muestra arriba?


11
Para su información, la mayoría de las personas la mayoría de las veces querrían un en ZonedDateTimelugar de un LocalDateTime. El nombre es contra-intuitivo; Esto Localsignifica cualquier localidad en general en lugar de una zona horaria específica. Como tal, un LocalDateTimeobjeto no está vinculado a la línea de tiempo. Para tener significado, para obtener un momento específico en la línea de tiempo, debe aplicar una zona horaria.
Basil Bourque

Vea mi respuesta para obtener una explicación de LocalDateTimevs. ZonedDateTimevs. OffsetDateTimevs. Instantvs. LocalDatevs. LocalTime, cómo mantener la calma sobre por qué es tan complicado y cómo hacerlo bien en el primer disparo.
Ondra Žižka

1
Si no fuera prácticamente largo, LocalDateTimeprobablemente habría sido nombrado ZonelessOffsetlessDateTime.
Ondra Žižka

Respuestas:


534

Fecha y hora de análisis

Para crear un LocalDateTimeobjeto a partir de una cadena, puede usar el LocalDateTime.parse()método estático . Toma una cadena y un DateTimeFormattercomo parámetro. El DateTimeFormatterse utiliza para especificar el patrón de fecha / hora.

String str = "1986-04-08 12:30";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(str, formatter);

Formato de fecha y hora

Para crear una cadena formateada de un LocalDateTimeobjeto, puede usar el format()método

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.of(1986, Month.APRIL, 8, 12, 30);
String formattedDateTime = dateTime.format(formatter); // "1986-04-08 12:30"

Tenga en cuenta que hay algunos formatos de fecha / hora de uso común predefinidos como constantes en DateTimeFormatter. Por ejemplo: usar DateTimeFormatter.ISO_DATE_TIMEpara formatear la LocalDateTimeinstancia desde arriba daría como resultado la cadena "1986-04-08T12:30:00".

Los métodos parse()y format()están disponibles para todos los objetos relacionados con la fecha / hora (p. Ej. LocalDateO ZonedDateTime)


77
Solo para tener en cuenta que DateTimeFormatter es inmutable y seguro para subprocesos, por lo que el enfoque recomendado es almacenarlo en una constante estática siempre que sea posible.
JodaStephen

@micha ¿qué pasa si tengo "2016-12-31T07: 59: 00.000Z" este formato de fecha?
Dawood Ahmed

14
@DawoodAbbasi intentaDateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
Ray Hulha

1
¿@Loenix quizás sea porque estás intentando llamar format()a la clase LocalDateTime en lugar de a la instancia? Al menos, eso es lo que hice: confundí DateTimecon dateTimeel ejemplo anterior.
Mirada

2
No olvide las mayúsculas en MM
Wesos de Queso

159

También puede usar LocalDate.parse()o LocalDateTime.parse()en un Stringsin proporcionarle un patrón, si Stringestá en formato ISO-8601 .

por ejemplo,

String strDate = "2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
System.out.println("Date: " + aLD);

String strDatewithTime = "2015-08-04T10:11:30";
LocalDateTime aLDT = LocalDateTime.parse(strDatewithTime);
System.out.println("Date with Time: " + aLDT);

Salida ,

Date: 2015-08-04
Date with Time: 2015-08-04T10:11:30

y úselo DateTimeFormattersolo si tiene que lidiar con otros patrones de fecha.

Por ejemplo, en el siguiente ejemplo, dd MMM uuuu representa el día del mes (dos dígitos), tres letras del nombre del mes (enero, febrero, marzo, ...) y un año de cuatro dígitos:

DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
String anotherDate = "04 Aug 2015";
LocalDate lds = LocalDate.parse(anotherDate, dTF);
System.out.println(anotherDate + " parses to " + lds);

Salida

04 Aug 2015 parses to 2015-08-04

recuerde también que el DateTimeFormatterobjeto es bidireccional; puede analizar tanto la entrada como el formato de salida.

String strDate = "2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
System.out.println(aLD + " formats as " + dTF.format(aLD));

Salida

2015-08-04 formats as 04 Aug 2015

(vea la lista completa de patrones para formatear y analizar DateFormatter )

  Symbol  Meaning                     Presentation      Examples
  ------  -------                     ------------      -------
   G       era                         text              AD; Anno Domini; A
   u       year                        year              2004; 04
   y       year-of-era                 year              2004; 04
   D       day-of-year                 number            189
   M/L     month-of-year               number/text       7; 07; Jul; July; J
   d       day-of-month                number            10

   Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
   Y       week-based-year             year              1996; 96
   w       week-of-week-based-year     number            27
   W       week-of-month               number            4
   E       day-of-week                 text              Tue; Tuesday; T
   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
   F       week-of-month               number            3

   a       am-pm-of-day                text              PM
   h       clock-hour-of-am-pm (1-12)  number            12
   K       hour-of-am-pm (0-11)        number            0
   k       clock-hour-of-am-pm (1-24)  number            0

   H       hour-of-day (0-23)          number            0
   m       minute-of-hour              number            30
   s       second-of-minute            number            55
   S       fraction-of-second          fraction          978
   A       milli-of-day                number            1234
   n       nano-of-second              number            987654321
   N       nano-of-day                 number            1234000000

   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
   z       time-zone name              zone-name         Pacific Standard Time; PST
   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;

   p       pad next                    pad modifier      1

   '       escape for text             delimiter
   ''      single quote                literal           '
   [       optional section start
   ]       optional section end
   #       reserved for future use
   {       reserved for future use
   }       reserved for future use

11
Esta respuesta tocó un tema importante: use formateadores predefinidos siempre que sea posible, por ejemplo, NO cree una base de formateador en "aaaa-MM-dd", use DateTimeFormatter.ISO_LOCAL_DATE en su lugar. Hará que su código se vea mucho más limpio. Además, trate de maximizar el uso del formato ISO8061, pagará dividendos a largo plazo.
Christopher Yang

Quiero analizar una fecha para la validación como, 2018-08-09 12:00:08pero cuando analizo veo que Tse agrega una que no necesito. Hay una manera de hacerlo ?
Raghuveer

@Raghuveer La T es solo el delimitador ISO-8061 entre fecha y hora. Si tiene un espacio en su formato, simplemente puede usar el patrón yyyy-MM-dd hh:mm:sspara analizar y formatear. La T siempre se mostrará en el formato predeterminado (ISO-8061), pero puede usar sus propios patrones.
Egor Hans

39

Ambas respuestas explican muy bien la pregunta sobre los patrones de cuerdas. Sin embargo, en caso de que esté trabajando con ISO 8601 no hay necesidad de aplicar DateTimeFormatterya que LocalDateTime ya está preparado para ello:

Convierta LocalDateTime a la zona horaria ISO8601 String

LocalDateTime ldt = LocalDateTime.now(); 
ZonedDateTime zdt = ldt.atZone(ZoneOffset.UTC); //you might use a different zone
String iso8601 = zdt.toString();

Convierta la cadena ISO8601 de nuevo a LocalDateTime

String iso8601 = "2016-02-14T18:32:04.150Z";
ZonedDateTime zdt = ZonedDateTime.parse(iso8601);
LocalDateTime ldt = zdt.toLocalDateTime();

20

Analizar una cadena con fecha y hora en un punto particular en el tiempo (Java lo llama " Instant") es bastante complicado. Java ha estado abordando esto en varias iteraciones. El último java.timey java.time.chronocubre casi todas las necesidades (excepto Time Dilation :)).

Sin embargo, esa complejidad trae mucha confusión.

La clave para comprender el análisis de fechas es:

¿Por qué Java tiene tantas formas de analizar una fecha?

  1. Hay varios sistemas para medir un tiempo. Por ejemplo, los calendarios japoneses históricos se derivaron de los rangos de tiempo del reinado del emperador o dinastía respectivos. Luego está, por ejemplo, la marca de tiempo UNIX. Afortunadamente, todo el mundo (de negocios) logró usar lo mismo.
  2. Históricamente, los sistemas se cambiaban de / a, por varias razones . Por ejemplo, del calendario juliano al calendario gregoriano en 1582. Por lo tanto, las fechas 'occidentales' antes de eso deben tratarse de manera diferente.
  3. Y, por supuesto, el cambio no ocurrió de inmediato. Debido a que el calendario provenía de las sedes centrales de algunas religiones y otras partes de Europa que creían en otras dietas, por ejemplo, Alemania no cambió hasta el año 1700.

... y por qué es el LocalDateTime, ZonedDateTimeet al. tan complicado

  1. Hay zonas horarias . Una zona horaria es básicamente una "franja" * [1] de la superficie de la Tierra cuyas autoridades siguen las mismas reglas de cuándo tiene qué compensación de tiempo. Esto incluye las reglas del horario de verano.
    Las zonas horarias cambian con el tiempo para varias áreas, principalmente en función de quién conquista a quién. Y las reglas de una zona horaria también cambian con el tiempo .

  2. Hay compensaciones de tiempo. Eso no es lo mismo que las zonas horarias, porque una zona horaria puede ser, por ejemplo, "Praga", pero tiene un desplazamiento de horario de verano y otro de invierno.
    Si obtiene una marca de tiempo con una zona horaria, el desplazamiento puede variar, dependiendo de la parte del año en que se encuentre. Durante la hora bisiesta, la marca de tiempo puede significar 2 tiempos diferentes, por lo que sin información adicional, no puede ser confiable convertido.
    Nota: Por marca de tiempo me refiero a "una cadena que contiene una fecha y / o hora, opcionalmente con una zona horaria y / o desplazamiento de hora".

  3. Varias zonas horarias pueden compartir el mismo desplazamiento de tiempo para ciertos períodos. Por ejemplo, la zona horaria GMT / UTC es la misma que la zona horaria "Londres" cuando la compensación de horario de verano no está vigente.

Para hacerlo un poco más complicado (pero eso no es demasiado importante para su caso de uso):

  1. Los científicos observan la dinámica de la Tierra, que cambia con el tiempo; basado en eso, agregan segundos al final de los años individuales. (Por 2040-12-31 24:00:00lo tanto, puede ser una fecha y hora válida). Esto necesita actualizaciones periódicas de los metadatos que los sistemas usan para tener las conversiones de fecha correctas. Por ejemplo, en Linux, obtiene actualizaciones periódicas de los paquetes de Java, incluidos estos nuevos datos.
  2. Las actualizaciones no siempre mantienen el comportamiento anterior para las marcas de tiempo históricas y futuras. Por lo tanto, puede ocurrir que el análisis de las dos marcas de tiempo alrededor del cambio de una zona horaria al compararlas pueda dar resultados diferentes cuando se ejecuta en diferentes versiones del software. Eso también se aplica a la comparación entre la zona horaria afectada y otra zona horaria.

    Si esto causa un error en su software, considere usar una marca de tiempo que no tenga reglas tan complicadas, como la marca de tiempo UNIX .

  3. Debido a 7, para las fechas futuras, no podemos convertir fechas exactamente con certeza. Entonces, por ejemplo, el análisis actual 8524-02-17 12:00:00puede estar desactivado unos segundos después del análisis futuro.

Las API de JDK para esto evolucionaron con las necesidades contemporáneas

  • Las primeras versiones de Java java.util.Datetenían un enfoque un poco ingenuo, suponiendo que solo hay año, mes, día y hora. Esto rápidamente no fue suficiente.
  • Además, las necesidades de las bases de datos eran diferentes, por lo que java.sql.Datese introdujo muy temprano , con sus propias limitaciones.
  • Como ninguno de los dos cubría diferentes calendarios y zonas horarias, Calendarse introdujo la API.
  • Esto todavía no cubría la complejidad de las zonas horarias. Y, sin embargo, la combinación de las API anteriores fue realmente difícil de trabajar. Entonces, a medida que los desarrolladores de Java comenzaron a trabajar en aplicaciones web globales, las bibliotecas dirigidas a la mayoría de los casos de uso, como JodaTime, se hicieron rápidamente populares. JodaTime fue el estándar de facto durante aproximadamente una década.
  • Pero el JDK no se integró con JodaTime, por lo que trabajar con él fue un poco engorroso. Entonces, después de una larga discusión sobre cómo abordar el asunto, JSR-310 fue creado principalmente basado en JodaTime .

Cómo lidiar con eso en Java java.time

Determine a qué tipo analizar una marca de tiempo

Cuando consume una cadena de marca de tiempo, necesita saber qué información contiene. Este es el punto crucial. Si no lo hace bien, terminará con excepciones crípticas como "No se puede crear Instantáneo" o "Falta el desplazamiento de zona" o "ID de zona desconocida", etc.

¿Contiene la fecha y la hora?

  1. ¿Tiene un desplazamiento de tiempo?
    Un desplazamiento de tiempo es la +hh:mmparte. A veces, +00:00se puede sustituir por Z"hora Zulú", UTCcomo hora universal coordinada o GMTcomo hora media de Greenwich. Estos también establecen la zona horaria.
    Para estas marcas de tiempo, usted usa OffsetDateTime.

  2. ¿Tiene una zona horaria?
    Para estas marcas de tiempo, usted usa ZonedDateTime.
    La zona se especifica por

    • nombre ("Praga", "Hora estándar del Pacífico", "PST") o
    • "ID de zona" ("América / Los_Angeles", "Europa / Londres"), representada por java.time.ZoneId .

    La lista de zonas horarias está compilada por una "base de datos TZ" , respaldada por ICAAN.

    Según ZoneIdel javadoc, los id de zona también se pueden especificar Zy compensar de alguna manera . No estoy seguro de cómo esto se asigna a zonas reales. Si la marca de tiempo, que solo tiene una TZ, cae en un cambio de compensación de hora bisiesta, entonces es ambigua y la interpretación está sujeta a ResolverStyle, ver más abajo.

  3. Si no tiene ninguno , entonces el contexto faltante es asumido o descuidado. Y el consumidor tiene que decidir. Por lo tanto, debe analizarse LocalDateTimey convertirse al OffsetDateTimeagregar la información que falta:

    • Puede suponer que es una hora UTC. Agregue el desplazamiento UTC de 0 horas.
    • Puede suponer que es el momento del lugar donde está ocurriendo la conversión. Conviértalo agregando la zona horaria del sistema.
    • Puede descuidar y simplemente usarlo como está. Eso es útil, por ejemplo, para comparar o restar dos veces (ver Duration), o cuando no sabe y realmente no importa (por ejemplo, el horario del autobús local).

Información de tiempo parcial

  • Sobre la base de lo que contiene la marca de tiempo, se puede tomar LocalDate, LocalTime, OffsetTime, MonthDay, Year, o YearMonthfuera de él.

Si tiene la información completa, puede obtener un java.time.Instant. Esto también se usa internamente para convertir entre OffsetDateTimey ZonedDateTime.

Descubre cómo analizarlo

Existe una extensa documentación sobre la DateTimeFormattercual se puede analizar una cadena de marca de tiempo y formatearla.

Los s creados previamenteDateTimeFormatter deben abarcar más todos los formatos de marca de tiempo estándar. Por ejemplo, ISO_INSTANTpuede analizar 2011-12-03T10:15:30.123457Z.

Si tiene algún formato especial, puede crear su propio DateTimeFormatter (que también es un analizador).

private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
   .parseCaseInsensitive()
   .append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
   .toFormatter();

Recomiendo mirar el código fuente DateTimeFormattere inspirarse en cómo construir uno usando DateTimeFormatterBuilder. Mientras esté allí, también observe ResolverStylequé controles controla si el analizador es LENIENTE, INTELIGENTE o ESTRICTO para los formatos y la información ambigua.

Accesorio temporal

Ahora, el error frecuente es entrar en la complejidad de TemporalAccessor. Esto proviene de cómo solían trabajar los desarrolladores SimpleDateFormatter.parse(String). Bien, DateTimeFormatter.parse("...")te da TemporalAccessor.

// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");

Pero, equipado con el conocimiento de la sección anterior, puede analizar convenientemente el tipo que necesita:

OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);

En realidad no es necesario DateTimeFormattertampoco. Los tipos que desea analizar tienen los parse(String)métodos.

OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");

Al respecto TemporalAccessor, puede usarlo si tiene una idea vaga de qué información hay en la cadena y desea decidir en tiempo de ejecución.

Espero arrojar algo de luz sobre tu alma :)

Nota: Hay un backport de java.timeJava 6 y 7: ThreeTen-Backport . Para Android tiene ThreeTenABP .

[1] No solo que no son rayas, sino que también hay algunos extremos extraños. Por ejemplo, algunas islas vecinas del Pacífico tienen zonas horarias de +14: 00 y -11: 00. Eso significa que, mientras que en una isla, es el 1 de mayo a las 3 p.m., en otra isla no tan lejos, todavía son 30 de abril a las 12 p.m. (si contaba correctamente :))


3

OBTENGA LA HORA ACTUAL UTC EN EL FORMATO REQUERIDO

// Current UTC time
        OffsetDateTime utc = OffsetDateTime.now(ZoneOffset.UTC);

        // GET LocalDateTime 
        LocalDateTime localDateTime = utc.toLocalDateTime();
        System.out.println("*************" + localDateTime);

        // formated UTC time
        DateTimeFormatter dTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        System.out.println(" formats as " + dTF.format(localDateTime));

        //GET UTC time for current date
        Date now= new Date();
        LocalDateTime utcDateTimeForCurrentDateTime = Instant.ofEpochMilli(now.getTime()).atZone(ZoneId.of("UTC")).toLocalDateTime();
        DateTimeFormatter dTF2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        System.out.println(" formats as " + dTF2.format(utcDateTimeForCurrentDateTime));

0

Me pareció maravilloso cubrir múltiples variantes de formato de fecha y hora como este:

final DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
dtfb.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"))
    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
    .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);

1
`` `public final static DateTimeFormatter TIMESTAMP_XX = new DateTimeFormatterBuilder (). appendPattern (" [[uuuu] [- MM] [- dd]] [[HH] [: mm] [: ss] [. SSS]] "). .Far. , 0) .parseDefaulting (ChronoField.NANO_OF_SECOND, 0) .toFormatter (); `` `
Alan Stewart
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.