Como analisar / formatar datas com LocalDateTime? (Java 8)

340

O Java 8 adicionou uma nova API java.time para trabalhar com datas e horas ( JSR 310 ).

Eu tenho data e hora como string (por exemplo "2014-04-08 12:30"). Como posso obter uma LocalDateTimeinstância da string especificada?

Depois que terminei de trabalhar com o LocalDateTimeobjeto: Como posso converter a LocalDateTimeinstância novamente em uma string com o mesmo formato, como mostrado acima?

micha
fonte
11
Para sua informação, a maioria das pessoas na maioria das vezes quer um a ZonedDateTimenão um LocalDateTime. O nome é contra-intuitivo; o Localsignifica qualquer localidade, em geral, ao invés de um fuso horário específico. Como tal, um LocalDateTimeobjeto não está vinculado à linha do tempo. Para ter significado, para obter um momento específico na linha do tempo, você deve aplicar um fuso horário.
Basil Bourque
Veja a minha resposta para uma explicação sobre LocalDateTimevs. ZonedDateTimevs. OffsetDateTimevs. Instantvs. LocalDatevs. LocalTime, como manter a calma sobre por que é tão complicado e como fazê-lo à direita no primeiro tiro.
Ondra Žižka
11
Se não fosse impraticávelmente longo, LocalDateTimeprovavelmente teria sido nomeado ZonelessOffsetlessDateTime.
Ondra Žižka

Respostas:

534

Data e hora da análise

Para criar um LocalDateTimeobjeto a partir de uma sequência, você pode usar o LocalDateTime.parse()método estático . É preciso uma string e um DateTimeFormattercomo parâmetro. A DateTimeFormatteré usado para especificar o padrão de data / hora.

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

Formatando data e hora

Para criar uma string formatada para fora de um LocalDateTimeobjeto, você pode usar o 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"

Observe que existem alguns formatos de data / hora comumente usados, predefinidos como constantes em DateTimeFormatter. Por exemplo: Usar DateTimeFormatter.ISO_DATE_TIMEpara formatar a LocalDateTimeinstância de cima resultaria na cadeia de caracteres "1986-04-08T12:30:00".

Os métodos parse()e format()estão disponíveis para todos os objetos relacionados a data / hora (por exemplo, LocalDateou ZonedDateTime)

micha
fonte
77
Apenas observe que o DateTimeFormatter é imutável e seguro para threads e, portanto, a abordagem recomendada é armazená-lo em uma constante estática sempre que possível.
JodaStephen
@micha e se eu tiver "2016-12-31T07: 59: 00.000Z" este formato de data?
Dawood Ahmed
14
@DawoodAbbasi tryDateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
Ray Hulha
11
@ Phoenix, talvez seja porque você está tentando chamar format()a classe LocalDateTime em vez de na instância? Pelo menos, é o que eu fiz: eu confuso DateTimecom dateTimeno exemplo acima.
glaed
2
Não esqueça as maiúsculas no MM
Wesos de Queso
159

Você também pode usar LocalDate.parse()ou LocalDateTime.parse()em um Stringsem fornecer-lo com um padrão, se o Stringé em formato ISO-8601 .

por exemplo,

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);

Saída ,

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

e use DateTimeFormattersomente se você tiver que lidar com outros padrões de data.

Por exemplo, no exemplo a seguir, dd MMM uuuu representa o dia do mês (dois dígitos), três letras do nome do mês (janeiro, fevereiro, março, ...) e um ano de quatro 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);

Resultado

04 Aug 2015 parses to 2015-08-04

lembre-se também de que o DateTimeFormatterobjeto é bidirecional; ele pode analisar a entrada e formatar a saída.

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));

Resultado

2015-08-04 formats as 04 Aug 2015

(veja a lista completa de padrões para formatação e análise do 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
Sufiyan Ghori
fonte
11
Esta resposta abordou um assunto importante: use formatadores predefinidos sempre que possível, por exemplo, NÃO crie uma base de formatador em "aaaa-MM-dd", use DateTimeFormatter.ISO_LOCAL_DATE. Isso fará com que seu código pareça muito mais limpo. Além disso, tente maximizar o uso do formato ISO8061, pois pagará dividendos a longo prazo.
Christopher Yang
Quero analisar uma data para validação como, 2018-08-09 12:00:08mas quando analiso, vejo a Tadição de uma que não preciso. Existe uma forma de fazer isso ?
Raghuveer
@Raghuveer OT é apenas o delimitador ISO-8061 entre data e hora. Se você tiver um espaço em seu formato, basta usar o padrão yyyy-MM-dd hh:mm:sspara analisar e formatar. OT sempre será exibido no formato padrão (ISO-8061), mas você pode usar seus próprios padrões.
Egor Hans
39

As duas respostas acima explicam muito bem a pergunta sobre os padrões das cordas. No entanto, caso você esteja trabalhando com a ISO 8601, não há necessidade de se aplicar, DateTimeFormatterpois o LocalDateTime já está preparado para isso:

Converter LocalDateTime em fuso horário ISO8601 String

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

Converter da string ISO8601 de volta para um LocalDateTime

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

A análise de uma string com data e hora em um determinado momento (Java chama de " Instant") é bastante complicada. O Java vem resolvendo isso em várias iterações. A mais recente, java.timee java.time.chrono, cobre quase todas as necessidades (exceto dilatação do tempo :)).

No entanto, essa complexidade traz muita confusão.

A chave para entender a análise de datas é:

Por que o Java tem tantas maneiras de analisar uma data

  1. Existem vários sistemas para medir um tempo. Por exemplo, os calendários históricos japoneses foram derivados dos intervalos de tempo do reinado do respectivo imperador ou dinastia. Depois, há, por exemplo, registro de data e hora do UNIX. Felizmente, o mundo inteiro (de negócios) conseguiu usar o mesmo.
  2. Historicamente, os sistemas estavam sendo alternados de / para, por várias razões . Por exemplo, do calendário juliano ao calendário gregoriano em 1582. Portanto, as datas 'ocidentais' antes disso precisam ser tratadas de maneira diferente.
  3. E é claro que a mudança não aconteceu de uma só vez. Como o calendário veio das matrizes de algumas religiões e de outras partes da Europa que acreditavam em outras dietas, por exemplo, a Alemanha não mudou até o ano de 1700.

... e por que é o LocalDateTime, ZonedDateTimeet al. tão complicado

  1. Existem fusos horários . Um fuso horário é basicamente uma "faixa" * [1] da superfície da Terra cujas autoridades seguem as mesmas regras de quando é que esse horário é compensado. Isso inclui regras de horário de verão.
    Os fusos horários mudam ao longo do tempo em várias áreas, principalmente com base em quem conquista quem. E as regras de um fuso horário também mudam com o tempo .

  2. Existem compensações de tempo. Isso não é o mesmo que fusos horários, porque um fuso horário pode ser, por exemplo, "Praga", mas com deslocamento de horário de verão e inverno.
    Se você obtiver um registro de data e hora com um fuso horário, o deslocamento poderá variar, dependendo da parte do ano em que estiver. Durante a hora do salto, o registro de data e hora poderá significar 2 vezes diferentes, portanto, sem informações adicionais, não poderá ser confiável. convertido.
    Nota: Por carimbo de data / hora, quero dizer "uma sequência que contém uma data e / ou hora, opcionalmente com um fuso horário e / ou deslocamento de hora".

  3. Vários fusos horários podem compartilhar o mesmo deslocamento de tempo por determinados períodos. Por exemplo, o fuso horário GMT / UTC é igual ao fuso horário "Londres" quando o deslocamento da hora de verão não está em vigor.

Para tornar um pouco mais complicado (mas isso não é muito importante para o seu caso de uso):

  1. Os cientistas observam a dinâmica da Terra, que muda com o tempo; com base nisso, eles adicionam segundos no final de anos individuais. (Assim2040-12-31 24:00:00 Pode ser uma data e hora válidas.) Isso precisa de atualizações regulares dos metadados que os sistemas usam para ter as conversões de data corretas. Por exemplo, no Linux, você recebe atualizações regulares dos pacotes Java, incluindo esses novos dados.
  2. As atualizações nem sempre mantêm o comportamento anterior para registros de data e hora históricos e futuros. Portanto, pode ser que a análise dos dois carimbos de data e hora em torno da alteração de algum fuso horário comparando-os possa fornecer resultados diferentes ao executar em versões diferentes do software. Isso também se aplica à comparação entre o fuso horário afetado e outro fuso horário.

    Se isso causar um erro no seu software, considere usar algum registro de data e hora que não possua regras tão complicadas, como o registro de data e hora do UNIX .

  3. Por causa do 7, para as datas futuras, não podemos converter datas exatamente com certeza. Assim, por exemplo, a análise atual 8524-02-17 12:00:00pode demorar alguns segundos a partir da análise futura.

As APIs do JDK para isso evoluíram com as necessidades contemporâneas

  • Os primeiros lançamentos do Java tinham acabado de java.util.Date tiveram uma abordagem um pouco ingênua, assumindo que há apenas o ano, mês, dia e hora. Isso rapidamente não foi suficiente.
  • Além disso, as necessidades dos bancos de dados eram diferentes, muito cedo, java.sql.Date foram introduzidas, com suas próprias limitações.
  • Como nenhum dos dois cobriu bem calendários e fusos horários, o Calendar API foi introduzida.
  • Isso ainda não cobria a complexidade dos fusos horários. E, no entanto, a combinação das APIs acima foi realmente difícil de se trabalhar. Assim, quando os desenvolvedores Java começaram a trabalhar em aplicativos da web globais, as bibliotecas direcionadas à maioria dos casos de uso, como o JodaTime, ficaram rapidamente populares. O JodaTime foi o padrão de fato por cerca de uma década.
  • Mas o JDK não se integrou ao JodaTime, portanto, trabalhar com ele foi um pouco complicado. Assim, após uma longa discussão sobre como abordar o assunto, o JSR-310 foi criado principalmente com base no JodaTime .

Como lidar com isso em Java java.time

Determine que tipo analisar um timestamp para

Quando você está consumindo uma sequência de carimbo de data e hora, precisa saber quais informações ela contém. Este é o ponto crucial. Se você não acertar, você terá exceções enigmáticas como "Não é possível criar instantâneo" ou "Falta o deslocamento da zona" ou "ID da zona desconhecida" etc.

Contém a data e a hora?

  1. Tem um deslocamento de tempo?
    Um deslocamento de tempo é a +hh:mmparte. Às vezes, +00:00pode ser substituído Zpor "horário do Zulu", UTCcomo horário universal coordenado ou GMThorário médio de Greenwich. Eles também definem o fuso horário.
    Para esses registros de data e hora, você usa OffsetDateTime.

  2. Tem um fuso horário?
    Para esses registros de data e hora, você usa ZonedDateTime.
    A zona é especificada por

    • nome ("Praga", "Horário padrão do Pacífico", "PST") ou
    • "ID da zona" ("America / Los_Angeles", "Europe / London"), representado por java.time.ZoneId .

    A lista de fusos horários é compilada por um "banco de dados TZ" , apoiado pelo ICAAN.

    De acordo com ZoneIdo javadoc, os IDs da zona também podem, de alguma forma, ser especificados como Ze deslocados. Não tenho certeza de como isso é mapeado para zonas reais. Se o registro de data e hora, que possui apenas um TZ, cai em uma hora bissexta da mudança de deslocamento de tempo, é ambíguo e a interpretação está sujeita ResolverStyle, veja abaixo.

  3. Se não tiver nenhum , o contexto ausente será assumido ou negligenciado. E o consumidor tem que decidir. Portanto, ele precisa ser analisado LocalDateTimee convertido em OffsetDateTime, adicionando as informações ausentes:

    • Você pode assumir que é uma hora UTC. Adicione o deslocamento UTC de 0 horas.
    • Você pode supor que é uma hora do local em que a conversão está ocorrendo. Converta-o adicionando o fuso horário do sistema.
    • Você pode negligenciar e apenas usá-lo como está. Isso é útil, por exemplo, para comparar ou subtrair duas vezes (ver Duration), ou quando você não sabe e isso realmente não importa (por exemplo, horário do ônibus local).

Informações de tempo parcial

  • Baseado no que o timestamp contém, você pode tomar LocalDate, LocalTime, OffsetTime, MonthDay, Year, ou YearMonthfora dele.

Se você tiver todas as informações, poderá obter um java.time.Instant. Isso também é usado internamente para converter entre OffsetDateTimee ZonedDateTime.

Descobrir como analisá-lo

Existe uma extensa documentação na DateTimeFormatterqual é possível analisar uma string de carimbo de data e hora e formatar a string.

Os s pré-criadosDateTimeFormatter devem abranger todos os formatos padrão de registro de data e hora. Por exemplo, ISO_INSTANTpode analisar 2011-12-03T10:15:30.123457Z.

Se você tiver algum formato especial, poderá criar seu próprio DateTimeFormatter (que também é um analisador).

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

Eu recomendo olhar para o código fonte DateTimeFormattere me inspirar em como criar um usando DateTimeFormatterBuilder. Enquanto estiver lá, verifique também o ResolverStyleque controla se o analisador é LENIENT, SMART ou STRICT para os formatos e informações ambíguas.

TemporalAccessor

Agora, o erro frequente é entrar na complexidade de TemporalAccessor. Isso vem de como os desenvolvedores estavam acostumados a trabalhar SimpleDateFormatter.parse(String). Certo, DateTimeFormatter.parse("...")te dá TemporalAccessor.

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

Mas, equipado com o conhecimento da seção anterior, você pode analisar convenientemente o tipo que precisa:

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

Você realmente não precisa disso DateTimeFormatter. Os tipos que você deseja analisar têm os parse(String)métodos

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

Em relação a TemporalAccessor, você pode usá-lo se tiver uma vaga idéia de quais informações existem na string e deseja decidir em tempo de execução.

Espero lançar alguma luz de entendimento em sua alma :)

Nota: Há um backport java.timepara Java 6 e 7: ThreeTen-Backport . Para o Android, ele possui o ThreeTenABP .

[1] Não apenas por não serem listras, mas também por alguns extremos estranhos. Por exemplo, algumas ilhas do Pacífico vizinhas têm fusos horários +14: 00 e -11: 00. Isso significa que, enquanto em uma ilha há 1 de maio às 15h, em outra ilha não tão longe, ainda é 30 de abril às 12h (se contei corretamente :))

Ondra Žižka
fonte
3

OBTENHA A HORA UTC ATUAL NO FORMATO NECESSÁRIO

// 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));
Santhosh Hirekerur
fonte
0

Achei maravilhoso cobrir várias variantes do formato de data e 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);
flowgrad
fonte
11
`` `public final estático DateTimeFormatter TIMESTAMP_XX = new DateTimeFormatterBuilder (). appendPattern (" [[uuuu] [- MM] [- dd]] [[HH] [: mm] [: ss] [. SSS]] "). parseDefaulting (ChronoField.YEAR, 2020) .parseDefaulting (ChronoField.MONTH_OF_YEAR, 1) .parseDefaulting (ChronoField.DAY_OF_MONTH, 1) .parseDefaulting (ChronoField.HOUR_OF_DED , 0) .parseDefaulting (ChronoField.NANO_OF_SECOND, 0) .toFormatter (); ``
Alan Stewart