Executando verificação de segurança...
4
kht
5 min de leitura ·

Lidando com datas em JavaScript (ou "criei uma data, mas mostra um dia a menos")

Um erro "clássico" que ocorre com JavaScript, ao criar uma data específica e imprimi-la, é ele mostrar um valor diferente do que você esperava:

// 1 de março de 2023
const data = new Date('2023-03-01');

// ao imprimir, mostra 28 de fevereiro
console.log(data.toLocaleString('pt-BR')); // 28/02/2023, 21:00:00 ????

O que aconteceu? Bem, vamos por partes...

O que é o Date do JavaScript?

O Date, apesar do nome, não é exatamente uma data. Pelo menos não no sentido de representar uma única combinação de dia, mês, ano, hora, minuto e segundo.

Na verdade, segundo a especificação da linguagem, o único valor que um Date possui é um número que corresponde ao Unix timestamp. Mais precisamente, esse número é a quantidade de milissegundos que se passaram desde o Unix Epoch (que seria o "instante zero").

O "instante zero", por sua vez, corresponde a 1970-01-01T00:00Z - 1 de janeiro de 1970 à meia-noite, em UTC.

Portanto, o Date representa um instante único, um ponto na linha do tempo. Pense no "agora": neste exato momento, que dia é hoje e que horas são? Em cada parte do mundo, a resposta será diferente (em algumas partes do mundo, é 1 de março, em outras pode ser dia 2 de março ou 28 de fevereiro, e o horário também pode ser diferente). Apesar dos valores numéricos de data e hora serem diferentes, o instante (o valor do timestamp) é o mesmo para todos. E o Date só possui o valor do timestamp.

O que acontece é que quando você imprime a data (via alert ou console.log, por exemplo), ou obtém informações dela (seja via os getters ou toString()), ou a converte para algum formato (como acontece com toLocaleString()), este timestamp é convertido para alguma data e hora específica, usando para isso o fuso horário (timezone) que está configurado no ambiente onde o código está rodando (browser, Node, Deno, etc). Ou seja, o Date usa a informação do timezone para converter o timestamp para os valores numéricos de data e hora.


Constrir um Date a partir de uma string

Outro ponto é que, quando passamos uma string ao construtor de Date, como foi feito no código acima, ele usa as regras do método Date.parse. E segundo essas regras, uma string no formato "AAAA-MM-DD" (que é definido pela norma ISO 8601), ou seja, somente com a data e sem horário, é interpretado como meia-noite em UTC.

Portanto, no código acima, a data corresponde a 1 de março, à meia-noite em UTC. Mas meu browser está configurado com o Horário de Brasília, e por isso toLocaleString mostrou os valores de data e hora correspondentes a este timezone. E como o Horário de Brasília está 3 horas antes de UTC, ele mostra a data "errada".

Lembrando que se seu ambiente está usando outro timezone, os resultados serão diferentes do meu.

E só pra confundir ainda mais, existem métodos em Date que trabalham com UTC, como toISOString(). Veja a diferença:

const data = new Date('2023-03-01');

// toLocaleString usa o timezone do browser (no meu caso, é o Horário de Brasília), por isso mostra 28 de fevereiro
console.log(data.toLocaleString('pt-BR')); // 28/02/2023, 21:00:00

// toISOString usa sempre UTC, então mostra 1 de março à meia-noite (o "Z" no final indica que é UTC)
console.log(data.toISOString());           // 2023-03-01T00:00:00.000Z

Como resolver?

Existem várias soluções diferentes.

Uma é adicionando o horário na string. Parece gambiarra, mas a especificação da linguagem define que se a string contém o horário, aí ele usa o timezone do browser/ambiente em vez de UTC:

date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time

Não me pergunte porque decidiram assim. Como disse uma vez um professor: "Eu não criei as regras, eu só as ensino". Então ficaria assim:

// string tem horário, então agora usa o timezone do browser
const data = new Date('2023-03-01T00:00');
console.log(data.toLocaleString('pt-BR')); // 01/03/2023, 00:00:00

// lembrando que o Horário de Brasília está 3 horas antes de UTC
// por isso se usar métodos que trabalham com UTC, ficará 3 horas "na frente"
console.log(data.toISOString()); // 2023-03-01T03:00:00.000Z

Ou, se eu já sei que a data criada considera UTC, então basta usar UTC na hora de formatá-la:

// string sem horário, então usa UTC
const data = new Date('2023-03-01');
// indico que quero usar UTC
console.log(data.toLocaleString('pt-BR', { timeZone: 'UTC' })); // 01/03/2023, 00:00:00

Ou então você pode extrair os valores numéricos da string e passá-los separadamente para o construtor, pois aí ele também considera que o horário é meia-noite no timezone do browser. Um detalhe chato é que ao usar valores numéricos, o mês é indexado em zero (janeiro é zero, fevereiro é 1, etc), então tem que lembrar de subtrair 1:

const string = '2023-03-01';
// extrai os valores numéricos
const [ano, mes, dia] = string.split('-').map(v => parseInt(v));

// assim, ele considera meia-noite no timezone do browser
const data = new Date(ano, mes - 1, dia); // lembrando que agora janeiro é zero, fevereiro é 1, etc
console.log(data.toLocaleString('pt-BR')); // 01/03/2023, 00:00:00

Claro, a "melhor" solução depende do que você precisa. Lembrando que há diferenças entre usar meia-noite em UTC ou no timezone do browser, já que correspondem a instantes diferentes (a menos, é claro, que o timezone seja igual a UTC, como ocorre na Inglaterra quando não está em horário de verão). Ou seja, o valor do timestamp não será o mesmo:

// meia-noite no timezone do browser (no meu caso, Horário de Brasília)
console.log(new Date('2023-03-01T00:00').getTime()); // 1677639600000

// meia-noite em UTC
console.log(new Date('2023-03-01').getTime());       // 1677628800000

Dependendo do que você vai fazer com a data, pode ou não fazer diferença. Por isso não existe o método "correto" para todas as situações. O importante é saber como cada alternativa funciona, e ver qual se encaixa melhor no seu caso.


Baseado neste post do meu blog.

Carregando publicação patrocinada...
2

Alguém que lê a especificação!
Que milagre!

Ótimo texto!

rsrsrs!

o Objeto Date é uma copia do Objeto Date do JAVA da época!

Aqui o Brendam falando sobre
https://twitter.com/brendaneich/status/481939099138654209

https://twitter.com/brendaneich/status/845826909061623808

Mas temos um novo Objeto para manipular tempo/datas para JS
trazendo uma API de data/hora moderna para a linguagem ECMAScript.
O Temporal que esta em estagio 3.

https://github.com/tc39/proposal-temporal

Ainda não implementada em nenhum navegador ou no Node

1

Alguém que lê a especificação! Que milagre!

Pior que não deveria ser algo fora do comum, né? :-)

A especificação é onde os achismos morrem (idealmente, claro, tem umas mal feitas que causam mais dúvida ainda, mas não é o caso aqui). Todo mundo deveria ter o hábito de consultá-la, pelo menos de vez em quando (isso vale pra qualquer linguagem).

Date é uma copia do Objeto Date do JAVA da época!

Sim, tanto que copiaram até os defeitos (janeiro é zero, internamente usa timestamp mas na hora imprimir usa o timezone do ambiente, etc).

temos um novo Objeto para manipular tempo/datas para JS

Não vejo a hora do Temporal ficar pronto. Faz um tempo que empacou no estágio 3, espero que avance logo.

1
1