O guia definitivo para manipulação de data e hora
Publicados: 2022-03-11Como desenvolvedor de software, você não pode fugir da manipulação de datas. Quase todos os aplicativos que um desenvolvedor cria terão algum componente em que a data/hora precisa ser obtida do usuário, armazenada em um banco de dados e exibida de volta ao usuário.
Pergunte a qualquer programador sobre sua experiência em lidar com datas e fusos horários e eles provavelmente compartilharão algumas histórias de guerra. Manipular campos de data e hora certamente não é ciência de foguetes, mas muitas vezes pode ser tedioso e propenso a erros.
Existem centenas de artigos sobre o assunto por aí, no entanto, a maioria é muito acadêmica, focando em detalhes minuciosos, ou são muito irregulares, fornecendo pequenos trechos de código sem muita explicação que os acompanhe. Este guia detalhado para manipulação de DateTime deve ajudá-lo a entender os conceitos de programação e as práticas recomendadas relevantes para hora e data sem precisar navegar por um mar de informações sobre esse tópico.
Neste artigo, vou ajudá-lo a pensar claramente sobre os campos de data e hora e sugerir algumas práticas recomendadas que podem ajudá-lo a evitar o inferno de data/hora. Aqui, exploraremos alguns dos principais conceitos necessários para manipular corretamente os valores de data e hora, formatos convenientes para armazenar valores DateTime e transferi-los por APIs e muito mais.
Para começar, a resposta certa para o código de produção é quase sempre usar uma biblioteca adequada em vez de usar a sua própria. As possíveis dificuldades com o cálculo de DateTime discutidas neste artigo são apenas a ponta do iceberg, mas ainda é útil conhecê-las, com ou sem uma biblioteca.
Bibliotecas DateTime ajudam se você as entender corretamente
As bibliotecas de datas ajudam de várias maneiras a facilitar sua vida. Eles simplificam bastante a análise de datas, operações aritméticas e lógicas de datas e formatação de datas. Você pode encontrar uma biblioteca de datas confiável para o front-end e o back-end para fazer a maior parte do trabalho pesado para você.
No entanto, muitas vezes usamos bibliotecas de datas sem pensar em como a data/hora realmente funciona. Data/hora é um conceito complexo. Os bugs que surgem devido ao seu entendimento incorreto podem ser extremamente difíceis de entender e corrigir, mesmo com a ajuda de bibliotecas de data. Como programador, você precisa entender o básico e ser capaz de apreciar os problemas que as bibliotecas de data resolvem para aproveitá-las ao máximo.
Além disso, as bibliotecas de data/hora só podem levá-lo até agora. Todas as bibliotecas de datas funcionam dando acesso a estruturas de dados convenientes para representar um DateTime. Se você estiver enviando e recebendo dados por meio de uma API REST, eventualmente precisará converter a data em uma string e vice-versa, pois o JSON não possui uma estrutura de dados nativa para representar DateTime. Os conceitos que descrevi aqui ajudarão você a evitar alguns dos problemas comuns que podem surgir ao fazer essas transformações de data para string e string para data.
Observação: embora eu tenha usado JavaScript como a linguagem de programação discutida neste artigo, esses são conceitos gerais que se aplicam, em grande parte, a praticamente todas as linguagens de programação e suas bibliotecas de data. Portanto, mesmo que você nunca tenha escrito uma linha de JavaScript antes, sinta-se à vontade para continuar lendo, pois dificilmente assumo qualquer conhecimento prévio de JavaScript no artigo.
Padronizando o tempo
Um DateTime é um ponto muito específico no tempo. Vamos pensar sobre isso. Enquanto escrevo este artigo, o relógio do meu laptop mostra 21 de julho às 13h29. Isso é o que chamamos de “hora local”, a hora que vejo nos relógios de parede ao meu redor e no meu relógio de pulso.
Mais ou menos alguns minutos, se eu pedir a minha amiga para me encontrar em um café próximo às 15h, posso esperar vê-la lá mais ou menos nesse horário. Da mesma forma, não haveria confusão se eu dissesse, por exemplo, “vamos nos encontrar em uma hora e meia”. Costumamos falar sobre o tempo dessa maneira com pessoas que moram na mesma cidade ou fuso horário.
Vamos pensar em um cenário diferente: quero dizer a um amigo que mora em Uppsala, na Suécia, que quero falar com ele às 17h. Eu mando uma mensagem para ele: “Ei, Anton, vamos conversar às 17h”. Eu imediatamente recebo a resposta: “Seu tempo ou meu tempo?”
Anton me diz que mora no fuso horário da Europa Central, que é UTC+01:00. Eu moro em UTC+05:45. Isso significa que quando são 17:00 onde eu moro, são 17:00 - 05:45 = 11:15 UTC, o que se traduz em 11:15 UTC + 01:00 = 12:15 em Uppsala, perfeito para ambos de nós.
Além disso, esteja ciente da diferença entre o fuso horário (Horário da Europa Central) e o deslocamento do fuso horário (UTC+05:45). Os países podem decidir alterar seus deslocamentos de fuso horário para o horário de verão também por motivos políticos. Quase todos os anos há uma mudança nas regras em pelo menos um país, o que significa que qualquer código com essas regras incorporadas deve ser mantido atualizado – vale a pena considerar do que sua base de código depende para cada camada do seu aplicativo.
Essa é outra boa razão pela qual recomendamos que apenas o front-end lide com fusos horários na maioria dos casos. Quando isso não acontece, o que acontece quando as regras que seu mecanismo de banco de dados usa não correspondem às de seu front-end ou back-end?
Esse problema de gerenciar duas versões diferentes do tempo, em relação ao usuário e em relação a um padrão universalmente aceito, é difícil, ainda mais no mundo da programação onde a precisão é fundamental e mesmo um segundo pode fazer uma enorme diferença. O primeiro passo para resolver esses problemas é armazenar DateTime em UTC.
Padronização do formato
Padronizar o horário é maravilhoso porque eu só preciso armazenar o horário UTC e desde que eu saiba o fuso horário do usuário, sempre posso converter para o horário dele. Por outro lado, se eu souber o horário local de um usuário e seu fuso horário, posso convertê-lo em UTC.
Mas datas e horas podem ser especificadas em muitos formatos diferentes. Para a data, você pode escrever “30 de julho” ou “30 de julho” ou “30/7” (ou 30/7, dependendo de onde você mora). Para a hora, você pode escrever “9:30 PM” ou “2130”.
Cientistas de todo o mundo se uniram para resolver esse problema e decidiram por um formato para descrever o tempo que os programadores realmente gostam porque é curto e preciso. Gostamos de chamá-lo de “formato de data ISO”, que é uma versão simplificada do formato estendido ISO-8601 e se parece com isso:
Para 00:00 ou UTC, usamos “Z”, que significa hora Zulu, outro nome para UTC.
Manipulação de data e aritmética em JavaScript
Antes de começarmos com as práticas recomendadas, aprenderemos sobre manipulação de data usando JavaScript para entender a sintaxe e os conceitos gerais. Embora usemos JavaScript, você pode adaptar facilmente essas informações à sua linguagem de programação favorita.
Usaremos a aritmética de datas para resolver problemas comuns relacionados a datas que a maioria dos desenvolvedores encontra.
Meu objetivo é deixá-lo confortável criando um objeto de data de uma string e extraindo componentes de um. Isso é algo que uma biblioteca de datas pode ajudá-lo, mas é sempre melhor entender como isso é feito nos bastidores.
Depois de sujar as mãos com data/hora, fica mais fácil pensar nos problemas que enfrentamos, extrair as melhores práticas e seguir em frente. Se você quiser pular para as práticas recomendadas, sinta-se à vontade para fazê-lo, mas eu recomendo que você pelo menos dê uma olhada na seção aritmética de datas abaixo.
O objeto de data JavaScript
As linguagens de programação contêm construções úteis para facilitar nossas vidas. O objeto JavaScript Date é uma dessas coisas. Ele oferece métodos convenientes para obter a data e hora atuais, armazenar uma data em uma variável, realizar aritmética de data e formatar a data com base na localidade do usuário.
Devido às diferenças entre as implementações do navegador e o manuseio incorreto do horário de verão (DST), depender do objeto Date para aplicativos de missão crítica não é recomendado e você provavelmente deve usar uma biblioteca DateTime como Luxon, date-fns ou dayjs. (O que quer que você use, evite o Moment.js, que já foi popular, muitas vezes chamado simplesmente de moment , como aparece no código, já que agora está obsoleto.)
Mas para fins educacionais, usaremos os métodos que o objeto Date() fornece para aprender como o JavaScript lida com DateTime.
Obtendo a data atual
const currentDate = new Date();Se você não passar nada para o construtor Date, o objeto date retornado conterá a data e hora atuais.
Você pode formatá-lo para extrair apenas a parte da data da seguinte maneira:
const currentDate = new Date(); const currentDayOfMonth = currentDate.getDate(); const currentMonth = currentDate.getMonth(); // Be careful! January is 0, not 1 const currentYear = currentDate.getFullYear(); const dateString = currentDayOfMonth + "-" + (currentMonth + 1) + "-" + currentYear; // "27-11-2020"Nota: A armadilha “Janeiro é 0” é comum, mas não universal. Vale a pena verificar a documentação de qualquer idioma (ou formato de configuração: por exemplo, cron é notavelmente baseado em 1) antes de começar a usá-lo.
Obtendo o carimbo de hora atual
Se você quiser obter o timestamp atual, você pode criar um novo objeto Date e usar o método getTime().
const currentDate = new Date(); const timestamp = currentDate.getTime();Em JavaScript, um carimbo de data/hora é o número de milissegundos que se passaram desde 1º de janeiro de 1970.
Se você não pretende oferecer suporte ao <IE8, você pode usar Date.now() para obter diretamente o carimbo de hora sem ter que criar um novo objeto Date.
Analisando uma data
A conversão de uma string em um objeto de data JavaScript é feita de diferentes maneiras.
O construtor do objeto Date aceita uma ampla variedade de formatos de data:
const date1 = new Date("Wed, 27 July 2016 13:30:00"); const date2 = new Date("Wed, 27 July 2016 07:45:00 UTC"); const date3 = new Date("27 July 2016 13:30:00 UTC+05:45");Observe que você não precisa incluir o dia da semana porque o JS pode determinar o dia da semana para qualquer data.
Você também pode passar o ano, mês, dia, horas, minutos e segundos como argumentos separados:
const date = new Date(2016, 6, 27, 13, 30, 0);Claro, você sempre pode usar o formato de data ISO:
const date = new Date("2016-07-27T07:45:00Z");No entanto, você pode ter problemas quando não fornecer o fuso horário explicitamente!
const date1 = new Date("25 July 2016"); const date2 = new Date("July 25, 2016");Qualquer um deles lhe dará 25 de julho de 2016 00:00:00 hora local.
Se você usar o formato ISO, mesmo que forneça apenas a data e não a hora e o fuso horário, ele aceitará automaticamente o fuso horário como UTC.
Isso significa que:
new Date("25 July 2016").getTime() !== new Date("2016-07-25").getTime() new Date("2016-07-25").getTime() === new Date("2016-07-25T00:00:00Z").getTime()Formatando uma data
Felizmente, o JavaScript moderno tem algumas funções de internacionalização convenientes incorporadas ao namespace Intl padrão que tornam a formatação de datas uma operação simples.
Para isso, precisaremos de dois objetos: um Date e um Intl.DateTimeFormat , inicializados com nossas preferências de saída. Supondo que gostaríamos de usar o formato americano (M/D/AAAA), isso ficaria assim:
const firstValentineOfTheDecade = new Date(2020, 1, 14); // 1 for February const enUSFormatter = new Intl.DateTimeFormat('en-US'); console.log(enUSFormatter.format(firstValentineOfTheDecade)); // 2/14/2020 Se, em vez disso, quiséssemos o formato holandês (D/M/YYYY), passaríamos apenas um código de cultura diferente para o construtor DateTimeFormat :
const nlBEFormatter = new Intl.DateTimeFormat('nl-BE'); console.log(nlBEFormatter.format(firstValentineOfTheDecade)); // 14/2/2020Ou uma forma mais longa do formato americano, com o nome do mês escrito:
const longEnUSFormatter = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); console.log(longEnUSFormatter.format(firstValentineOfTheDecade)); // February 14, 2020 Agora, se quisermos um formato ordinal adequado no dia do mês - ou seja, "14º" em vez de apenas "14" - isso infelizmente precisa de uma solução alternativa, porque os únicos valores válidos de day até o momento são "numeric" ou "2-digit" . Tomando emprestada a versão de Flavio Copes do código de Mathias Bynens para alavancar outra parte do Intl para isso, podemos personalizar a saída do dia do mês via formatToParts() :
const pluralRules = new Intl.PluralRules('en-US', { type: 'ordinal' }) const suffixes = { 'one': 'st', 'two': 'nd', 'few': 'rd', 'other': 'th' } const convertToOrdinal = (number) => `${number}${suffixes[pluralRules.select(number)]}` // At this point: // convertToOrdinal("1") === "1st" // convertToOrdinal("2") === "2nd" // etc. const extractValueAndCustomizeDayOfMonth = (part) => { if (part.type === "day") { return convertToOrdinal(part.value); } return part.value; }; console.log( longEnUSFormatter.formatToParts(firstValentineOfTheDecade) .map(extractValueAndCustomizeDayOfMonth) .join("") ); // February 14th, 2020 Infelizmente, formatToParts não é suportado pelo Internet Explorer (IE) até o momento, mas todas as outras tecnologias de desktop, dispositivos móveis e back-end (ou seja, Node.js) têm suporte. Para aqueles que precisam dar suporte ao IE e precisam absolutamente de ordinais, a nota abaixo (ou melhor, uma biblioteca de datas adequada) fornece uma resposta.
Se você precisa dar suporte a navegadores mais antigos como o IE antes da versão 11, a formatação de data em JavaScript é mais difícil porque não havia funções padrão de formatação de data como strftime em Python ou PHP.
No PHP, por exemplo, a função strftime("Today is %b %d %Y %X", mktime(5,10,0,12,30,99)) fornece Today is Dec 30 1999 05:10:00 .
Você pode usar uma combinação diferente de letras precedidas por % para obter a data em formatos diferentes. (Cuidado, nem todos os idiomas atribuem o mesmo significado a cada letra - particularmente, 'M' e 'm' podem ser trocados por minutos e meses.)
Se você tiver certeza do formato que deseja usar, é melhor extrair bits individuais usando as funções JavaScript que abordamos acima e criar uma string você mesmo.
var currentDate = new Date (); var date = currentDate.getDate(); var month = currentDate.getMonth(); var year = currentDate.getFullYear();Podemos obter a data no formato MM/DD/AAAA como
var monthDateYear = (month+ 1 ) + "/" + date + "/" + year;O problema com esta solução é que ela pode dar um comprimento inconsistente às datas porque alguns meses e dias do mês são de um dígito e outros de dois dígitos. Isso pode ser problemático, por exemplo, se você estiver exibindo a data em uma coluna da tabela, porque as datas não estão alinhadas.
Podemos resolver isso usando uma função “pad” que adiciona um 0 inicial.
function pad ( n ) { return n< 10 ? '0' +n : n; }Agora, obtemos a data correta no formato MM/DD/AAAA usando:
var mmddyyyy = pad(month + 1 ) + "/" + pad(date) + "/" + year;Se quisermos DD-MM-AAAA, o processo é semelhante:
var ddmmyyyy = pad(date) + "-" + pad(month + 1 ) + "-" + year;Vamos aumentar a aposta e tentar imprimir a data no formato “Month Date, Year”. Vamos precisar de um mapeamento de índices de meses para nomes:
var monthNames = [ "January" , "February" , "March" , "April" , "May" , "June" , "July" , "August" , "September" , "October" , "November" , "December" ]; var dateWithFullMonthName = monthNames[month] + " " + pad(date) + ", " + year; Algumas pessoas gostam de exibir a data como 1º de janeiro de 2013. Sem problemas, tudo o que precisamos é de um ordinal de função auxiliar que retorne 1º para 1, 12º para 12 e 103º para 103, etc., e o resto é simples:
var ordinalDate = ordinal(date) + " " + monthNames[month] + ", " + year;É fácil determinar o dia da semana a partir do objeto de data, então vamos adicionar isso em:
var daysOfWeek = [ "Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" ]; ordinalDateWithDayOfWeek = daysOfWeek[currentDate.getDay()] + ", " + ordinalDate;O ponto maior aqui é que, uma vez que você extraiu os números da data, a formatação é principalmente relacionada a strings.
Alterando o formato da data
Depois de saber como analisar uma data e formatá-la, alterar uma data de um formato para outro é apenas uma questão de combinar os dois.
Por exemplo, se você tem uma data no formato 21 de julho de 2013 e deseja alterar o formato para 21-07-2013, isso pode ser feito assim:
const myDate = new Date("Jul 21, 2013"); const dayOfMonth = myDate.getDate(); const month = myDate.getMonth(); const year = myDate.getFullYear(); function pad(n) { return n<10 ? '0'+n : n } const ddmmyyyy = pad(dayOfMonth) + "-" + pad(month + 1) + "-" + year; // "21-07-2013"Usando as funções de localização do objeto de data JavaScript
Os métodos de formatação de data que discutimos acima devem funcionar na maioria dos aplicativos, mas se você realmente deseja localizar a formatação da data, sugiro que use o método toLocaleDateString() do objeto Date :
const today = new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', }); …nos dá algo como 26 Jul 2016 .

Alterar a localidade para 'en-US' dá "26 de julho de 2016" em vez disso. Observe como a formatação mudou, mas as opções de exibição ainda permaneceram as mesmas - um recurso muito útil. Conforme mostrado na seção anterior, a técnica baseada em Intl.DateTimeFormat mais recente funciona de maneira muito semelhante a isso, mas permite que você reutilize um objeto formatador para que você só precise definir as opções uma vez.
Com toLocaleDateString() , é um bom hábito sempre passar as opções de formatação, mesmo que a saída pareça boa em seu computador. Isso pode proteger a interface do usuário de quebrar em localidades inesperadas com nomes de meses muito longos ou parecer estranho por causa de nomes curtos.
Se eu quisesse o mês completo “julho”, tudo o que faço é alterar o parâmetro do mês nas opções para “longo”. JavaScript lida com tudo para mim. Para en-US, agora recebo 26 de julho de 2016.
Nota: Se você quiser que o navegador use automaticamente a localidade do usuário, você pode passar “undefined” como o primeiro parâmetro.
Se você quiser mostrar a versão numérica da data e não quiser se preocupar com MM/DD/AAAA vs. DD/MM/AAAA para diferentes localidades, sugiro a seguinte solução simples:
const today = new Date().toLocaleDateString(undefined, { day: 'numeric', month: 'numeric', year: 'numeric', }); No meu computador, isso gera 7/26/2016 . Se você quiser garantir que o mês e a data tenham dois dígitos, basta alterar as opções:
const today = new Date().toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric', }); Isso 07/26/2016 . Exatamente o que queríamos!
Você também pode usar algumas outras funções relacionadas para localizar a forma como a hora e a data são exibidas:
| Código | Saída | Descrição |
|---|---|---|
| "04:21:38" | Exibir versão localizada de apenas hora |
| "04:21:38" | Exibir hora localizada com base nas opções fornecidas |
| "22/07/2016, 04:21:38" | Exibir data e hora para a localidade do usuário |
| "22/07/2016, 04:21" | Exibir data e hora localizadas com base nas opções fornecidas |
Cálculo de datas e horas relativas
Aqui está um exemplo de adição de 20 dias a uma data JavaScript (ou seja, descobrir a data 20 dias após uma data conhecida):
const myDate = new Date("July 20, 2016 15:00:00"); const nextDayOfMonth = myDate.getDate() + 20; myDate.setDate(nextDayOfMonth); const newDate = myDate.toLocaleString(); O objeto de data original agora representa uma data 20 dias após 20 de julho e newDate contém uma string localizada que representa essa data. No meu navegador, newDate contém “8/9/2016, 3:00:00 PM”.
Para calcular carimbos de hora relativos com uma diferença mais precisa do que dias inteiros, você pode usar Date.getTime() e Date.setTime() para trabalhar com números inteiros que representam o número de milissegundos desde uma determinada época, ou seja, 1º de janeiro de 1970. Para exemplo, se você quiser saber quando são 17 horas depois de agora:
const msSinceEpoch = (new Date()).getTime(); const seventeenHoursLater = new Date(msSinceEpoch + 17 * 60 * 60 * 1000);Comparando datas
Tal como acontece com tudo relacionado a datas, comparar datas tem suas próprias pegadinhas.
Primeiro, precisamos criar objetos de data. Felizmente, <, >, <= e >= todos funcionam. Portanto, comparar 19 de julho de 2014 e 18 de julho de 2014 é tão fácil quanto:
const date1 = new Date("July 19, 2014"); const date2 = new Date("July 28, 2014"); if(date1 > date2) { console.log("First date is more recent"); } else { console.log("Second date is more recent"); }Verificar a igualdade é mais complicado, pois dois objetos de data representando a mesma data ainda são dois objetos de data diferentes e não serão iguais. Comparar strings de datas é uma má ideia porque, por exemplo, “20 de julho de 2014” e “20 de julho de 2014” representam a mesma data, mas têm representações de strings diferentes. O trecho abaixo ilustra o primeiro ponto:
const date1 = new Date("June 10, 2003"); const date2 = new Date(date1); const equalOrNot = date1 == date2 ? "equal" : "not equal"; console.log(equalOrNot); Isso produzirá uma saída not equal .
Este caso particular pode ser corrigido comparando os equivalentes inteiros das datas (seus carimbos de hora) da seguinte forma:
date1.getTime() == date2.getTime()Já vi esse exemplo em muitos lugares, mas não gosto porque você não cria um objeto de data a partir de outro objeto de data normalmente. Então eu sinto que o exemplo é importante apenas do ponto de vista acadêmico. Além disso, isso requer que os dois objetos Date se refiram exatamente ao mesmo segundo, enquanto você pode querer saber apenas se eles se referem ao mesmo dia, hora ou minuto.
Vejamos um exemplo mais prático. Você está tentando comparar se o aniversário que o usuário digitou é o mesmo que a data de sorte que você está recebendo de uma API.
const userEnteredString = "12/20/1989"; // MM/DD/YYYY format const dateStringFromAPI = "1989-12-20T00:00:00Z"; const dateFromUserEnteredString = new Date(userEnteredString) const dateFromAPIString = new Date(dateStringFromAPI); if (dateFromUserEnteredString.getTime() == dateFromAPIString.getTime()) { transferOneMillionDollarsToUserAccount(); } else { doNothing(); }Ambos representavam a mesma data, mas infelizmente seu usuário não receberá o milhão de dólares.
Aqui está o problema: JavaScript sempre assume que o fuso horário é aquele que o navegador fornece, a menos que explicitamente especificado de outra forma.
Isso significa que, para mim, a new Date ("12/20/1989") criará uma data 1989-12-20T00:00:00+5:45 ou 1989-12-19T18:15:00Z que não é a mesma que 1989-12-20T00:00:00Z em termos de carimbo de hora.
Não é possível alterar apenas o fuso horário de um objeto de data existente, portanto, nosso objetivo agora é criar um novo objeto de data, mas com UTC em vez do fuso horário local.
Iremos ignorar o fuso horário do usuário e usar o UTC ao criar o objeto de data. Existem duas maneiras de fazê-lo:
- Crie uma string de data formatada ISO a partir da data de entrada do usuário e use-a para criar um objeto Date. Usando um formato de data ISO válido para criar um objeto Date, deixando a intenção de UTC versus local muito clara.
const userEnteredDate = "12/20/1989"; const parts = userEnteredDate.split("/"); const userEnteredDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const userEnteredDateObj = new Date(userEnteredDateISO + "T00:00:00Z"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateObj.getTime() == dateFromAPI.getTime(); // trueIsso também funciona se você não especificar a hora, pois o padrão será meia-noite (ou seja, 00:00:00Z):
const userEnteredDate = new Date("1989-12-20"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDate.getTime() == dateFromAPI.getTime(); // trueLembre-se: Se o construtor de data receber uma string no formato de data ISO correto de AAAA-MM-DD, ele assumirá o UTC automaticamente.
- JavaScript fornece uma função elegante Date.UTC() que você pode usar para obter o carimbo de hora UTC de uma data. Extraímos os componentes da data e os passamos para a função.
const userEnteredDate = new Date("12/20/1989"); const userEnteredDateTimeStamp = Date.UTC(userEnteredDate.getFullYear(), userEnteredDate.getMonth(), userEnteredDate.getDate(), 0, 0, 0); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateTimeStamp == dateFromAPI.getTime(); // true ...Encontrando a diferença entre duas datas
Um cenário comum que você encontrará é encontrar a diferença entre duas datas.
Discutimos dois casos de uso:
Encontrando o número de dias entre duas datas
Converta ambas as datas para o carimbo de hora UTC, encontre a diferença em milissegundos e encontre os dias equivalentes.
const dateFromAPI = "2016-02-10T00:00:00Z"; const now = new Date(); const datefromAPITimeStamp = (new Date(dateFromAPI)).getTime(); const nowTimeStamp = now.getTime(); const microSecondsDiff = Math.abs(datefromAPITimeStamp - nowTimeStamp); // Math.round is used instead of Math.floor to account for certain DST cases // Number of milliseconds per day = // 24 hrs/day * 60 minutes/hour * 60 seconds/minute * 1000 ms/second const daysDiff = Math.round(microSecondsDiff / (1000 * 60 * 60 * 24)); console.log(daysDiff);Encontrando a idade do usuário a partir da data de nascimento
const birthDateFromAPI = "12/10/1989";Nota: Temos um formato fora do padrão. Leia o documento da API para determinar se isso significa 12 de outubro ou 10 de dezembro. Altere para o formato ISO de acordo.
const parts = birthDateFromAPI.split("/"); const birthDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const birthDate = new Date(birthDateISO); const today = new Date(); let age = today.getFullYear() - birthDate.getFullYear(); if(today.getMonth() < birthDate.getMonth()) { age--; } if(today.getMonth() == birthDate.getMonth() && today.getDate() < birthDate.getDate()) { age--; }Eu sei que existem maneiras mais concisas de escrever esse código, mas eu gosto de escrevê-lo dessa maneira por causa da clareza da lógica.
Sugestões para evitar o inferno da data
Agora que estamos confortáveis com a aritmética de datas, estamos em condições de entender as melhores práticas a serem seguidas e as razões para segui-las.
Obtendo DateTime do usuário
Se você está obtendo a data e a hora do usuário, provavelmente está procurando o DateTime local. Vimos na seção de aritmética de datas que o construtor Date pode aceitar a data de várias maneiras diferentes.
Para remover qualquer confusão, sempre sugiro criar uma data usando o new Date(year, month, day, hours, minutes, seconds, milliseconds) , mesmo que você já tenha a data em um formato analisável válido. Se todos os programadores da sua equipe seguirem essa regra simples, será extremamente fácil manter o código a longo prazo, pois é o mais explícito possível com o construtor Date .
A parte legal é que você pode usar as variações que permitem omitir qualquer um dos quatro últimos parâmetros se forem zero; ou seja, new Date(2012, 10, 12) é o mesmo que new Date(2012, 10, 12, 0, 0, 0, 0) porque os parâmetros não especificados são zero.
Por exemplo, se você estiver usando um seletor de data e hora que fornece a data 2012-10-12 e a hora 12:30, você pode extrair as partes e criar um novo objeto Date da seguinte maneira:
const dateFromPicker = "2012-10-12"; const timeFromPicker = "12:30"; const dateParts = dateFromPicker.split("-"); const timeParts = timeFromPicker.split(":"); const localDate = new Date(dateParts[0], dateParts[1]-1, dateParts[2], timeParts[0], timeParts[1]);Tente evitar criar uma data a partir de uma string, a menos que esteja no formato de data ISO. Use o método Data (ano, mês, data, horas, minutos, segundos, microssegundos).
Obtendo apenas a data
Se você estiver obtendo apenas a data, a data de nascimento de um usuário, por exemplo, é melhor converter o formato para o formato de data ISO válido para eliminar qualquer informação de fuso horário que possa fazer com que a data avance ou retroceda quando convertida para UTC. Por exemplo:
const dateFromPicker = "12/20/2012"; const dateParts = dateFromPicker.split("/"); const ISODate = dateParts[2] + "-" + dateParts[0] + "-" + dateParts[1]; const birthDate = new Date(ISODate).toISOString(); Caso você tenha esquecido, se você criar um objeto Date com a entrada no formato de data ISO válido (YYYY-MM-DD), o padrão será UTC em vez do fuso horário do navegador.
Armazenando a data
Sempre armazene o DateTime em UTC. Sempre envie uma string de data ISO ou um carimbo de data/hora para o back-end.
Gerações de programadores de computador perceberam essa verdade simples após amargas experiências tentando mostrar a hora local correta para o usuário. Armazenar a hora local no back-end é uma má ideia, é melhor deixar o navegador lidar com a conversão para a hora local no front-end.
Além disso, deve ficar claro que você nunca deve enviar uma string DateTime como “20 de julho de 1989 12:10 PM” para o back-end. Mesmo se você enviar o fuso horário também, estará aumentando o esforço para que outros programadores entendam suas intenções e analisem e armazenem a data corretamente.
Use os toISOString() ou toJSON() do objeto Date para converter o DateTime local em UTC.
const dateFromUI = "12-13-2012"; const timeFromUI = "10:20"; const dateParts = dateFromUI.split("-"); const timeParts = timeFromUI.split(":"); const date = new Date(dateParts[2], dateParts[0]-1, dateParts[1], timeParts[0], timeParts[1]); const dateISO = date.toISOString(); $.post("http://example.com/", {date: dateISO}, ...)Exibição da data e hora
- Obtenha o carimbo de data/hora ou a data formatada ISO de uma API REST.
- Crie um objeto
Date. - Use os métodos
toLocaleString()outoLocaleDateString()etoLocaleTimeString()ou uma biblioteca de datas para exibir a hora local.
const dateFromAPI = "2016-01-02T12:30:00Z"; const localDate = new Date(dateFromAPI); const localDateString = localDate.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric', }); const localTimeString = localDate.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', });Quando você deve armazenar a hora local também?
“Às vezes é importante saber o fuso horário em que um evento ocorreu, e a conversão para um único fuso horário elimina irrevogavelmente essa informação.
“Se você está fazendo uma promoção de marketing e quer saber quais clientes fizeram pedidos na hora do almoço, um pedido que parece ter sido feito ao meio-dia GMT não é muito útil quando na verdade foi feito no café da manhã em Nova York.”
Se você se deparar com esse tipo de situação, seria mais sensato economizar o horário local também. Como de costume, gostaríamos de criar a data no formato ISO, mas primeiro temos que encontrar o deslocamento do fuso horário.
A função getTimeZoneOffset() do objeto Date nos informa o número de minutos que, quando adicionados a uma determinada hora local, fornece a hora UTC equivalente. Sugiro convertê-lo para o formato (+-)hh:mm porque torna mais óbvio que é um deslocamento de fuso horário.
const now = new Date(); const tz = now.gettime zoneOffset();Para o meu fuso horário +05:45, recebo -345, este não é apenas o sinal oposto, mas um número como -345 pode ser completamente desconcertante para um desenvolvedor de back-end. Então, convertemos isso para +05:45.
const sign = tz > 0 ? "-" : "+"; const hours = pad(Math.floor(Math.abs(tz)/60)); const minutes = pad(Math.abs(tz)%60); const tzOffset = sign + hours + ":" + minutes;Agora obtemos o restante dos valores e criamos uma string ISO válida que representa o DateTime local.
const localDateTime = now.getFullYear() + "-" + pad(now.getMonth()+1) + "-" + pad(now.getDate()) + "T" + pad(now.getHours()) + ":" + pad(now.getMinutes()) + ":" + pad(now.getSeconds());Se desejar, você pode agrupar as datas UTC e locais em um objeto.
const eventDate = { utc: now.toISOString(), local: localDateTime, tzOffset: tzOffset, } Agora, no back-end, se você quiser descobrir se o evento ocorreu antes do meio-dia, horário local, você pode analisar a data e simplesmente usar a função getHours() .
const localDateString = eventDate.local; const localDate = new Date(localDateString); if(localDate.getHours() < 12) { console.log("Event happened before noon local time"); } Não usamos o tzOffset aqui, mas ainda o armazenamos porque podemos precisar dele no futuro para fins de depuração. Na verdade, você pode enviar apenas o deslocamento do fuso horário e a hora UTC. Mas eu gosto de armazenar a hora local também porque você eventualmente terá que armazenar a data em um banco de dados e ter a hora local armazenada separadamente permite que você consulte diretamente com base em um campo em vez de ter que realizar cálculos para obter a data local.
Às vezes, mesmo com o fuso horário local armazenado, você desejará exibir datas em um fuso horário específico. Por exemplo, os horários dos eventos podem fazer mais sentido no fuso horário do usuário atual se forem virtuais ou no fuso horário em que ocorrerão fisicamente, se não forem. De qualquer forma, vale a pena examinar antes as soluções estabelecidas para formatação com nomes de fuso horário explícitos.
Configuração de servidor e banco de dados
Sempre configure seus servidores e bancos de dados para usar o fuso horário UTC. (Observe que UTC e GMT não são a mesma coisa - GMT, por exemplo, pode implicar uma mudança para BST durante o verão, enquanto UTC nunca será.)
Já vimos o quanto as conversões de fuso horário podem ser dolorosas, especialmente quando não são intencionais. Sempre enviar UTC DateTime e configurar seus servidores para estarem no fuso horário UTC pode facilitar sua vida. Seu código de back-end será muito mais simples e limpo, pois não precisa fazer nenhuma conversão de fuso horário. DateTime data coming in from servers across the world can be compared and sorted effortlessly.
Code in the back end should be able to assume the time zone of the server to be UTC (but should still have a check in place to be sure). A simple configuration check saves having to think about and code for conversions every time new DateTime code is written.
It's Time for Better Date Handling
Date manipulation is a hard problem. The concepts behind the practical examples in this article apply beyond JavaScript, and are just the beginning when it comes to properly handling DateTime data and calculations. Plus, every helper library will come with its own set of nuances—which is even true of the eventual official standard support{target=”_blank”} for these types of operations.
The bottom line is: Use ISO on the back end, and leave the front end to format things properly for the user. Professional programmers will be aware of some of the nuances, and will (all the more decidedly) use well-supported DateTime libraries on both the back end and the front end. Built-in functions on the database side are another story, but hopefully this article gives enough background to make wiser decisions in that context, too.
