date-fns: Uma biblioteca de datas para JavaScript
Tags: ptbr, javascript, webdev, node
Published: 2020-09-28 02:29:35

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:

```javascript
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:

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

Esse método `lastDayOfMonth` é fornecido por [date-fns](https://date-fns.org/), 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](https://momentjs.com/) é 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 `subtract`alteram 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.

{% twitter 1304676118822174721 %}

Tudo isso levou os mantenedores do Moment a [colocar o projeto em modo de manutenção](https://momentjs.com/docs/#/-project-status/) 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](https://momentjs.com/docs/#/-project-status/recommendations/) 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.

```bash
npm install date-fns
```

Ou via Yarn:

```bash
yarn add date-fns
```

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

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

ou:

```javascript
// 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](https://github.com/date-fns/date-fns/issues/1780) . 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](https://www.sitepoint.com/quick-tip-multiple-versions-node-nvm/) .

Em seguida, instale o [Parcel](https://en.parceljs.org/). Este é um empacotador (semelhante ao webpack), que permitirá que você empacote seu JavaScript e o exiba em um navegador.

```bash
npm install -g parcel-bundler
```

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

```bash
mkdir datefns
cd datefns
npm init -y
```

Instale a biblioteca date-fns:

```bash
npm install date-fns
```

Agora crie dois arquivos `index.html`e `index.js`.

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

E:

```javascript
import { lastDayOfMonth } from 'date-fns';

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

Inicie o servidor de desenvolvimento embutido do parcel:

```bash
parcel index.html
```

E navegue até [http: // localhost: 1234](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:

```bash
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"](https://date-fns.org/docs/format) de data-fns .

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

```markup
<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.

```javascript
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:

```javascript
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](https://date-fns.org/docs/format).

## 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:

```markup
<h1>Heute ist <span></span></h1>
```

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

```javascript
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.

```javascript
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:

```javascript
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.

```javascript
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"](https://date-fns.org/v2.16.1/docs/formatDistance) .

Vamos comparar duas datas diferentes.

```javascript
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](https://date-fns.org/docs/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.

```javascript
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](https://date-fns.org/docs/compareDesc) .

```javascript
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](https://date-fns.org/docs/addDays) que conhecemos anteriormente, bem como o [auxiliar eachDayOfInterval](https://date-fns.org/v2.16.1/docs/eachDayOfInterval) que retorna um array de datas dentro do intervalo especificado.

```javascript
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](https://date-fns.org/docs/closestTo). Este snippet de código segue o exemplo anterior:

```javascript
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](https://date-fns.org/docs/closestIndexTo) se você deseja obter o índice do array.

### Validando uma Data

O utilitário final que desejo examinar é o [método isValid](https://date-fns.org/v2.16.1/docs/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:

```javascript
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 2020`que 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](https://date-fns.org/v2.16.1/docs/parse) da data antes de verificar sua validade:

```javascript
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](http://stackoverflow.com/a/15171030) 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).

```javascript
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](http://momentjs.com/timezone/) tem uma biblioteca separada para ele. Existem [planos em andamento para adicionar suporte a fuso horário para date-fns](https://github.com/date-fns/date-fns/issues/180#issuecomment-264932949), mas no momento em que este artigo foi escrito, ainda é um trabalho em andamento.

No entanto, existe um [pacote disponível no npm](https://www.npmjs.com/package/date-fns-tz) (baseado em uma [solicitação de pull não-aceita para date-fns](https://github.com/date-fns/date-fns/pull/707) ) que adiciona suporte de fuso horário para date-fns v2.0.0 usando a [API Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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:

```bash
npm i date-fns-tz
```

E nosso código:

```javascript
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

- [Learn date-fns: A Lightweight JavaScript Date Library](https://www.sitepoint.com/date-fns-javascript-date-library/), escrito originalmente por [James Hibbard](https://twitter.com/jchibbard).