3 pontos por GN⁺ 2023-09-01 | 1 comentários | Compartilhar no WhatsApp
  • Ao tratar de notação de data e hora, o RFC 3339 é mais próximo de um subconjunto restrito e fácil de usar na web e na internet, enquanto a ISO 8601-1:2019 inclui um conjunto de formatos muito mais amplo
  • O escopo da comparação é limitado à ISO 8601-1:2019, e expressões adicionais da ISO 8601-2:2019, como estações, conjuntos, qualificadores de incerteza e aritmética de datas, ainda não estão refletidas na tabela
  • Os dois padrões tratam em comum dos formatos básicos de data e hora amplamente usados, como 2026-06-26, 14:08:00Z, 2026-06-26T14:08:00Z e offsets +00:00
  • A ISO 8601 abrange século, década, dia ordinal, data por semana, hora abreviada, frações com vírgula, duração (P1Y) e intervalo (2026-06-26/P1Y), mas o RFC 3339 exclui a maior parte disso na tabela
  • Na notação Date-Time, diferenças como o separador T, maiúsculas e minúsculas e offsets como -00:00 acabam definindo a compatibilidade real entre parsers

Escopo da comparação e premissas

  • A tabela de formatos não é uma lista completa
  • O padrão considerado é ISO 8601-1:2019
    • Há diferenças importantes em relação a edições anteriores e rascunhos
  • A ISO 8601-2:2019 inclui expressões adicionais, mas isso ainda não está refletido nesta página
    • Grupos de subano, por exemplo estações
    • Unidades de agrupamento
    • Conjuntos
    • Qualificadores de incerteza
    • Aritmética de datas
  • O RFC 3339 propõe que padrões derivados possam substituir T por outro caractere, mas os exemplos fornecem apenas o caractere de espaço
  • Cada padrão define formatos para finalidades específicas, e formatos fora disso não são recomendados

A ISO 8601 é mais ampla na notação de datas

  • Tanto o RFC 3339 quanto a ISO 8601 aceitam datas ano-mês-dia como 2026-06-26
  • A ISO 8601 trata de representações de data mais variadas do que o RFC 3339
    • Século: 20
    • Década: 202
    • Ano: 2026
    • Ano-mês: 2026-06
    • Dia ordinal: 2026-177
    • Data por semana: 2026-W26, 2026-W26-5
    • Formato básico: 20260626, 2026177, 2026W26, 2026W265
  • Na tabela, o RFC 3339 não permite esses formatos de data exclusivos da ISO

Diferenças na notação de hora

  • Os dois padrões aceitam em comum horários com segundos e offset de fuso como 14:08:00Z, 14:08:00+00:00 e 14:08:00.372+00:00
  • O RFC 3339 não diferencia maiúsculas de minúsculas, então T e Z podem ser escritos como t e z
    • Edições anteriores da ISO 8601 também não diferenciavam maiúsculas de minúsculas
  • A ISO 8601 permite adicionar uma parte fracionária ao menor valor de tempo
    • A tabela mostra principalmente exemplos com uma casa decimal, mas o padrão permite precisão arbitrária
    • Tanto vírgula quanto ponto são aceitos como separador decimal, e podem ser usados de forma intercambiável em todos os formatos
  • A ISO 8601-1:2019 permite omitir T em representações de hora isoladas quando não houver ambiguidade
  • A ISO 8601 aceita representações de hora abreviadas, básicas e com fração por vírgula como 14, 14:08, 14:08:00, 140800, T14:08:00 e 14:08:00,372
  • O RFC 3339 aceita o offset -00:00 em casos como 14:08:00-00:00, mas na tabela a ISO 8601 não aceita isso

T e separadores em Date-Time

  • Tanto o RFC 3339 quanto a ISO 8601 aceitam Date-Time como 2026-06-26T14:08:00Z e 2026-06-26T14:08:00+00:00
  • Na representação Date-Time da ISO 8601, T é sempre obrigatório
    • Em edições anteriores, também era permitido omitir T em Date-Time
    • Mesmo em edições anteriores, não era permitido inserir caracteres substitutos como espaço ou sublinhado
  • Na tabela, o RFC 3339 aceita as seguintes variações de Date-Time
    • t e z minúsculos: 2026-06-26t14:08:00z
    • Separador por espaço: 2026-06-26 14:08:00Z
    • Separador por sublinhado: 2026-06-26_14:08:00Z
    • Offset -00:00: 2026-06-26T14:08:00-00:00
  • A ISO 8601 trata de Date-Time abreviados e Date-Time baseados em dia ordinal e data por semana, como 2026-06-26T14, 2026-06-26T14:08, 2026-06-26T14:08:00, 2026-177T14:08 e 2026-W26-5T14:08

Duração e intervalo são centrados na ISO 8601

  • Na tabela, formatos de duração (Periods) estão marcados apenas para a ISO 8601
    • Ex.: P1Y, P1M, P1W, P1D
    • Com hora incluída: PT1H, PT1M, PT1S
    • Combinações: P1Y1M1DT1H1M1S
    • Frações: P1.5Y, P1,5W, PT1.5S
  • Formatos de intervalo (Ranges) também estão marcados apenas para a ISO 8601
    • Data e duração: 2026-06-26/P1Y
    • Data e data: 2026-06-26/2026-06-26
    • Duração e data: P1Y/2026-06-26
    • Date-Time e duração: 2026-06-26T14:08/P1DT1H
    • Intervalos repetidos: R/2026-06-26/P1Y, R10/2026-06-26/P1Y

Chaves de formato e ferramenta de teste

  • A tabela de formatos usa chaves de formato como %Y, %M, %D, %h, %m, %s
    • %Y: Year
    • %M: Month
    • %D: Day
    • %V: Week Year
    • %W: Week
    • %w: Week Day
    • %O: Ordinal Day
    • %h: Hour
    • %m: Minute
    • %s: Second
    • %u: Microsecond
    • %n: Nanosecond
    • %Z: hora do fuso com + ou -
    • %z: minuto do fuso
  • O verificador de formato apenas confirma se o formato de entrada corresponde a um formato da tabela
    • Ele não verifica todos os formatos possíveis
  • ISO 8601 as a Service é um serviço em beta para testes
    • Atualmente suporta apenas Date, Time e DateTime
    • Period e Range não são suportados
  • O código-fonte está disponível no GitHub

1 comentários

 
GN⁺ 2023-09-01
Opiniões do Hacker News
  • Acho estranho não haver uma forma de especificar uma data/hora futura com base em um fuso horário específico. Por exemplo, eu posso querer marcar uma reunião para 1º de julho de 2030, às 18h, no horário local de Londres, e ela deve continuar sendo “18h em Londres”, independentemente de como as regras de fuso horário do Reino Unido mudem nesse meio-tempo
    Atualmente, o Reino Unido usa, aproximadamente, Z+00:00 de novembro a março e horário de verão Z+01:00 de abril a outubro[0], mas antes de 2030 poderia adotar o Horário da Europa Central[1], tentar novamente o British Double Summer Time[2] ou abolir o horário de verão. Assim, a mesma “18h” pode mudar bastante em relação a um epoch específico
    Quero colocar em um evento de calendário “18h no horário de Londres naquele momento”, mas não há uma forma padrão e interoperável de representar 2030-07-01 18:00:00 Europe/London
    [0] https://en.wikipedia.org/wiki/British_Summer_Time
    [1] https://en.wikipedia.org/wiki/Central_European_Time
    [2] https://en.wikipedia.org/wiki/British_Summer_Time#Periods_of...

    • Há um rascunho de documento para esse tipo de formato, chamado IXDTF (Internet Extended Date/Time Format)[0]. É possível anexar o fuso horário como um nome tz entre colchetes depois de uma string RFC 3339 e, para expressar a hora local, também é preciso escrever junto uma estimativa do offset UTC
      Por exemplo, 2030-07-01 18:00:00 Europe/London vira 2030-07-01T18:00:00+01:00[Europe/London]. Se as regras do Reino Unido mudarem antes disso, o timestamp se torna “inconsistente”, e a aplicação decide como tratá-lo. Porém, se você colocar ! antes do nome do fuso horário dentro dos colchetes, o problema deve ser detectado em vez de simplesmente seguir cegamente o offset UTC
      Esse formato estendido de timestamp também é usado na biblioteca Temporal proposta para JavaScript[1], e a função de parsing ZonedDateTime.from()[2] permite controlar, com a opção offset, qual lado deve ter prioridade em um timestamp inconsistente. Também há suporte para omitir o offset UTC e escrever apenas o fuso horário, mas ela alerta que o intervalo de uma hora repetido durante a transição do horário de verão fica ambíguo
      [0] https://www.ietf.org/archive/id/draft-ietf-sedate-datetime-e...
      [1] https://tc39.es/proposal-temporal/docs/strings.html#iana-tim...
      [2] https://tc39.es/proposal-temporal/docs/ambiguity.html#ambigu...
    • Na verdade, parece que é preciso uma informação mais específica do que o fuso horário. Por exemplo, digamos que eu queira me encontrar em 1º de julho de 2030, às 18h, não em Londres, mas em Glasgow, na Escócia; hoje, Glasgow está no fuso horário Europe/London
      Mas não é impossível imaginar que, nesse meio-tempo, a Escócia realize novamente um referendo de independência e passe a adotar o Horário da Europa Central, ou crie um Scottish Standard Time
    • A armadilha desse tipo de representação é que surgem timestamps ambíguos ou impossíveis. 2023-11-05 01:30:00 America/New_York corresponde a um de dois instantes diferentes
      Em calendários, “a mesma hora no relógio de parede” costuma ser o significado desejado, então isso é razoável, mas há dificuldades de UI para lidar com horários estranhos, e talvez se queira incluir na sintaxe um modo de resolver ambiguidades. Felizmente, essas transições geralmente acontecem no meio da noite, mas já vi isso ocorrer em trabalho real
      Se você convidar alguém de um fuso sem horário de verão, o horário no calendário dessa pessoa vai oscilar, e às vezes colegas internacionais precisam conviver com isso
    • No mundo dos calendários, o iCal já oferece suporte a isso. Uma data-hora sem fuso horário significa apenas a hora local[0]
      [0]: https://www.rfc-editor.org/rfc/rfc5545#section-3.3.5
    • As informações necessárias são três: data, local e hora local. Talvez o que se queira de fato não seja um fuso horário. É preciso pensar no que fazer se algum lugar que não seja Londres passar para outro fuso horário
      Formatos comuns de data-hora foram criados para representar um instante específico, mas, neste caso, esse instante específico ainda não existe. É comum que especificações de data/hora tenham uma estrutura não trivial, como uma reunião na última sexta-feira de cada mês, uma reunião mensal que começa em 31 de janeiro ou dois dias antes do fim do trimestre
      Criar um padrão que cubra todos os casos que as pessoas conseguem imaginar provavelmente ficaria complexo rapidamente. Se uma simples data, hora ou instante não bastar, parece que a única saída é criar uma estrutura separada que contenha todos os elementos necessários
  • Como a especificação ISO não é disponibilizada gratuitamente, em geral é melhor seguir a RFC, e muitas implementações open source também se baseiam em rascunhos, então é difícil dizer que ela seja totalmente amigável ao open source. É um grande ônus também para desenvolvedores open source
    Se você estiver criando algo que lida com datas futuras, quase sempre vai querer armazenar hora de relógio de parede + localização. Infelizmente, não há um padrão para isso. Na Europa e nos EUA os fusos horários são bastante estáveis, então talvez isso não seja tão perceptível, mas em muitas regiões os fusos mudam com frequência, de modo que armazenar o offset não é estável
    5 de junho de 2026, 13:30 no relógio de parede, Paris é o que a maioria das pessoas quer dizer, e pode ser UTC+2 ou UTC+1 dependendo do que a UE decidir fazer com o horário de verão. Se for uma API que lida com instantes no passado, basta usar um timestamp POSIX em segundos, milissegundos, microssegundos ou nanossegundos

    • iCalendar é um padrão RFC para isso[1]. Porém, quando o horário muda por causa do horário de verão, alguns instantes têm duas representações, e alguns instantes não podem ser representados nesse formato
      O iCal também não leva em conta o problema de uma localização ser reatribuída a outro fuso horário. Além disso, um arquivo iCal correto inclui todos os dados dos fusos horários referenciados, o que é trabalhoso quando você só quer emitir uma única data-hora
      [1] https://icalendar.org/iCalendar-RFC-5545/3-3-5-date-time.htm...
    • Há um rascunho de padrão chamado IXDTF que estende a RFC 3339 colocando o nome do fuso horário IANA entre colchetes: 2026-06-05T13:30+0200[Europe/Paris]
      https://www.ietf.org/archive/id/draft-ietf-sedate-datetime-e...
  • Uma parte frequentemente ignorada nos padrões é a representação de durações
    Veja a seção 5.5.4.2, “Representation of time-interval by duration only”, na página 21 de http://xml.coverpages.org/ISO-FDIS-8601.pdf. Seria bom permitir que parsers JSON de linguagens estáticas definissem um campo como duração e o serializassem em um formato válido
    Um exemplo de proposta para Crystal está aqui: https://github.com/crystal-lang/crystal/issues/11942
    Por exemplo, 15 dias, 5 horas e 20 segundos vira P15DT5H0M20S, e 7 semanas vira P7W

    • Em vez disso, também dá para consultar a definição de duration no ABNF do Apêndice A da RFC 3339
    • Não entendo por que alguém iria querer deixar dados que podem ser estruturados em formato de string
      Por exemplo, se usar algo como "duration": { "days": 15, "hours": 5, "seconds": 20 }, o parser JSON não precisa entender o significado dos dados; isso fica a cargo do validador de entrada. De todo modo, qualquer que seja a forma como o JSON represente esses dados, será necessária uma etapa de conversão que pode falhar
  • É “engraçado” que a RFC 3339 e a ISO 8601 incluam muitos formatos de data-hora redundantes com propósitos sobrepostos, mas nenhuma das duas inclua 2023-09-01 15:30:59, que é o formato mais usado em todos os sistemas e é óbvio demais
    Além disso, ambos os padrões são muito pouco claros sobre como representar datas antes da era comum e datas posteriores a 9999-12-31 ou anteriores a -9999-01-01, e bibliotecas comuns em geral não lidam com isso de forma alguma. Mesmo quando lidam, o comportamento de 00-01-01 é praticamente indefinido
    O calendário gregoriano é estranho porque o ano depois de 1 BC é 1 CE, e quase todo software, exceto software astronômico especializado, não lida corretamente com datas fora do intervalo de tempo Unix do futuro próximo. Mesmo algo que bastaria armazenar como string, como os anos de nascimento e morte do imperador Augusto, poderia ser definido claramente pelo padrão

    • A ISO 8601 permite esse formato se houver acordo mútuo. “Acordo mútuo” soa grandioso, mas basta acrescentar uma restrição simples como ISO 8601 com a possibilidade de trocar o T por um espaço. A RFC 3339 faz algo parecido de uma forma muito mais verbosa
      Também é difícil ter certeza de que 2023-09-01 15:30:59 seja o formato de data-hora mais usado. A língua mais usada é o chinês, e separadores próprios como 2023年9月1日 são comuns
      A ISO 8601 permite anos anteriores a 1582 ou posteriores a 9999 se houver acordo mútuo. Se não couberem em quatro dígitos, é preciso prefixar um único caractere de sinal. Essas datas geralmente não são suportadas porque há pouca coisa significativa que se possa fazer com elas, mas já vi bastante bibliotecas fazerem o parsing, especialmente implementações independentes de C
      00-01-01 é definido como 1º de janeiro do ano 1 antes da era comum. A ISO 8601 deixa claro que a numeração dos anos segue o calendário gregoriano proléptico (proleptic Gregorian calendar), portanto é extrapolada até o infinito negativo
    • O formato separado por espaço é muito mais legível. Depois de seguir o padrão por quase 20 anos, recentemente comecei a ignorar ambos em favor do formato melhor, separado por espaço
      Entendo por que era necessário um caractere que não fosse espaço, mas pelo menos poderiam ter usado sublinhado ou ponto. E também não entendo por que, sendo uma string, limitaram o campo do ano a quatro dígitos, sacrificando a universalidade do formato
  • Anos com 6 dígitos parecem uma solução para um problema que, na prática, não vai acontecer, só para parecer voltado ao futuro. Não há como a tecnologia atual ou as normas sociais durarem 8000 anos

    • Computadores não são usados apenas para coisas relacionadas ao presente. Por exemplo, imagine executar cálculos climáticos de prazo muito longo; talvez dê para simplesmente ignorar se der erro por causa da data, mas não seria melhor se não desse?
    • Seja 5 ou 7 dígitos, se for uma quantidade de dígitos com a qual os dois lados possam concordar antes de começar a comunicação, dá para usar. A parte 2 do padrão inclui até um exemplo de ano com 10 dígitos
    • Anos com 6 dígitos são uma solução para um problema que já existe. Basta pensar em um geólogo simulando a deriva continental
  • A explicação de que a ISO 8601 usa U+2010 HYPHEN e U+2212 MINUS, e que em conjuntos de caracteres que não têm esses caracteres deve-se usar U+2D HYPHEN-MINUS, está errada
    A ISO 8601 real especifica que, se o conjunto de caracteres de destino for baseado em ISO/IEC 646, em ambos os casos deve-se usar o caractere hífen-menos. Isso certamente inclui Unicode. Há uma pequena ambiguidade, mas em Unicode a interpretação é clara, e parece ser uma forma indireta de garantir compatibilidade com outros conjuntos de caracteres baseados no 646 ao especificar o mapeamento canônico do 646

    • O parágrafo relacionado está na ISO 8601-1:2019 §3.2.1
      O conteúdo diz que “todos os caracteres usados em representações de data e hora pertencem ao repertório ISO/IEC 646, exceto ‘hyphen’, ‘minus’ e ‘plus-minus’. Em ambientes que usam um repertório de caracteres baseado em ISO/IEC 646, tanto ‘hyphen’ quanto ‘minus’ devem ser mapeados para ‘hyphen-minus’”
      Como Unicode é baseado em ISO 8859, e ISO 8859 é baseado em ISO 646, parece correto entender que a intenção, no conjunto de caracteres Unicode, é usar U+2D hyphen-minus
  • No Windows, o fato de dois-pontos ser um caractere especial frequentemente incomoda, porque não há uma forma compatível com RFC 3339 de colocar data e hora em nomes de arquivo
    Seria bom se fosse possível continuar em conformidade com a ISO 8601 usando hífens na data, mas omitindo os dois-pontos. Por exemplo, 20230831T1510-0500 é compatível e pode ser usado em nomes de arquivo, mas 2023-08-31T1510-0500 e variações semelhantes não são. Além disso, a função Get-Date do PowerShell não entende o primeiro timestamp, sem hífens e sem dois-pontos

    • Remover os dois-pontos é seguro e não torna ambíguas as datas e date-times da RFC 3339. É sempre possível restaurar os dois-pontos sem perda
    • Eu não sabia que o Windows tinha esse problema. O MacOS também tem outro problema relacionado a dois-pontos em nomes de arquivo. Se dois dos três sistemas operacionais mais usados têm esse problema, dá para dizer que faltou bastante reflexão no design
  • É uma visualização muito boa deste tema
    Ao separar a parte de data e a de hora, prefiro espaço ou sublinhado em vez de T, mas, para evitar problemas com processadores que só lidam com ISO 8601 e por consistência, geralmente continuo usando T

    • Prefiro T. Afinal, não deve haver casos em que essa string seja acidentalmente dividida com base no T
  • Tenho duas dúvidas. Primeiro, qual é a justificativa para anos de 6 dígitos? Nenhum sistema projetado hoje vai existir no ano 100.000, certo?
    Segundo, a ISO 8601 é amplamente difundida, mas a RFC 3339 também é muito usada e adotada em sistemas reais?

    • Vendo quanto tempo leva para nos livrarmos completamente de tecnologias antigas, eu não ficaria surpreso se, no ano 100.000, ainda estivéssemos emulando x86 em computadores quânticos
    • Quando uma biblioteca diz que é ISO 8601, em 90% dos casos ela não implementa de fato as partes mais obscuras da ISO 8601
    • O pacote oficial time de Golang e o Chrono de Rust têm ferramentas embutidas para lidar com RFC 3339, mas não para RFC 8601
      Pelo que lembro, a RFC 8601 tinha problemas de ambiguidade que a RFC 3339 não tem, e sei que o Python também teve problemas com conversão de ida e volta de datas por causa disso
    • Ferramentas científicas podem querer representar datas no futuro distante
    • Y10k (:
  • Não há um formato em que o fuso horário seja especificado com quatro dígitos sem dois-pontos, mas date +%z retorna ±NNNN

    • Felizmente existe %:z. Relacionado a isso, também dá para ensinar o date a imprimir assim por padrão: https://gist.github.com/d081dad407432d53172e30d0d35c39db
      $ date
      2023-08-31T11:15:00-07:00
    • ±NNNN é válido sem dois-pontos quando usado como parte do “formato básico”. Ou seja, o formato inteiro não deve conter hífens nem dois-pontos
      Portanto, os dois exemplos abaixo são equivalentes e ambos válidos:
      2023-09-01T09:40:01+08:00
      20230901T094001+0800