date-fns: Uma biblioteca de datas para JavaScript

Image for post
Image for post

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?

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

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

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

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

<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

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

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

Solicitando uma coleção de datas

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

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

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

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, o que??

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

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

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

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store