AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 77251539
Accepted
Paul Marcelin Bejan
Paul Marcelin Bejan
Asked: 2023-10-08 05:16:31 +0800 CST2023-10-08 05:16:31 +0800 CST 2023-10-08 05:16:31 +0800 CST

Como usar as classes java.time corretamente?

  • 772

Sei que isso pode parecer estranho após 3 anos de desenvolvimento de software, mas estou tentando entender melhor as classes java.time e como usá-las para evitar erros e práticas inadequadas.

No primeiro projeto em que estive envolvido, a versão java utilizada foi a 8, mas os valores de data foram armazenados na classe legada java.util.Date

Dois anos depois, nova empresa, novo projeto com java versão 17, mas ainda valores de data são armazenados na classe legada java.util.Date

A confusão começou quando vi que havia um DateUtils no projeto com métodos como:

public static String toString_ddMMyyyy_dotted(Date date){
    LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefualt()).toLocalDateTime();
    DateTimeFormatter.ofPattern("dd.MM.yyyy").format(localDateTime);
}

public static Date getBeginOfTheYear(Date date){
    LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefualt()).toLocalDate();
    LocalDate beginOfTheYear = localDate.withMonth(1).withDayOfMonth(1);
    return Date.from(beginOfTheYear.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
}

Então minha pergunta foi: por que não usar diretamente as classes java.time que possuem APIs realmente boas em vez de converter uma variável em LocalDate/LocalDateTime sempre que uma operação na Data for necessária?

A ideia é refatorar o projeto o mais rápido possível para armazenar valores de Data/DataHora/Hora em classes java.time. Comecei com um módulo simples, que salva alguns dados no MongoDB. Apliquei a seguinte conversão de tipo de campo:

  • Valores de data em LocalDate
  • Valores de data e hora em LocalDateTime

Durante o teste, encontrei imediatamente um problema, uma variável localDateTime com valor de 2023-10-07 12:00:00 foi salva no MongoDB como 2023-10-07 10:00:00 Isso não é um problema, desde que os dados é buscado de volta em java, o valor volta para 2023-10-07 12:00:00, mas isso não está acontecendo, então é um grande problema.

Primeiramente, isso acontece apenas com o LocalDateTime ou também acontece com o LocalDate?

Quais são as melhores práticas para armazenar valores de data/data e hora em classes de entidade/documento? Devo usar ZonedDateTime em vez de LocalDateTime?

java
  • 1 1 respostas
  • 120 Views

1 respostas

  • Voted
  1. Best Answer
    Basil Bourque
    2023-10-08T10:12:42+08:002023-10-08T10:12:42+08:00

    Conversão em vez de renovação

    A ideia é refatorar o projeto o mais rápido possível para armazenar valores de Data/DataHora/Hora em classes java.time

    Dependendo do tamanho e da complexidade da sua base de código, isso pode ser bastante arriscado.

    Uma abordagem mais conservadora é fazer toda a nova programação em java.time . Para interoperar com código antigo ainda não atualizado para java.time , converta. Você encontrará novos métodos de conversão nas classes antigas. Procure to…e from…métodos. Esses métodos de conversão fornecem cobertura completa, permitindo que você vá e volte.

    Outra palavra de cautela ao renovar o código: muitos/a maioria dos programadores não entendem bem o tratamento de data e hora. O tópico é surpreendentemente complicado e complicado, os conceitos escorregadios quando encontrados pela primeira vez. Nossa compreensão cotidiana de data e hora realmente funciona contra nós quando lidamos com o rigor da programação e dos bancos de dados. Portanto, tome cuidado com códigos ruins, códigos com bugs, que manipulam mal a data e a hora. Cada vez que você descobrir esse código defeituoso, você estará abrindo uma série de problemas, pois relatórios podem ter sido produzidos mostrando resultados incorretos, bancos de dados podem armazenar dados inválidos, etc.

    Você pode pesquisar perguntas e respostas existentes do Stack Overflow para saber mais. Abaixo estão alguns breves pontos para ajudar a orientá-lo.

    Momento versus Não-momento

    Valores de data em LocalDate

    Valores de data e hora em LocalDateTime

    Incorreta. Ou incompleto, devo dizer.

    Eu recomendo que você estruture seu pensamento desta forma… Existem dois tipos de controle de tempo:

    • Momento (definitivo)
    • Nem um momento (indefinido)

    Momento

    Um momento é um ponto específico na linha do tempo. Os momentos são rastreados em java.time como uma contagem de segundos inteiros, mais um segundo fracionário como uma contagem de nanossegundos, desde a referência de época do primeiro momento de 1970 em UTC. "in UTC" é a abreviação de "um deslocamento do meridiano temporal do UTC de zero horas-minutos-segundos".

    A classe básica para representar um momento é java.time.Instant. Objetos desta classe representam um momento visto em UTC, sempre em UTC. Esta classe é o bloco de construção básico da estrutura jav.time . Esta aula deve ser seu primeiro e último pensamento ao lidar com momentos. Use Instantpara rastrear um momento, a menos que suas regras de negócios envolvam especificamente uma zona ou deslocamento específico. E java.util.Dateé especificamente substituído por java.time.Instant, ambos representando um momento no UTC, mas com Instantuma resolução mais precisa de nanossegundos em relação à java.util.Dateresolução de milissegundos.

    Duas outras classes rastreiam momentos em java.time : OffsetDateTime& ZonedDateTime.

    OffsetDateTimerepresenta um momento visto com um deslocamento do UTC diferente de zero. Um deslocamento é apenas um número de horas-minutos-segundos à frente ou atrás do UTC. Assim, as pessoas em Paris acertam os relógios nas paredes uma ou duas horas antes do UTC, enquanto as pessoas em Tóquio acertam os relógios nove horas antes, enquanto as pessoas na Nova Escócia, Canadá, acertam os relógios três ou quatro horas antes do UTC .

    Um fuso horário é muito mais do que um mero deslocamento. Um fuso horário é uma história nomeada de mudanças passadas, presentes e futuras no deslocamento usado pelas pessoas de uma determinada região, conforme decidido por seus políticos. Um fuso horário tem um nome no formato Continent/Regioncomo Europe/Paris& Asia/Tokyo.

    Para visualizar a data e a hora do dia através do horário de uma determinada região, use ZonedDateTime.

    Observe que você pode alternar de maneira fácil e limpa entre essas três classes, Instant, OffsetDateTimee ZonedDateTimepor meio de seus métodos to…, at…e . with…Eles fornecem três maneiras de olhar para o mesmo momento. Geralmente você usa Instantem sua lógica de negócios e armazenamento e troca de dados, enquanto usa ZonedDateTimepara apresentação ao usuário.

    Nem um momento

    Por outro lado, não temos rastreamento “nem um momento”. Esses são os tipos indefinidos.

    A classe que não causa mais confusão é LocalDateTime. Esta classe representa uma data com hora do dia, mas sem qualquer conceito de deslocamento ou fuso horário. Portanto, se eu disser meio-dia no próximo dia 23 de janeiro de 2024, LocalDateTime.of( 2024 , Month.JANUARY , 23 , 12 , 0 , 0 , 0 )você não tem como saber se me refiro ao meio-dia em Tóquio, ao meio-dia em Toulouse ou ao meio-dia em Toledo, Ohio, EUA - três momentos muito diferentes com várias horas de intervalo.

    Na dúvida, não use LocalDateTime. Geralmente, na programação de aplicativos de negócios, nos preocupamos com os momentos e, portanto, raramente usamos arquivos LocalDateTime.

    A grande exceção, quando mais precisamos LocalDateTimeem aplicativos empresariais, é o agendamento de consultas, como para o dentista. Lá, precisamos manter a LocalDateTimee fuso horário ( ZoneId) separadamente , em vez de combiná-los em a ZonedDateTime. A razão é crucial: os políticos mudam frequentemente as regras do fuso horário, e fazem isso de forma imprevisível e muitas vezes com pouco aviso prévio. Portanto, a consulta do dentista às 15h pode ocorrer uma hora antes, ou uma hora depois, se os políticos decidirem adotar o horário de verão (DST), ou decidirem abandonar o horário de verão, ou decidirem permanecer no horário de verão o ano todo (o último moda), ou decidir que uma compensação diferente pode trazer oportunidades de negócios (ex: Islândia adotando uma compensação de zero) ou resolver questões políticas (ex: um único fuso horário em toda a Índia), ou decidir ajustar seu relógio como um movimento diplomático em relação a um país vizinho, ou deve adotar a compensação de uma força invasora/ocupante. Tudo isso acontece com muito mais frequência do que a maioria das pessoas, inclusive os programadores, imaginam. Essas mudanças quebram desnecessariamente aplicativos escritos de forma ingênua.

    Os outros tipos indefinidos incluem:

    • LocalDatepara um valor somente de data, sem hora do dia e sem qualquer zona/deslocamento.
    • LocalTimepara um valor somente de tempo sem qualquer data e sem qualquer zona/deslocamento.

    Isso LocalDatepode ser complicado porque muitas pessoas não sabem que, em um determinado momento, a data varia em todo o mundo de acordo com o fuso horário. Agora é “amanhã” em Tóquio, Japão, e simultaneamente “ontem” em Edmonton, Canadá.

    Seu código

    LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefualt()).toLocalDateTime();
    

    Esse código tem dois problemas.

    • Um deles é o uso inadequado de LocalDateTime. A java.util.Dateclasse atrás datede var representa um momento com deslocamento zero. Você descarta a informação crucial, o deslocamento, ao converter para LocalDateTime.
    • Outro problema é o uso de ZoneId.systemDefualt(). Isso significa que os resultados variam dependendo do fuso horário padrão atual da JVM. Conforme discutido acima, isso significa que a data pode variar, não apenas a hora do dia. O mesmo código executado em duas máquinas diferentes pode produzir duas datas diferentes. Isso pode ser o que você deseja em seu aplicativo ou pode ser um rude despertar para um programador inconsciente.

    Seu outro trecho de código também apresenta problemas.

    public static Date getBeginOfTheYear(Date date){
        LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefualt()).toLocalDate();
        LocalDate beginOfTheYear = localDate.withMonth(1).withDayOfMonth(1);
        return Date.from(beginOfTheYear.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
    }
    

    Em primeiro lugar, esteja ciente de que, infelizmente, existem duas Date classes entre as classes herdadas de data e hora: java.util.Datepor um momento, e java.sql.Dateque finge representar apenas uma data, mas na verdade é um momento (em uma decisão terrivelmente ruim de design de classe). Eu assumirei seus meios java.util.Date.

    Em segundo lugar, evite esses tipos de cadeias de chamadas com esse tipo de código. A legibilidade, a depuração e o registro ficam mais difíceis. Em vez disso, escreva linhas curtas e simples ao fazer uma série de conversões. Use comentários em cada linha para justificar a operação e explicar sua motivação. E evite o uso varpelo mesmo motivo; use tipos de retorno explícitos ao fazer tais conversões.

    Isso date.toInstant()é bom. Ao encontrar um java.util.Date, converta imediatamente para um Instant.

    Novamente, o uso de ZoneId.systemDefualt()significa que os resultados do seu código variam de acordo com o capricho de qualquer administrador de sistema ou usuário que esteja alterando o fuso horário padrão. Esta pode ser a intenção do autor deste código, mas duvido.

    A parte beginOfTheYear.atStartOfDay()produz um LocalDateTimequando você não passa nenhum argumento. Então você está novamente descartando informações valiosas (compensação) sem ganhar nada em troca.

    Outro problema: seu código nem será compilado. O java.util.Date.frommétodo recebe um Instant, não um LocalDateTimeretornado pela sua chamada beginOfTheYear.atStartOfDay().

    Para obter corretamente o primeiro momento do ano de um determinado momento, é quase certo que você desejaria que as pessoas que decidem as regras de negócios ditassem um fuso horário específico.

    ZoneId zTokyo = ZoneId.of( "Asia/Tokyo" ) ;
    

    Você realmente deve converter a entrada java.util.Dateem um arquivo Instant.

    Instant instant = myJavaUtilDate.toInstant() ;
    

    Em seguida, aplique a zona.

    ZonedDateTime zdt = instant.atZone( zTokyo ) ;
    

    Extraia a parte do ano. Conforme discutido acima, a data pode variar de acordo com o fuso horário. Portanto, o ano pode variar dependendo da zona que você forneceu na linha anterior do código acima!

    Year year = Year.from( zdt ) ;
    

    Obtenha o primeiro dia daquele ano.

    LocalDate firstOfYear = year.atDay( 1 ) ;
    

    Deixe java.time determinar o primeiro momento daquele dia naquela zona. Não presuma que o horário de início é 00:00. Algumas datas em algumas zonas começam em outro horário, como 01:00.

    Observe que na determinação do primeiro momento do ano, passamos o argumento for ZoneIdto atStartOfDay, em contraste com o seu código. O resultado aqui é um ZonedDateTimeem vez de no LocalDateTimeseu código.

    ZonedDateTime zdtFirstMomentOfTheYearInTokyo = firstOfYear.atStartOfDay( zTokyo ) ; 
    

    Por último, convertemos para java.util.Date. No código greenfield , evitaríamos essa classe como a Peste. Mas na sua base de código existente, devemos converter para interoperar com as partes do seu código antigo ainda não atualizadas para java.time .

    Instant instantFirstMomentOfTheYearInTokyo = zdtFirstMomentOfTheYearInTokyo.toInstant(); 
    java.util.Date d = java.util.Date.from( instantFirstMomentOfTheYearInTokyo ) ;
    

    MongoDB

    Você disse:

    Durante o teste, encontrei imediatamente um problema, uma variável localDateTime com valor de 2023-10-07 12:00:00 foi salva no MongoDB como 2023-10-07 10:00:00 Isso não é um problema, desde que os dados é buscado de volta em java, o valor volta para 2023-10-07 12:00:00, mas isso não está acontecendo, então é um grande problema.

    Muita coisa para desempacotar aí, com poucos detalhes seus.

    Sugiro que você poste outra pergunta especificamente sobre esse assunto. Forneça detalhes suficientes, juntamente com código de exemplo, para fazer um diagnóstico.

    • 1

relate perguntas

  • Lock Condition.notify está lançando java.lang.IllegalMonitorStateException

  • Resposta de microsserviço Muitos para Um não aparece no carteiro

  • Validação personalizada do SpringBoot Bean

  • Os soquetes Java são FIFO?

  • Por que não é possível / desencorajado definir um lado do servidor de tempo limite de solicitação?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    destaque o código em HTML usando <font color="#xxx">

    • 2 respostas
  • Marko Smith

    Por que a resolução de sobrecarga prefere std::nullptr_t a uma classe ao passar {}?

    • 1 respostas
  • Marko Smith

    Você pode usar uma lista de inicialização com chaves como argumento de modelo (padrão)?

    • 2 respostas
  • Marko Smith

    Por que as compreensões de lista criam uma função internamente?

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Marko Smith

    java.lang.NoSuchMethodError: 'void org.openqa.selenium.remote.http.ClientConfig.<init>(java.net.URI, java.time.Duration, java.time.Duratio

    • 3 respostas
  • Marko Smith

    Por que 'char -> int' é promoção, mas 'char -> short' é conversão (mas não promoção)?

    • 4 respostas
  • Marko Smith

    Por que o construtor de uma variável global não é chamado em uma biblioteca?

    • 1 respostas
  • Marko Smith

    Comportamento inconsistente de std::common_reference_with em tuplas. Qual é correto?

    • 1 respostas
  • Marko Smith

    Somente operações bit a bit para std::byte em C++ 17?

    • 1 respostas
  • Martin Hope
    fbrereto Por que a resolução de sobrecarga prefere std::nullptr_t a uma classe ao passar {}? 2023-12-21 00:31:04 +0800 CST
  • Martin Hope
    比尔盖子 Você pode usar uma lista de inicialização com chaves como argumento de modelo (padrão)? 2023-12-17 10:02:06 +0800 CST
  • Martin Hope
    Amir reza Riahi Por que as compreensões de lista criam uma função internamente? 2023-11-16 20:53:19 +0800 CST
  • Martin Hope
    Michael A formato fmt %H:%M:%S sem decimais 2023-11-11 01:13:05 +0800 CST
  • Martin Hope
    God I Hate Python std::views::filter do C++20 não filtrando a visualização corretamente 2023-08-27 18:40:35 +0800 CST
  • Martin Hope
    LiDa Cute Por que 'char -> int' é promoção, mas 'char -> short' é conversão (mas não promoção)? 2023-08-24 20:46:59 +0800 CST
  • Martin Hope
    jabaa Por que o construtor de uma variável global não é chamado em uma biblioteca? 2023-08-18 07:15:20 +0800 CST
  • Martin Hope
    Panagiotis Syskakis Comportamento inconsistente de std::common_reference_with em tuplas. Qual é correto? 2023-08-17 21:24:06 +0800 CST
  • Martin Hope
    Alex Guteniev Por que os compiladores perdem a vetorização aqui? 2023-08-17 18:58:07 +0800 CST
  • Martin Hope
    wimalopaan Somente operações bit a bit para std::byte em C++ 17? 2023-08-17 17:13:58 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve