Convertendo entre java.time.LocalDateTime e java.util.Date

484

O Java 8 possui uma API completamente nova para data e hora. Uma das classes mais úteis nessa API é LocalDateTimea retenção de um valor de data e hora independente do fuso horário.

Provavelmente existem milhões de linhas de código usando a classe herdada java.util.Datepara esse fim. Como tal, ao fazer a interface de código antigo e novo, será necessário converter entre os dois. Como parece não haver métodos diretos para fazer isso, como isso pode ser feito?

Knut Arne Vedaa
fonte

Respostas:

706

Resposta curta:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());

Explicação: (com base nesta pergunta sobre LocalDate)

Apesar do nome, java.util.Daterepresenta um instante na linha do tempo, não uma "data". Os dados reais armazenados no objeto são umlong contagem de milissegundos desde 01-01-2009 - 00: 00Z (meia-noite no início de 1970 GMT / UTC).

A classe equivalente java.util.Datena JSR-310 é Instant, portanto, existem métodos convenientes para fornecer a conversão de um lado para outro:

Date input = new Date();
Instant instant = input.toInstant();
Date output = Date.from(instant);

Uma java.util.Dateinstância não tem conceito de fuso horário. Isso pode parecer estranho se você chamar toString()um java.util.Date, porque toStringé relativo a um fuso horário. No entanto, esse método realmente usa o fuso horário padrão de Java rapidamente para fornecer a string. O fuso horário não faz parte do estado real de java.util.Date.

Um Instanttambém não contém nenhuma informação sobre o fuso horário. Portanto, para converter de uma Instantdata para hora local, é necessário especificar um fuso horário. Esse pode ser o fuso horário padrão - ZoneId.systemDefault()- ou pode ser um fuso horário que seu aplicativo controla, como um fuso horário nas preferências do usuário. LocalDateTimepossui um método conveniente de fábrica que utiliza o instante e o fuso horário:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());

Ao contrário, o LocalDateTimefuso horário é especificado chamando o atZone(ZoneId)método O ZonedDateTimepode então ser convertido diretamente em um Instant:

LocalDateTime ldt = ...
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Date output = Date.from(zdt.toInstant());

Observe que a conversão de LocalDateTimepara ZonedDateTimetem o potencial de introduzir um comportamento inesperado. Isso ocorre porque nem todas as datas locais existem devido ao horário de verão. No outono / outono, há uma sobreposição na linha do tempo local, onde a mesma data e hora local ocorre duas vezes. Na primavera, há uma lacuna, onde uma hora desaparece. Veja o Javadoc deatZone(ZoneId) para obter mais a definição do que a conversão fará.

Resumo, se você ida e volta uma java.util.Datea uma LocalDateTimee de volta a um java.util.Datevocê pode acabar com um instante diferente devido ao horário de verão.

Informações adicionais: Há outra diferença que afetará datas muito antigas. java.util.Dateusa um calendário que muda em 15 de outubro de 1582, com datas anteriores a usar o calendário juliano em vez do calendário gregoriano. Por outro lado, java.time.*usa o sistema de calendário ISO (equivalente ao gregoriano) para todos os tempos. Na maioria dos casos de uso, o sistema de calendário ISO é o que você deseja, mas você pode observar efeitos estranhos ao comparar datas anteriores ao ano de 1582.

JodaStephen
fonte
5
Muito obrigado pela explicação clara. Especialmente por java.util.Dateque não contém fuso horário, mas imprima-o durante toString(). A documentação oficial do evento não informa isso claramente quando você publica.
Cherry
Aviso: LocalDateTime.ofInstant(date.toInstant()... não se comporta como seria de esperar. Por exemplo, new Date(1111-1900,11-1,11,0,0,0);passará a 1111-11-17 23:53:28usar essa abordagem. Dê uma olhada na implementação de java.sql.Timestamp#toLocalDateTime()se você precisou que o resultado estivesse 1111-11-11 00:00:00no exemplo anterior.
dog
2
Adicionei uma seção sobre datas muito antigas (antes de 1582). FWIW, sua correção sugerida pode estar errada, pois 1111-11-11 em java.util.Date é o mesmo dia real no histórico que 1111-11-18 em java.time devido aos diferentes sistemas de calendário (diferença de 6,5 minutos ocorre a muitos fusos horários antes de 1900)
JodaStephen
2
Também vale a pena notar que java.sql.Date#toInstantlança um UnsupportedOperationException. Portanto, não use toInstantem um RowMapper ativado java.sql.ResultSet#getDate.
LazerBass 12/04
132

Aqui está o que eu criei (e, como todos os enigmas de Data e Hora, provavelmente será refutado com base em algum ajuste estranho de fuso horário - salto durante o dia: D)

Rodada: Date<<->>LocalDateTime

Dado: Date date = [some date]

(1) LocalDateTime<< Instant<<Date

    Instant instant = Instant.ofEpochMilli(date.getTime());
    LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

(2) Date<< Instant<<LocalDateTime

    Instant instant = ldt.toInstant(ZoneOffset.UTC);
    Date date = Date.from(instant);

Exemplo:

Dado:

Date date = new Date();
System.out.println(date + " long: " + date.getTime());

(1) LocalDateTime<< Instant<< Date:

Criar a Instantpartir de Date:

Instant instant = Instant.ofEpochMilli(date.getTime());
System.out.println("Instant from Date:\n" + instant);

Crie a Datepartir de Instant(não necessário, mas para ilustração):

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

Criar a LocalDateTimepartir deInstant

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
System.out.println("LocalDateTime from Instant:\n" + ldt);

(2) Date<< Instant<<LocalDateTime

Criar a Instantpartir de LocalDateTime:

instant = ldt.toInstant(ZoneOffset.UTC);
System.out.println("Instant from LocalDateTime:\n" + instant);

Criar a Datepartir de Instant:

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

A saída é:

Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

Instant from Date:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

LocalDateTime from Instant:
2013-11-01T14:13:04.574

Instant from LocalDateTime:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574
Saint Hill
fonte
2
@ scottb você está endereçando isso para mim ou para a pessoa que fez a pergunta? Quanto aos meus pensamentos, bem, seria bom ter a conversão declarada explicitamente na API do jdk 8 - eles poderiam pelo menos ter feito isso. De qualquer forma, existem muitas bibliotecas que serão re-fatoradas para incluir os novos recursos do Java-8 e saber como fazê-lo é importante.
The Coordinator
4
@scottb Por que o caso de terceiros seria incomum? É malditamente comum. Um exemplo: JDBC 4 e menos (espero que não 5).
Raman
5
Como regra geral do JSR-310, não há necessidade de conversão entre tipos usando epoch-millis. Existem alternativas melhores usando objetos, veja minha resposta completa abaixo. A resposta acima também é totalmente válida apenas se você usar um deslocamento de zona como o UTC - algumas partes da resposta não funcionarão em um fuso horário completo como America / New_York.
JodaStephen
2
Em vez de Instant.ofEpochMilli(date.getTime())fazerdate.toInstant()
cabra
1
@ goat toInstant()parece bom, exceto que falha java.sql.Date, arggggh! Portanto, é finalmente mais fácil de usar Instant.ofEpochMilli(date.getTime()).
Vadipp 9/12/19
22

Maneira muito mais conveniente, se você tiver certeza de que precisa de um fuso horário padrão:

Date d = java.sql.Timestamp.valueOf( myLocalDateTime );
Petrychenko
fonte
25
Claro, é mais fácil, mas eu não gosto de misturar coisas relacionadas ao jdbc com manipulação simples de data, pelo menos IMHO.
Enrico Giurin
9
Sinto muito, esta é uma solução terrível para um problema simples. É como definir 5 como igual ao número de toques no logotipo olímpico.
24518 Madbreaks
7

o seguinte parece funcionar ao converter da nova API LocalDateTime para java.util.date:

Date.from(ZonedDateTime.of({time as LocalDateTime}, ZoneId.systemDefault()).toInstant());

a conversão reversa pode ser (espero) alcançada de maneira semelhante ...

espero que ajude...

tonto
fonte
5

Está tudo aqui: http://blog.progs.be/542/date-to-java-time

A resposta com "round-trip" não é exata: quando você faz

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

se o fuso horário do sistema não for UTC / GMT, você altera a hora!

joe-mojo
fonte
Somente se você fizer isso durante 1 hora por ano, no outono, quando duas vezes do LocalDateTime se sobrepuserem. O avanço da mola não causa um problema. A maioria das vezes converte as duas direções corretamente.
David
4

A maneira mais rápida de LocalDateTime-> Dateé:

Date.from(ldt.toInstant(ZoneOffset.UTC))

Lukasz Czerwinski
fonte
3

Não tenho certeza se essa é a maneira mais simples ou melhor, ou se existem armadilhas, mas funciona:

static public LocalDateTime toLdt(Date date) {
    GregorianCalendar cal = new GregorianCalendar();
    cal.setTime(date);
    ZonedDateTime zdt = cal.toZonedDateTime();
    return zdt.toLocalDateTime();
}

static public Date fromLdt(LocalDateTime ldt) {
    ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.systemDefault());
    GregorianCalendar cal = GregorianCalendar.from(zdt);
    return cal.getTime();
}
Knut Arne Vedaa
fonte
3
Há definitivamente uma armadilha que vai de LocalDateTimepara Date. Nas transições de horário de verão, a LocalDateTimepode ser inexistente ou ocorrer duas vezes. Você precisa descobrir o que deseja que aconteça em cada caso.
Jon Skeet
1
BTW, GregorianCalendarpertence API estranho velho que os novos java.timeobjetivos da API para substituir
Vadzim
3

Se você estiver no Android e estiver usando o threetenbp, poderá usar DateTimeUtils.

ex:

Date date = DateTimeUtils.toDate(localDateTime.atZone(ZoneId.systemDefault()).toInstant());

você não pode usar, Date.frompois ele é suportado apenas na API 26+

humazed
fonte