Como converter ZonedDateTime em Date?

102

Estou tentando definir uma data e hora agnóstica do servidor em meu banco de dados e acredito que a melhor prática para fazer isso é definir uma data e hora UTC. Meu servidor db é Cassandra e o driver db para Java entende apenas o tipo de data.

Portanto, supondo que em meu código estou usando o novo Java 8 ZonedDateTime para obter o UTC now ( ZonedDateTime.now(ZoneOffset.UTC)), como posso converter essa instância de ZonedDateTime para a classe Date "legada"?

Milen Kovachev
fonte
Existem duas classes "Date" construídas em Java java.util.Datee java.sql.Date.
Basil Bourque

Respostas:

162

Você pode converter ZonedDateTime em um instante, que pode ser usado diretamente com Date.

Date.from(java.time.ZonedDateTime.now().toInstant());
Slim Soltani Dridi
fonte
27
Não, será a data atual no padrão do seu sistema de zona.
Slim Soltani Dridi
6
@MilenKovachev Sua pergunta não faz sentido - uma data não tem fuso horário - ela representa apenas um instante no tempo.
assylias
1
@assylias Na verdade, sua declaração não faz sentido. O formato em que a hora é armazenada indica um fuso horário. A data é baseada em UTC, infelizmente, Java faz algumas coisas estúpidas e não a trata como tal e, além disso, considera a hora como TZ local em vez de UTC. A maneira como Data, LocalDateTime, ZonedDateTime armazenam dados implica um fuso horário. A maneira como eles são usados ​​é como se os TZs não existissem, o que é simplesmente totalmente errado. java.util.Date tem um TZ do padrão JVM, implicitamente. O fato de as pessoas tratarem isso como algo diferente (incluindo os documentos java para isso!) É simplesmente ruim.
David
5
@David uh não - a Dateé um número de milissegundos desde a época - portanto, está relacionado ao UTC. Se você imprimi- lo, o fuso horário padrão será usado, mas a classe Date não tem conhecimento do fuso horário do usuário ... Veja por exemplo a seção "mapeamento" em docs.oracle.com/javase/tutorial/datetime/iso/legacy .html E LocalDateTime é explicitamente sem referência a um fuso horário - o que pode ser visto como confuso ...
assylias 23/10/2015
1
Sua resposta envolve desnecessariamente ZonedDateTime. A java.time.Instantclasse é uma substituição direta de java.util.Date, ambas representando um momento no UTC, embora Instantuse uma resolução mais precisa de nanossegundos em vez de milissegundos. Date.from( Instant.now() )deveria ter sido sua solução. Ou por falar nisso, apenas new Date()que tenha o mesmo efeito, captando o momento atual em UTC.
Basil Bourque
64

tl; dr

java.util.Date.from(  // Transfer the moment in UTC, truncating any microseconds or nanoseconds to milliseconds.
    Instant.now() ;   // Capture current moment in UTC, with resolution as fine as nanoseconds.
)

Embora não houvesse nenhum ponto naquele código acima. Ambos java.util.Datee Instantrepresentam um momento na UTC, sempre na UTC. O código acima tem o mesmo efeito que:

new java.util.Date()  // Capture current moment in UTC.

Nenhum benefício aqui para usar ZonedDateTime. Se você já tiver um ZonedDateTime, ajuste para UTC extraindo um Instant.

java.util.Date.from(             // Truncates any micros/nanos.
    myZonedDateTime.toInstant()  // Adjust to UTC. Same moment, same point on the timeline, different wall-clock time.
)

Outra resposta correta

A resposta de ssoltanid aborda corretamente sua questão específica, como converter um objeto java.time ( ZonedDateTime) da nova escola em um objeto da velha escola java.util.Date. Extraia o Instantde ZonedDateTime e passe para java.util.Date.from().

Perda de dados

Observe que você sofrerá perda de dados , pois Instantrastreia nanossegundos desde a época, enquanto java.util.Daterastreia milissegundos desde a época.

diagrama comparando resoluções de milissegundo, microssegundo e nanossegundo

Sua pergunta e comentários levantam outras questões.

Manter servidores em UTC

Seus servidores devem ter seu sistema operacional host definido para UTC como uma prática recomendada em geral. A JVM escolhe essa configuração do sistema operacional host como seu fuso horário padrão, nas implementações Java que conheço.

Especifique o fuso horário

Mas você nunca deve confiar no fuso horário padrão atual da JVM. Em vez de escolher a configuração do host, um sinalizador passado ao iniciar uma JVM pode definir outro fuso horário. Pior ainda: qualquer código em qualquer thread de qualquer aplicativo a qualquer momento pode fazer uma chamada para java.util.TimeZone::setDefaultpara alterar esse padrão em tempo de execução!

TimestampTipo Cassandra

Qualquer banco de dados e driver decente deve lidar automaticamente com o ajuste de uma data e hora passada para UTC para armazenamento. Eu não uso o Cassandra, mas parece ter algum suporte rudimentar para data e hora. A documentação diz que seu Timestamptipo é uma contagem de milissegundos da mesma época (primeiro momento de 1970 em UTC).

ISO 8601

Além disso, o Cassandra aceita entradas de string nos formatos padrão ISO 8601 . Felizmente, java.time usa formatos ISO 8601 como padrão para analisar / gerar strings. A implementação da Instantclasse toStringservirá perfeitamente.

Precisão: Milissegundo vs Nanosegordo

Mas primeiro precisamos reduzir a precisão de nanossegundos de ZonedDateTime para milissegundos. Uma maneira é criar um novo Instant usando milissegundos. Felizmente, java.time tem alguns métodos úteis para conversão de e para milissegundos.

Código de exemplo

Aqui está um exemplo de código no Java 8 Update 60.

ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );

Instant instant = zdt.toInstant();
Instant instantTruncatedToMilliseconds = Instant.ofEpochMilli( instant.toEpochMilli() );
String fodderForCassandra = instantTruncatedToMilliseconds.toString();  // Example: 2015-08-18T06:36:40.321Z

Ou de acordo com este documento de driver Cassandra Java , você pode passar uma java.util.Dateinstância (não deve ser confundida com java.sqlDate). Portanto, você pode fazer um juDate com isso instantTruncatedToMillisecondsno código acima.

java.util.Date dateForCassandra = java.util.Date.from( instantTruncatedToMilliseconds );

Se fizer isso com freqüência, você pode fazer um one-liner.

java.util.Date dateForCassandra = java.util.Date.from( zdt.toInstant() );

Mas seria mais legal criar um pequeno método utilitário.

static public java.util.Date toJavaUtilDateFromZonedDateTime ( ZonedDateTime zdt ) {
    Instant instant = zdt.toInstant();
    // Data-loss, going from nanosecond resolution to milliseconds.
    java.util.Date utilDate = java.util.Date.from( instant ) ;
    return utilDate;
}

Observe a diferença em todo esse código em relação à pergunta. O código da pergunta estava tentando ajustar o fuso horário da instância ZonedDateTime para UTC. Mas isso não é necessário. Conceitualmente:

ZonedDateTime = Instant + ZoneId

Nós apenas extraímos a parte Instant, que já está em UTC (basicamente em UTC, leia a documentação da classe para detalhes precisos).


Tabela de tipos de data e hora em Java, tanto modernos quanto legados


Sobre java.time

A estrutura java.time é integrada ao Java 8 e posterior. Essas classes suplantar os velhos problemáticos legados aulas de data e hora, como java.util.Date, Calendar, e SimpleDateFormat.

O projeto Joda-Time , agora em modo de manutenção , aconselha a migração para as classes java.time .

Para saber mais, consulte o tutorial Oracle . E pesquise no Stack Overflow para muitos exemplos e explicações. A especificação é JSR 310 .

Você pode trocar objetos java.time diretamente com seu banco de dados. Use um driver JDBC compatível com JDBC 4.2 ou posterior. Não há necessidade de cordas, não há necessidade de java.sql.*classes.

Onde obter as classes java.time?

Tabela de qual biblioteca java.time usar com qual versão do Java ou Android

O projeto ThreeTen-Extra estende java.time com classes adicionais. Este projeto é um campo de provas para possíveis adições futuras ao java.time. Você pode encontrar algumas classes úteis aqui, como Interval, YearWeek, YearQuarter, e mais .

Basil Bourque
fonte
Explicação maravilhosa, bem detalhada. Muito obrigado!
Gianmarco F.
4

Aqui está um exemplo de conversão da hora atual do sistema em UTC. Envolve a formatação de ZonedDateTime como String e, em seguida, o objeto String será analisado em um objeto de data usando java.text DateFormat.

    ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
    final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
    final DateFormat FORMATTER_YYYYMMDD_HH_MM_SS = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
    String dateStr = zdt.format(DATETIME_FORMATTER);

    Date utcDate = null;
    try {
        utcDate = FORMATTER_YYYYMMDD_HH_MM_SS.parse(dateStr);
    }catch (ParseException ex){
        ex.printStackTrace();
    }
Asanka Siriwardena
fonte
4

Se você estiver usando o backport ThreeTen para Android e não puder usar o mais recente Date.from(Instant instant)(que requer no mínimo API 26), você pode usar:

ZonedDateTime zdt = ZonedDateTime.now();
Date date = new Date(zdt.toInstant().toEpochMilli());

ou:

Date date = DateTimeUtils.toDate(zdt.toInstant());

Leia também o conselho na resposta de Basil Bourque

David Rawson
fonte
1
O Backport ThreeTen (e ThreeTenABP) incluem uma DateTimeUtilsclasse com os métodos de conversão, então eu usaria Date date = DateTimeUtils.toDate (zdt.toInstant ()); `. Não é tão baixo.
Ole VV de
1

Você pode fazer isso usando as classes java.time integradas ao Java 8 e posterior.

ZonedDateTime temporal = ...
long epochSecond = temporal.getLong(INSTANT_SECONDS);
int nanoOfSecond = temporal.get(NANO_OF_SECOND);
Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);
Peter Lawrey
fonte
Desculpe, de onde vêm os temporais e todas essas constantes?
Milen Kovachev
@MilenKovachev A ZonedDateTime é uma instância de Temporal
Peter Lawrey
Obrigado, mas você deveria ter mencionado que editou sua resposta para que meu comentário não pareça bobo.
Milen Kovachev
2
@MilenKovachev você pode deletar um comentário, mas não é uma pergunta boba.
Peter Lawrey
1
Não consigo entender por que essa resposta foi rejeitada. Instants rastreia segundos e nanossegundos. Dates trilha milissegundos. Essa conversão de um para o outro está correta.
scottb
1

Eu uso isso.

public class TimeTools {

    public static Date getTaipeiNowDate() {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Asia/Taipei");
        ZonedDateTime dateAndTimeInTai = ZonedDateTime.ofInstant(now, zoneId);
        try {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInTai.toString().substring(0, 19).replace("T", " "));
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
}

Porque Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); não dá certo !!! Se vc executar seu aplicativo em seu computador, não é problema. Mas se você executar em qualquer região do AWS ou Docker ou GCP, isso vai gerar problema. Porque o computador não é o seu fuso horário na nuvem. Você deve definir seu fuso horário corretamente no Código. Por exemplo, Ásia / Taipei. Em seguida, ele irá corrigir no AWS ou Docker ou GCP.

public class App {
    public static void main(String[] args) {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Australia/Sydney");
        ZonedDateTime dateAndTimeInLA = ZonedDateTime.ofInstant(now, zoneId);
        try {
            Date ans = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInLA.toString().substring(0, 19).replace("T", " "));
            System.out.println("ans="+ans);
        } catch (ParseException e) {
        }
        Date wrongAns = Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant());
        System.out.println("wrongAns="+wrongAns);
    }
}
Beehuang
fonte
1
Esta parece ser uma das formas mais complexas em qualquer uma das 7 respostas. Isso tem alguma vantagem? Eu acho que não. Realmente não há necessidade de passar por formatação e análise.
Ole VV de
Obrigado, você pergunta. Porque Date wrongAns = Date.from (java.time.ZonedDateTime.ofInstant (now, zoneId) .toInstant ()) ;; Não é trabalho !!!!!
beehuang
Eu executei o snippet de segundos antes das 17h08 no meu fuso horário e peguei ans=Mon Jun 18 01:07:56 CEST 2018, o que está incorreto e wrongAns=Sun Jun 17 17:07:56 CEST 2018, em seguida , o que está correto.
Ole VV
Eu não tenho nenhum problema ao executar o aplicativo no meu computador. Mas ao usar docker, tem problema. Porque o fuso horário no docker não é o seu fuso horário no computador. Você deve definir seu fuso horário corretamente no Código. Por exemplo, Ásia / Taipei. Em seguida, ele corrigirá no AWS, Docker, GCP ou qualquer computador.
beehuang
@ OleV.V. Você consegue entender ?? ou alguma dúvida?
beehuang
1

A resposta aceita não funcionou para mim. A data retornada é sempre a data local e não a data do fuso horário original. Eu moro na UTC + 2.

//This did not work for me
Date.from(java.time.ZonedDateTime.now().toInstant()); 

Eu vim com duas maneiras alternativas de obter a data correta de um ZonedDateTime.

Digamos que você tenha este ZonedDateTime para o Havaí

LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10

ou para UTC como solicitado originalmente

Instant zulu = Instant.now(); // GMT, UTC+0
ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));

Alternativa 1

Podemos usar java.sql.Timestamp. É simples, mas provavelmente também afetará sua integridade de programação

Date date1 = Timestamp.valueOf(zdt.toLocalDateTime());

Alternativa 2

Criamos a data de millis (respondido aqui anteriormente). Observe que o ZoneOffset local é obrigatório.

ZoneOffset localOffset = ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now());
long zonedMillis = 1000L * zdt.toLocalDateTime().toEpochSecond(localOffset) + zdt.toLocalDateTime().getNano() / 1000000L;
Date date2 = new Date(zonedMillis);
Avec
fonte
0

Para um aplicativo docker como o beehuang comentou, você deve definir seu fuso horário.

Como alternativa, você pode usar withZoneSameLocal . Por exemplo:

01-07-2014T00: 00 + 02: 00 [GMT + 02: 00] é convertido por

Date.from(zonedDateTime.withZoneSameLocal(ZoneId.systemDefault()).toInstant())

para Ter 01 de julho 00:00:00 CEST 2014 e até

Date.from(zonedDateTime.toInstant())

a Seg 30 de junho às 22:00:00 UTC de 2014

dwe
fonte
-1

Se você está interessado apenas no agora, basta usar:

Date d = new Date();
Jacob Eckel
fonte
Estou interessado agora, mas agora UTC.
Milen Kovachev
2
Sim, esse será o UTC agora, o Date não conhece nada melhor.
Jacob Eckel
3
Não, não vai. Será agora no fuso horário do sistema local. A data não armazena nenhuma informação de fuso horário, mas usa a hora do sistema atual e ignora o fuso horário do sistema atual, portanto, nunca o converte de volta para UTC
David
2
@David Date mantém a hora relativa à época UTC, então se você pegar uma nova Date () de um sistema nos EUA e outro objeto de um computador no Japão, eles serão idênticos (observe o tempo que ambos mantêm internamente).
Jacob Eckel
1
@JacobEckel - Sim, mas se você colocar 9h em uma data enquanto seu tz é um tz dos EUA e então mudar para JP tz e criar uma nova data com 9h, o valor interno em Date será diferente. Assim que você adiciona o DST à mistura, não pode usar o Date de forma confiável, a menos que seu aplicativo SEMPRE seja executado em UTC especificamente, o que pode mudar por uma série de razões, muitas das quais giram em torno de um código incorreto.
David