DEV Community

Cover image for date-fns: Uma biblioteca de datas para JavaScript
Eduardo Rabelo
Eduardo Rabelo

Posted on

date-fns: Uma biblioteca de datas para JavaScript

Trabalhar com datas em JavaScript é uma dor. Os métodos de datas nativo costumam ser prolixos e ocasionalmente inconsistentes - algo que também os torna sujeitos a erros. Mas boas notícias estão à mão. Existem várias bibliotecas para lidar com a manipulação de datas e removem essa dor. Essas bibliotecas são para datas em JavaScript, o que jQuery é para a API DOM nativa.

Aqui está um exemplo. Esta é a resposta aceita para uma pergunta do Stack Overflow perguntando como obter o último dia do mês:

var t = new Date();
alert( new Date(t.getFullYear(), t.getMonth() + 1, 0, 23, 59, 59) );

Claro que funciona, mas não é imediatamente óbvio o que os números depois de getMonth representam. Agora compare isso com o consideravelmente mais legível:

const today = new Date();
console.log( lastDayOfMonth(today) );

Esse método lastDayOfMonth é fornecido por date-fns, um conjunto abrangente de funções para manipular datas em JavaScript no navegador e Node.js.

Neste artigo, vou mostrar como começar a usar o date-fns. Depois de ler, você poderá inseri-lo em seus projetos e aproveitar os vários métodos auxiliares para manipular datas com facilidade. Isso tornará o código t.getMonth() + 1, 0, 23, 59, 59 uma coisa do passado.

Então, por que não usar apenas Moment.js?

Moment.js é uma biblioteca fantástica para trabalhar com datas em JavaScript - ela tem muitos recursos excelentes e oferece uma série de utilitários úteis. No entanto, não deixa de ter seus críticos.

Muitas pessoas citam o fato de que os objetos Moment são mutáveis ​​(ou seja, operações semelhantes add ou subtractalteram o objeto Moment original) como sendo confuso para os desenvolvedores e uma fonte de bugs.

Ele também foi criticado por seu grande tamanho. O Moment não funciona bem com algoritmos modernos de "agitação de árvore" (tree shaking) e se você precisar de internacionalização ou suporte de fuso horário, poderá rapidamente se encontrar com um pacote JavaScript bastante grande.

Isso foi tão longe que as ferramentas de desenvolvimento do Chrome agora destacam o fato de que usar o Moment pode levar a um desempenho ruim.

Tudo isso levou os mantenedores do Moment a colocar o projeto em modo de manutenção e a desencorajar o Moment de ser usado em novos projetos no futuro.

Reconhecemos que muitos projetos existentes podem continuar a usar o Moment, mas gostaríamos de desencorajar o Moment de ser usado em novos projetos no futuro. Ao invés disso, gostaríamos de recomendar alternativas que são excelentes opções para uso em aplicações modernas hoje.

Isso torna o date-fns uma das melhores alternativas ao Moment.js que existe.

Instalação

Desde a versão dois da biblioteca, a única maneira de instalar date-fns é como um pacote npm.

npm install date-fns

Ou via Yarn:

yarn add date-fns

Você pode usar date-fns com o sistema de módulo CommonJS e também com módulos ES:

// CommonJS
const { lastDayOfMonth } = require('date-fns');

ou:

// ES Modules
import { lastDayOfMonth } from 'date-fns';

Infelizmente, não há atualmente nenhuma versão CDN de date-fns disponível. Sua remoção e possível reintegração estão sendo discutidas nesta conversa do GitHub . Mas, isso não quer dizer que você não possa usá-lo em um navegador, apenas que você precisa introduzi-lá em uma etapa de empacotamento em seu fluxo de trabalho.

Vamos ver como fazer isso agora.

Como empacotar date-fns para uso em um navegador

Vou assumir que você tem o Node e o npm instalados em sua máquina. Caso contrário, consulte nosso tutorial sobre como instalar o Node .

Em seguida, instale o Parcel. Este é um empacotador (semelhante ao webpack), que permitirá que você empacote seu JavaScript e o exiba em um navegador.

npm install -g parcel-bundler

A seguir, faça um novo projeto com um arquivo package.json.

mkdir datefns
cd datefns
npm init -y

Instale a biblioteca date-fns:

npm install date-fns

Agora crie dois arquivos index.htmle index.js.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>date-fns</title>
  </head>
  <body>
    <script src="index.js"></script>
  </body>
</html>

E:

import { lastDayOfMonth } from 'date-fns';

const today = new Date();
console.log(lastDayOfMonth(today));

Inicie o servidor de desenvolvimento embutido do parcel:

parcel index.html

E navegue até http: // localhost: 1234. Você não verá nada exibido na página, mas se você abrir o console do navegador, você deve ter registrado o último dia do mês atual.

Quando se trata de implantação, você pode executar:

parcel build index.js --experimental-scope-hoisting

Para que o Parcel crie um pacote na pasta dist.

Uso básico de Date-fns

Agora que estamos prontos e funcionando, vamos ver o que o date-fns pode fazer.

Uma das tarefas mais comuns ao trabalhar com datas é a capacidade de formatá-las. Podemos fazer isso com a função "format" de data-fns .

Altere o HTML de nossa página do exemplo acima e mude para:

<body>
  <h1>The date today is <span></span></h1>
  <script src="index.js"></script>
</body>

Em index.js queremos importar a função format, podemos então passar a data de hoje e uma string de formato. Em seguida, queremos enviar o resultado para a página.

import { format } from 'date-fns';

const today = new Date();
const formattedDate = format(today, 'dd.MM.yyyy');

document.querySelector('h1 > span').textContent = formattedDate;

Claro, não estamos limitados a um formato dd.MM.yyyy, vamos tentar algo diferente:

const formattedDate = format(today, 'PPPP');

Isso irá formatar a saída assim: Wednesday, September 16th, 2020. Você pode encontrar uma lista completa de opções de formatação na documentação oficial.

Mudar a Localização

Se você tem um site em vários idiomas, o date-fns simplifica a internacionalização de horários e datas. Vamos cumprimentar nossos usuários alemães:

<h1>Heute ist <span></span></h1>

E no arquivo JavaScript, podemos importar a localidade alemã e passá-la para a função format:

import { format } from 'date-fns';
import { de } from 'date-fns/locale';

const today = new Date();
const formattedDate = format(today, 'PPPP', { locale: de });

document.querySelector('h1 > span').textContent = formattedDate;

A saída será algo ao longo das linhas de: Heute ist Mittwoch, 16. September 2020.

Pode parecer complicado exigir e passar localidades como opções, mas ao contrário do Moment.js que incha sua construção com todas as localidades por padrão, date-fns força os desenvolvedores a exigirem manualmente as localidades como e quando forem necessários.

Você pode ver uma lista de localidades disponíveis olhanda na pasta node_modules/date-fns/locale em seu projeto.

Imutabilidade, Pureza e Simplicidade

Um dos pontos de venda do date-fns é que suas funções são puras e simples de explicar. Isso leva a um código fácil de entender, que é mais fácil de depurar quando as coisas dão errado.

Vou demonstrar isso usando Moment.js como um contra-exemplo. Como mencionado antes, as datas no Moment são mutáveis, o que pode levar a um comportamento inesperado.

const moment = require('moment');
const now = new Date();
const mNow = moment(now);

mNow.add('day', 3);
console.log(mNow.toDate());
mNow.add(3, 'day');
console.log(mNow.toDate());

// 2020-09-19T10:08:36.999Z
// 2020-09-22T10:08:36.999Z

Há algumas coisas a serem observadas aqui. A função add do Moment não é exigente quanto à ordem em que aceita seus argumentos (embora o primeiro método agora emita um aviso de depreciação). Mas o mais confuso é que, se você chamar add várias vezes consecutivas, não obterá o mesmo resultado porque os objetos Moment são mutáveis:

mNow.add(3, 'day'); // add 3 days
mNow.add(3, 'day'); // adds 3 **more** days

Agora compare isso com date-fns que mantém os argumentos em uma ordem e sempre retorna o mesmo resultado, retornando um novo objeto Date para cada chamada.

import { addDays } from 'date-fns';

const today = new Date();
const threeDaysTime = addDays(3, today);
const sixDaysTime = addDays(threeDaysTime, 3);

console.log(today); // Wed Sep 16 2020 12:11:55 GMT+0200
console.log(threeDaysTime); // Sat Sep 19 2020 12:12:58 GMT+0200
console.log(sixDaysTime); // Invalid Date

Observe também como o nome do método é mais expressivo (addDays ao invés de apenas add), mantendo as coisas consistentes e tendo um método para fazer uma coisa e apenas uma coisa.

Comparando datas

Se você olhar a lista de postagens no canal JavaScript do SitePoint, poderá ver que algumas estão listadas como publicadas em uma determinada data, enquanto outras estão listadas como publicadas há X dias. Pode demorar um pouco se você tentar implementar isso em JavaScript puro, mas com date-fns isso é muito fácil - basta usar o método "formatDistance" .

Vamos comparar duas datas diferentes.

import { formatDistance } from 'date-fns';

const startDate = new Date(2020, 8, 16); // (Sep 16 2020)
const endDate = new Date(2020, 11, 25); // (Dec 25 2020)
const distanceInWords = formatDistance(startDate, endDate);

console.log(`It is ${distanceInWords} until Christmas`);
// It is 3 months until Christmas

Observe como, ao trabalhar com JavaScript, os meses são baseados em zero (por exemplo, mês 11 = dezembro), mas os dias contam a partir de um. Isso me confunde uma e outra vez.

Trabalho com coleções de datas

date-fns tem alguns métodos auxiliares muito úteis que você pode usar para manipular coleções de datas de todas as maneiras.

Solicitando uma coleção de datas

O exemplo a seguir usa compareAsc para classificar as datas em ordem crescente. Para fazer isso, ele retorna 1 se a primeira data for posterior à segunda, -1 se a primeira data for anterior à segunda ou 0 se as datas forem iguais.

import { compareAsc } from 'date-fns';

const date1 = new Date('2005-01-01');
const date2 = new Date('2010-01-01');
const date3 = new Date('2015-01-01');
const arr = [date3, date1, date2];
const sortedDates = arr.sort(compareAsc);

// [ 2005-01-01, 2010-01-01, 2015-01-01 ]

Como você pode ver, as datas agora estão em ordem crescente.

O método oposto é compareAsc, veja a documentação em compareDesc .

import { compareDesc } from 'date-fns';
...
const sortedDates = arr.sort(compareDesc);
// [ 2015-01-01, 2010-01-01, 2005-01-01 ]

Gerando os dias entre duas datas

Para gerar os dias entre duas datas, você pode usar o método addDays que conhecemos anteriormente, bem como o auxiliar eachDayOfInterval que retorna um array de datas dentro do intervalo especificado.

import { addDays, eachDayOfInterval } from 'date-fns';

const today = new Date();
const aWeekFromNow = addDays(today, 7);
const thisWeek = eachDayOfInterval(
  { start: today, end: aWeekFromNow },
);

console.log(thisWeek);

/*
[
  Wed Sep 16 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Thu Sep 17 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Fri Sep 18 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Sat Sep 19 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Sun Sep 20 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Mon Sep 21 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Tue Sep 22 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Wed Sep 23 2020 00:00:00 GMT+0200 (Central European Summer Time)
]
*/

Encontrando a Data Mais Próxima

Encontrar a data mais próxima de uma determinada data em um array de datas pode ser feito usando o método closestTo. Este snippet de código segue o exemplo anterior:

import { addDays, eachDayOfInterval, closestTo } from 'date-fns';
...
const christmas = new Date(2020, 11, 25);
const closestToChristmasDate = closestTo(christmas, thisWeek);

console.log(closestToChristmasDate);
// Wed Sep 23 2020 00:00:00 GMT+0200 (Central European Summer Time)

Existe também o método closestIndexTo se você deseja obter o índice do array.

Validando uma Data

O utilitário final que desejo examinar é o método isValid que, como o nome sugere, verifica se uma determinada data é válida.

No entanto, devido à maneira como o JavaScript lida com datas, existem algumas pegadinhas que você deve conhecer:

import { isValid } from 'date-fns';

const invalidDate = new Date('2020, 02, 30');
console.log(isValid(invalidDate));
// true, lol, wut?

Você seria perdoado por pensar que o snippet acima deve resultar false, já que 30 de fevereiro de 2020 é obviamente uma data inválida. Para entender o que está acontecendo, entre new Date('2020, 02, 30')no console do seu navegador. Você verá Sun Mar 01 2020que voltará para você - JavaScript pegou o dia extra desde o final de fevereiro e o transformou em 1º de março (que é, obviamente, uma data válida).

Para contornar isso, podemos fazer o parse da data antes de verificar sua validade:

import { isValid, parse } from 'date-fns';

const validDate = parse('29.02.2020', 'dd.MM.yyyy', new Date());
const invalidDate = parse('30.02.2020', 'dd.MM.yyyy', new Date());

console.log(validDate);
// Sat Feb 29 2020 00:00:00 GMT+0100 (Central European Standard Time)

console.log(invalidDate);
// Invalid Date

console.log(isValid(validDate));
// true

console.log(isValid(invalidDate));
// false

Isso pode ser facilmente extraído em um pequeno método auxiliar, por exemplo, para validar a entrada do usuário em formulários.

Fusos Horários

Uma desvantagem de date-fns é que atualmente ele não tem nenhuma função auxiliar de fuso horário como o Moment.js, em vez disso, date-fns retorna o fuso horário local no qual o código está sendo executado.

Esta resposta do Stack Overflow fornece algumas informações básicas sobre como os objetos Date nativos não armazenam dados de "fuso horário real". Nesse tópico, você notará que eles mencionam um método de definir fusos horários nativamente em JavaScript. Esta não é uma solução abrangente, mas funciona para muitos cenários que exigem apenas conversão de visualização (de UTC ou hora local para um fuso horário específico).

new Date().toLocaleString("en-US", {timeZone: "America/New_York"});

Os fusos horários são, na verdade, um problema complicado de resolver, por isso o MomentJS tem uma biblioteca separada para ele. Existem planos em andamento para adicionar suporte a fuso horário para date-fns, mas no momento em que este artigo foi escrito, ainda é um trabalho em andamento.

No entanto, existe um pacote disponível no npm (baseado em uma solicitação de pull não-aceita para date-fns ) que adiciona suporte de fuso horário para date-fns v2.0.0 usando a API Intl. Com 140 mil downloads semanais, parecia popular, mas no momento em que este artigo foi escrito, ele não era atualizado há vários meses.

Dito isso, veja como você pode usá-lo:

npm i date-fns-tz

E nosso código:

import { format, utcToZonedTime } from 'date-fns-tz';

const today = new Date(); // Wed Sep 16 2020 13:25:16
const timeZone = 'Australia/Brisbane'; // Vamos ver que horas são Lá Embaixo
const timeInBrisbane = utcToZonedTime(today, timeZone);

console.log(`
  Time in Munich: ${format(today, 'yyyy-MM-dd HH:mm:ss')}
  Time in Brisbane: ${format(timeInBrisbane, 'yyyy-MM-dd HH:mm:ss')}
`);

// Time in Munich: 2020-09-16 13:26:48
// Time in Brisbane: 2020-09-16 21:26:48

Conclusão

date-fns é uma ótima pequena biblioteca que coloca um monte de métodos auxiliares para trabalhar com datas e horas em JavaScript ao seu alcance. Ele está em desenvolvimento ativo e agora que o Moment.js foi colocado em modo de manutenção, ele é um ótimo substituto para o Moment.js.

Espero que este artigo tenha lhe dado compreensão e inspiração suficiente para dar uma olhada e começar a usá-lo em seus próprios projetos.

Créditos

Top comments (0)