Aguarde...

28 de outubro de 2023

A data selecionada deve estar dentro do último 10 anos

A data selecionada deve estar dentro do último 10 anos

O que vem à mente se eu fornecer o seguinte requisito de validação de data do formulário?

O usuário deve selecionar uma data, que é um campo obrigatório.

Bastante fácil. Posso usar uma type="date"entrada e garantir que ela tenha um requiredatributo.

A data selecionada deve ser dos últimos dez anos.

Respire fundo… vou te contar o que me vem à cabeça… encontros são difíceis!

Programando datas e fusos horários… argh!

Era uma noite de semana, 21h30, horário local (vida dos novos pais), e quando mudei de assunto para minha próxima tarefa de recurso, percebi algo… o código de validação de data que funcionava no início do dia foi quebrado repentinamente .

O que aconteceu? Eu não tinha mudado nada. Eu estava sonhando? Foi privação de sono? Eu poderia jurar que o código estava funcionando perfeitamente bem apenas algumas horas atrás!

Eu estava tão confuso.

Acontece que minha lógica estava errada por um dia, permitindo-me escolher a data de amanhã quando não deveria. Mas só descobri porque mudei minha programação mais tarde do que o normal. Durante o horário normal de trabalho diurno, o código funcionou muito bem! Mas depois das 17h, horário local, não!

Resumindo, datas e fusos horários são difíceis. Um dos recursos mais abrangentes que encontrei foi a transcrição de uma palestra de Zach Holman, que começa dizendo: “Programação de horário, datas, fusos horários, eventos recorrentes, segundos bissextos… tudo é bastante terrível”. O melhor conselho sobre como lidar com fusos horários vem de Tom Scott, que disse: “ Você nunca deve lidar com fusos horários se puder evitar ”.

Acho que você entendeu.

Validação de intervalo de datas

Uma entrada de “data de compra” onde a data selecionada deve estar dentro dos últimos dez anos.

Neste artigo, quero examinar as lições aprendidas relacionadas a essa restrição de validação. Passando por mim, eu teria caído na toca do coelho sobre como escrever uma lógica de validação de data personalizada, eventualmente ficando confuso sobre fusos horários e, em seguida, ultrapassando minha estimativa de meio dia com apenas uma solução parcialmente funcional para oferecer.

Então, além de mim, isso é para você.

Os insights e artigos mais recentes do Cloud Four, direto na sua caixa de entrada

Lição 1: Use recursos de validação integrados

Para validar se um valor de data está nos últimos dez anos, comecei no caminho certo adicionando atributos type="date"requiredmax. Estava faltando o minatributo e não planejava usar APIs de validação do navegador.

Present me agora sabe que tudo que preciso para garantir que a data selecionada esteja dentro de um intervalo especificado está  integrado  à plataforma web:

  • maxvalor do atributo deve ser a data de hoje
  • minvalor do atributo deveria ser há dez anos a partir de hoje
  • Use o método da API de validação de restriçãocheckValidity para validar se a data atende aos minmaxrequisitos

Por exemplo, supondo que hoje seja 5 de setembro de 2023 , inputseria mais ou menos assim:

<!-- Use HTML min/max validation constraint attributes --> 
<input
  id="purchase-date" 
  name="purchaseDate" 
  type="date" 
  min="2013-09-05" 
  max="2023-09-05" 
  required
>Linguagem de código: HTML, XML ( xml )

Um bônus: ao adicionar valores minmax, o calendário integrado do navegador não permitirá que os usuários selecionem uma data antes ou depois das restrições minmax. Por exemplo, no Chrome, todas as datas posteriores à data de hoje têm cores mais claras e são restritas:

O navegador restringe a seleção de datas futuras se o maxvalor do atributo estiver definido para a data de hoje.

Para validar se a seleção de “data de compra” está dentro do intervalo especificado, posso usar a API de validação de restrição:

// Use the Constraint Validation API to validate the input
const purchaseDate = document.querySelector('#purchase-date');
purchaseDate.checkValidity(); // true or falseLinguagem de código: JavaScript ( javascript )

Confira Validação de formulário progressivamente aprimorada, Parte 2: Camadas em JavaScript para obter mais informações sobre como usar a API de validação de restrição.

A mente de além de mim está explodida. 🤯

Lição para mim:  não complique as coisas mais do que o necessário; use os recursos de validação integrados do navegador.

Lição 2: Tenha cuidado ao alternar acidentalmente entre a hora local e o UTC

Como não queremos que o usuário selecione um valor futuro para a data de compra, o maxvalor do atributo deve ser a data de hoje e formatado adequadamente (YYYY-MM-DD).

Minha tentativa inicial de gerar o maxvalor foi a seguinte:

  • Use new Date()para obter a data de hoje
  • Use Date.prototype.toISOString para obter uma string de data no formato baseado em ISO 8601
    • por exemplo,'2023-03-21T04:15:47.000Z'
  • Divida a string de data para obter o YYYY-MM-DDformato adequado
    • por exemplo,'2023-03-21'
// Assuming today is September 5, 2023…
const today = new Date();
const todayFormatted = today.toISOString().split('T')[0];

console.log(todayFormatted); // "2023-09-05"Linguagem de código: JavaScript ( javascript )

O formato parece ótimo. Pronto para ir, certo? Não exatamente.

Há uma pegadinha: a string de data retornada por Date.prototype.toISOString é sempre UTC (Tempo Universal Coordenado) .

Você obterá resultados inesperados usando uma data UTC em um fuso horário diferente do UTC. Meu conto acima é um excelente exemplo; Eu acidentalmente descobri um bug em meu código onde eu poderia selecionar a data de amanhã se fosse depois das 17h, meu horário local. Ops.

Vamos dar uma olhada mais de perto para entender o porquê

Continuando com a suposição de que hoje é 5 de setembro de 2023, 17h30, meu horário local , new Date()retorna minha string de data do fuso horário local , conforme esperado:

const today = new Date();

console.log(today);
// Tue Sep 05 2023 17:30:17 GMT-0700 (Pacific Daylight Time)Linguagem de código: JavaScript ( javascript )

Se eu chamar o Date.prototype.toISOStringmétodo on todaya data de amanhã será retornada…não era isso que eu esperava:

const todayAsISOString = today.toISOString();

console.log(todayAsISOString);
// "2023-09-06T00:30:17.479Z" Linguagem de código: JavaScript ( javascript )

Lembre-se, Date.prototype.toISOStringretorna uma string de data UTC. Meu fuso horário local está sete horas atrás do UTC. Portanto, se você pegar meu horário local de 17h30 e adicionar sete horas a ele, você terá o dia seguinte , 12h30, em UTC.

Então, e agora?

Poderíamos ser complicados e compensar a string de data UTC pela diferença entre o fuso horário local do usuário e o UTC usando Date.prototype.getTimezoneOffset. Eu segui esse caminho inicialmente. Funciona, mas podemos fazer algo mais simples.

Em vez de alternar entre o fuso horário local do usuário e o UTC com truques de deslocamento de fuso horário, vamos permanecer no fuso horário do usuário:

  • Use Date.prototype.getFullYear para obter o ano local de 4 dígitos
  • Use Date.prototype.getMonth para obter o valor do mês local
    • Retorna um número inteiro entre 0 e 11
  • Use Date.prototype.getDate para obter o dia do mês
    • Retorna um número inteiro entre 1 e 31
  • Use String.prototype.padStart para garantir que o mês e o dia sejam sempre valores de dois dígitos
  • Combine-os usando uma string de modelo literal para criar o YYYY-MM-DDformato esperado
/**
 * Generates a date string from a Date object in the format: YYYY-MM-DD
 * @param {Date} date The date object to format
 * @returns {string} A date string formatted as follow: YYYY-MM-DD
 */
export const getISOFormattedDate = (date) => {
  // Get 4-digit year.
  const year = date.getFullYear();
  // Use padding to ensure 2 digits.
  // Note: January is 0, February is 1, and so on.
  const month = (date.getMonth() + 1).toString().padStart(2, '0');
  // Use padding to ensure 2 digits.
  const day = date.getDate().toString().padStart(2, '0');
  // Return the date formatted as YYYY-MM-DD.
  return `${year}-${month}-${day}`;
};Linguagem de código: JavaScript ( javascript )

Supondo que você esteja usando uma estrutura como Astro (ou Svelte, Vue ou algo semelhante), o maxatributo para a entrada “data de compra” poderia então ser atualizado da seguinte forma:

<input
  id="purchase-date" 
  name="purchaseDate" 
  type="date" 
  min="2013-09-05" 
  max={getISOFormattedDate(new Date())}
  required
>
Linguagem de código: HTML, XML ( xml )

Se estiver usando JavaScript vanilla, você pode fazer algo como o seguinte:

const purchaseDate = document.querySelector('#purchase-date');
purchaseDate.setAttribute('max', getISOFormattedDate(new Date()));
Linguagem de código: JavaScript ( javascript )

Lição para mim: preste atenção aos Datevalores de retorno do método API (eles são UTC?) e teste depois da meia-noite UTC para garantir que a lógica da data não esteja errada em um dia.

Você não precisa ficar acordado até tarde como eu para testar. Descubra quando a meia-noite UTC é relativa ao seu fuso horário e, em seguida, avance/atrase o relógio do computador conforme necessário para testar.

Lição 3: Como calcular anos atrás

Vamos terminar de adicionar o último bit de lógica para o campo “data de compra”, que gera o minvalor do atributo. Precisamos gerar um valor de data dez anos antes de hoje para o minatributo. Não existe uma maneira integrada de obter uma quantidade específica de anos atrás a partir de hoje usando JavaScript.

Minha pesquisa online continuou me mostrando algo semelhante ao seguinte:

const tenYearsAgoToday = new Date();
tenYearsAgoToday.setFullYear(tenYearsAgoToday.getFullYear() - 10);

console.log(tenYearsAgoToday); 
// Thu Sep 05 2013 14:17:38 GMT-0700 (Pacific Daylight Time)Linguagem de código: JavaScript ( javascript )

Muito arrumado! Eu não tinha visto o padrão setFullYeargetFullYearantes.

Date.prototype.getFullYearDate.prototype.setFullYear são métodos específicos da hora local . Como só precisamos nos preocupar com o fuso horário local do usuário, não há necessidade de recorrer aos métodos da variante UTC.

Acabei envolvendo essa lógica também em uma função utilitária (também disponível na fonte demo):

/**
 * Returns a Date object representing the number of years ago from today.
 * @param {number} years - The number of years ago from today.
 * @returns {Date} - A Date object.
 */
const yearsAgoFromToday = (years) => {
  const date = new Date();
  date.setFullYear(date.getFullYear() - years);
  return date;
};Linguagem de código: JavaScript ( javascript )

Usando esta função utilitária (combinada com a getISOFormattedDatefunção acima para formatação), a entrada “data de compra” pode ser atualizada da seguinte forma:

<input
  id="purchase-date" 
  name="purchaseDate" 
  type="date" 
  min={getISOFormattedDate(yearsAgoFromToday(10))}
  max={getISOFormattedDate(new Date())}
  required
>
Linguagem de código: HTML, XML ( xml )

Semelhante a antes, se estiver usando JavaScript vanilla, você pode fazer o seguinte:

const purchaseDate = document.querySelector('#purchaseDate');
purchaseDate.setAttribute(
  'min', 
  getISOFormattedDate(yearsAgoFromToday(10))
);
Linguagem de código: JavaScript ( javascript )

Lição para mim: calcular a data como um valor passado não precisa ser complexo. Use os métodos de horário local e mantenha a simplicidade.

Empacotando

Ao validar um campo de entrada de data que possui um requisito de intervalo de datas:

  • Use recursos de validação integrados ao navegador ( minmaxe a API de validação de restrição)
  • Considere os fusos horários ao escrever a lógica de data. Você precisa prestar contas deles?
  • Preste atenção aos valores de retorno dos Datemétodos; alguns são UTC e outros não
  • Teste em relação à meia-noite UTC para garantir que a lógica da data não esteja errada em um dia

Embora a programação para datas e fusos horários ainda seja difícil e confusa, adicionar validação de formulário para um intervalo de datas não precisa ser assustador.

Postado em BlogTags:
Escreva um comentário