Appearance
Datas e formatos
Regra central
Datas usadas em cálculos e em APIs devem estar em formato ISO sem dependência de fuso horário. O formato localizado dd/mm/yyyy é reservado à interface gráfica (UI) e a documentos PDF; nunca aparece em payloads de API, em chaves de objetos serializados ou em estruturas de dados persistidas.
| Contexto | Formato | Exemplo |
|---|---|---|
| Data de calendário em payloads de API e cálculos | yyyy-mm-dd | 2026-05-19 |
| Competência mensal (salário, índice) | yyyy-mm | 2021-01 |
| Timestamp completo (criação/alteração, auditoria) | ISO 8601 com fuso | 2026-05-19T14:30:00.000Z |
| UI e PDFs | dd/mm/yyyy | 19/05/2026 |
A motivação é dupla:
- Imunidade a fuso horário — uma data civil (nascimento, DIB, dataApuracao) não tem horário associado. Representá-la como string ISO sem hora evita as armadilhas clássicas do
Date.toISOString()(que pode deslocar o dia em uma hora a depender do fuso da máquina). - Contratos previsíveis — clientes gerados a partir de OpenAPI validam o
patterndo schema. Misturar formatos localizado e ISO no contrato gera erros de validação no consumidor (foi exatamente esse o bug que motivou esta política).
Fuso horário
Data de calendário e competência são intervalos do calendário: uma competência é um mês, qualquer que seja o fuso. O resultado de um cálculo não depende do fuso de quem usa nem do fuso em que o servidor roda.
- Data de hoje. Quando um cálculo parte da data de hoje (fim da evolução, data de atualização padrão, competência corrente), vale o dia do calendário de quem usa, nas calculadoras, e o dia do calendário de Brasília, nas APIs. No código,
obterDataAtualNormalizadaé o único "hoje";new Date()fica para instantes. - Forma em memória. Enquanto a data de calendário é um
Date, ela é semprenew Date(ano, mes, dia): meia-noite local, ou 01:00 nos dias em que a meia-noite não existiu por causa do horário de verão.ehDataCivilCanonicareconhece essa forma, e somas e comparações (adicionarNaData,mesmaData) a preservam. - JSON. Todo JSON que leva data de calendário a escreve como
yyyy-mm-dde a lê de volta como data local.JSON.stringifyde umDateproduz um instante UTC e, em fusos a leste de UTC, desloca o dia: em dados de cálculo, usejsonDateOnlySerializer(ouclonarComoDataCivil) na ida ejsonReviverna volta. - Instantes. Carimbos de criação, alteração e geração continuam instantes ISO 8601. O "gerado em" dos demonstrativos em PDF sai no horário de Brasília, com a indicação.
- Verificação. Nos motores de cálculo, uma regra de lint recusa o "agora" cru, datas criadas a partir de texto ou número, a leitura do calendário UTC e
toISOString; e as suítes dos motores rodam em vários fusos — os brasileiros, o UTC e fusos a leste de UTC.
ts
import { obterDataAtualNormalizada } from '@fc/utils/datas/obterDataAtualNormalizada'
import { jsonDateOnlySerializer } from '@fc/utils/etc/jsonDateOnly'
const hoje = obterDataAtualNormalizada() // o dia de hoje, sem hora
const corpo = jsonDateOnlySerializer({ der: hoje }) // '{"der":"2026-09-18"}'Funções recomendadas
Para serializadores de API e backend
ts
import { Competencia } from '@fc/comum/entidades/Competencia'
import { formatarCompetenciaIso } from '@fc/utils/datas/formatarCompetenciaIso'
import { formatarDataIsoLocal } from '@fc/utils/datas/formatarDataIsoLocal'
// Date → "yyyy-mm-dd"
const dib = formatarDataIsoLocal(new Date(2020, 9, 5)) // "2020-10-05"
// Date → "yyyy-mm"
const competencia = formatarCompetenciaIso(new Date(2021, 0, 1)) // "2021-01"
// Equivalente via Competencia (já entidade do domínio)
const comp = new Competencia('01/2021')
comp.valor // "2021-01" — chave canônica dos mapas, dos arquivos .calc e das APIs
comp.comoTexto // sinônimo de valor
comp.comoTextoLocalizadoCurto // "01/2021" — só para exibiçãoO construtor de Competencia aceita mm/yyyy, dd/mm/yyyy, yyyy-mm e yyyy-mm-dd; valor (e comoTexto) é sempre yyyy-mm, a competência mensal. As formas localizadas (comoTextoLocalizado, comoTextoLocalizadoCurto) existem para a interface e para ler chaves gravadas antes da ISO, nunca para produzir chave ou payload.
Para timestamps
ts
const criadoEm = new Date().toISOString() // "2026-05-19T14:30:00.000Z"Para UI (Vue/Quasar, PDF)
Aqui — e apenas aqui — toLocaleDateString('pt-BR') é apropriado, porque o destinatário é um humano lendo a tela.
ts
const visivel = data.toLocaleDateString('pt-BR') // "19/05/2026"Veto explícito
É proibido usar toLocaleDateString('pt-BR') em código que produza payloads de API: handlers de servidor, serializadores, leitores e conversores que alimentam respostas. A linha de corte é simples: se a string vai para JSON de resposta, deve sair em ISO.
A regra ESLint fc/sem-data-localizada-em-api bloqueia esses usos. Quando dispara, troque por formatarDataIsoLocal ou formatarCompetenciaIso.
O veto vale também fora das APIs sempre que a data localizada funcionar como chave técnica: chave de Map/Set, índice de objeto, identificador de linha, chave de cache ou operando de comparação. A mesma regra, em modo somenteChavesTecnicas, cobre todo o monorepo nesses usos e deixa livre o que é apresentação — rótulo, mensagem, nota. Exemplo: o conjunto de feriados que adicionarNaData recebe para contar dias úteis é de datas civis aaaa-mm-dd, e as planilhas de encadeamento identificam cada linha pela data ISO do início do período.
Chaves de objetos serializados também seguem ISO. Um Record<string, X> cuja chave representa uma data ou competência deve ter chaves no formato yyyy-mm-dd ou yyyy-mm, não dd/mm/yyyy — inclusive nos arquivos .calc das calculadoras e nas tabelas de índices; arquivos gravados antes dessa regra são convertidos na leitura.
Camada de segurança
A função normalizarDatasContratoApi (em @fc/comum/api), aplicada por responderContratoApi no envelope de resposta, converte qualquer string dd/mm/yyyy → yyyy-mm-dd e mm/yyyy → yyyy-mm antes da serialização final. Funciona como rede de proteção: mesmo que algum serializador escape do padrão, a saída ainda fica em ISO.
Não confie nessa rede como contrato: corrija o serializador. A rede serve para tolerar transições, não para perpetuar inconsistências.
Campos que representam instantes completos usam uma allow-list compartilhada (chavesInstantesTemporais) para preservar hora e fuso. Hoje a lista cobre: timestamp, criadoEm, atualizadoEm, excluidoEm, expiraEm, iniciadoEm, finalizadoEm, registradoEm, desativadoEm, ultimoAcesso, ultimoLoginAnterior e localidadeResolvida. Ao adicionar um novo timestamp de contrato, inclua o nome nessa lista e cubra o caso em teste; datas civis continuam fora dela.
Entrada
Os endpoints ativos (/rgps/api/v1/**) declaram entrada em ISO. Internamente, converterStringEmData aceita tanto yyyy-mm-dd quanto dd/mm/yyyy por compatibilidade durante a transição — mas o contrato oficial é ISO. Consumidores novos devem enviar ISO.
Banco de dados
A mesma regra vale para colunas persistidas: datas civis em ISO yyyy-mm-dd, competências em ISO yyyy-mm, e timestamps no tipo nativo com fuso horário. Toda persistência passa pelo backoffice (Persistência de dados); o catálogo literal de tabelas, tipos de coluna e regras de migração fica na documentação interna do projeto.
Não introduzir colunas novas com datas em formato localizado. Dados antigos nesse formato devem ser convertidos antes de serem expostos por qualquer API.