Datas e horários no JavaScript com date-fns (adeus moment)

Se existe algo no JavaScript que não tem como utilizar sem uma biblioteca é a API de datas e horários que vem disponível nativamente, por isso, desde muito tempo nós utilizamos libs como o MomentJS para lidar com esse trabalho.

Libs como essa nos facilitam a lidar com vários aspectos chatos em relação às datas, como formatação, transformação, timezones, locales, etc.

Ou seja, hoje se quisermos pegar uma data qualquer, convertê-la para um timezone diferente, aumentar 2h e ainda formatar o valor final de uma maneira legal para o usuário final, temos que usar uma biblioteca.

Hoje, existem duas bibliotecas que são extremamente boas no que fazem quando o assunto é datas, o MomentJS e o date-fns. Nesse post vou falar diretamente sobre a segunda opção, mas antes precisamos entender o real motivo do date-fns estar fazendo tanto sucesso na comunidade JS.

Moment vs date-fns

O Moment é uma lib fantástica, esteve comigo durante muito tempo, nos dias bons e ruins, éramos inseparáveis, mas toda história tem um fim (feels bad). Não existem muitos critérios para a mudança, mas existem dois muito importantes, tamanho e API.

Com a API do Moment que segue um conceito de chain (corrente) nós não conseguimos utilizar parte de sua API apenas, ou seja, necessitamos carregar todo o bundle da biblioteca mesmo se queremos apenas formatar um simples campo de data.

Além disso, o date-fns fornece uma API mais simples com nomes bem declarativos e com apenas uma forma de realizar algo, enquanto que Moment oferecia no mínimo umas 8 formas diferentes de você fazer a mesma ação.

Com isso, o date-fns foi crescendo e chamando a atenção da comunidade JS, principalmente front-end que leva velocidade e tamanho das libs muito em conta na hora da utilização.

Tweet do Dan Abramov, core team do React, sobre o date-fns.

Entrando em ação

Agora que já entendemos os diferenciais do date-fns para o Moment vamos entender como utiliza-lo na prática realizando algumas operações simples do dia-a-dia.

Nesse post vou estar utilizando a versão 2.0 do date-fns que (pelo menos enquanto estou escrevendo) ainda está em alpha, mas já está sendo utilizada pela comunidade pois sua API não vem mudando muito há um bom tempo.

Comparações de datas

Pense que você está recebendo uma data em formato de texto e precisa ver se essa data já passou, com o date-fns conseguimos fazer isso de forma simples:

import { parseISO, isAfter } from 'date-fns';

const date = '2018-04-01 18:00:00';
const parsedDate = parseISO(date);

const past = isAfter(parsedDate, new Date()); // true

Veja que utilizamos o parseISO para formatar uma string para data, nesse momento a variável parsedDate é um objeto Date nativo do Javascript, e isso é um dos pontos mais fantásticos, temos uma biblioteca extremamente poderosa que por baixo dos panos utiliza tipos primitivos do JavaScript.

Formatação de datas

A formatação de datas dessa biblioteca me deixou boquiaberto, eu gostava do que o moment oferecia, mas cara, formatar até antes de cristo, depois de cristo? Sério, o date-fns foi longe demais, literalmente.

Fora que essa lib tem uns helpers extremamente úteis que nos dá a distância entre duas datas de uma forma muito humana, perfeita para exibir ao usuário final.

Outra coisa extremamente interessante é que temos acesso a uma gama de locales para facilmente traduzir o nome dos meses e formatações de distância entre duas datas.

import { 
  parseISO, 
  format, 
  formatRelative, 
  formatDistance,
} from 'date-fns';

import pt from 'date-fns/locales/pt';

const firstDate = parseISO('2018-04-01 16:00:00');
const secondDate = parseISO('2018-04-02 16:00:00');

const formattedDate = format(
  firstDate, 
  "'Dia' dd 'de' MMMM', às ' HH:mm'h'"
);
 
// Dia 01 de Abril às 16:00h

const distance = formatDistance(
  firstDate,
  secondDate
);

// 24 horas

const relative = formatRelative(
  firstDate,
  secondDate
);

// Ontem às 16h

Veja que no format tudo que incluirmos entre '' é escapado e não é utilizado na hora do preenchimento dos valores da data.

O método formatRelative na minha opinião é um dos mais eficientes no front-end. Eu particularmente utilizo muito em minhas aplicações pois o valor retornado é extremamente agradável e acessível pra quem está utilizando.

Lidando com timezones

Lidar com datas e horários já é um pouco chato, agora lidar com timezones é de fazer qualquer programador pensar duas vezes se está feliz nesse mundo da programação.

A nossa sorte é que o date-fns também lida de forma muito simples com timezones com um pacote auxiliar chamado date-fns-tz que nos permite ler valores de data com uma timezone específica e formatá-lo em outra.

import { parseISO } form 'date-fns'; 
import { zonedTimeToUtc } from 'date-fns-tz';

const parsedDate = parseISO('2018-04-01 16:00:00');
const znDate = zonedTimeToUtc(parsedDate, 'America/Sao_Paulo');

// 2018-04-01 19:00:00

Nesse exemplo podemos ver que usando o método zonedTimeToUtc estamos basicamente criando um novo objeto de data a partir de uma timezone de entrada, ou seja, estamos indicando que a entrada da data 2018-04-01 16:00:00 foi feita através da timezone de São Paulo, que é UTC-3 e então o date-fns a converteu para UTC, ou seja, com três horas a mais.

import { parseISO, isBefore } from 'date-fns';

const compareDate = parseISO('2018-04-01 18:00:00');

isBefore(parsedDate, compareDate); // true
isBefore(znDate, compareDate) // false

Através do exemplo acima podemos enxergar a importância de tratar a timezone da data no back-end antes de comparar algum valor, precisamos sempre garantir que sempre que estivermos comparando datas ambas estão na mesma timezone, preferencialmente UTC.

Agora podemos manipular a data e a retornar ao usuário a formando novamente:

import { addHours } from 'date-fns';
import { format } from 'date-fns-tz';

const addedDate = addHours(znDate, 2);

format(addedDate, 'dd/MM/YYYY HH:mm', {
  timeZone: 'America/Sao_Paulo',
});

// 01/04/2018 18:00:00

Veja que agora usamos o método format da biblioteca date-fns-tz e não da date-fns, para ter suporte à propriedade timeZone.

Concluindo

Infelizmente agora já deu minha data e hora (há) e preciso ir embora, mas se esse post te ensinou algo de novo e se você, assim como eu, curtiu bastante essa biblioteca date-fns não esquece de deixar um comentário aí pra eu saber.

Vou ficando por aqui, valeu!