ISO8601DateFormatter é uma classe Foundation no iOS e macOS projetada para formatar e analisar datas no padrão internacional ISO 8601. De acordo com Apple Developer Documentation, 2024, ISO8601DateFormatter lida automaticamente com formatos com milissegundos, fusos horários e frações de segundo sem precisar definir DateFormat manualmente. Ao contrário do DateFormatter, esta classe não depende de Locale e TimeZone — funciona estritamente de acordo com a especificação ISO 8601, tornando-a ideal para troca de datas entre servidor e cliente. A classe está disponível desde iOS 10 e macOS 10.12.
Principais Pontos
ISO8601DateFormatter é uma subclasse especializada de Formatter no Foundation que implementa a conversão bidirecional entre Date e strings no formato ISO 8601. O padrão ISO 8601 (International Standard for the Representation of Dates and Times) define um formato internacional para troca de datas e horas: 2024-07-21T14:30:00+00:00. Ao contrário do DateFormatter, esta classe não requer especificação de dateFormat e determina automaticamente a estrutura da string com base nas opções fornecidas.
As principais vantagens do ISO8601DateFormatter sobre o DateFormatter: ausência de dependência de localidade (a análise funciona igualmente em qualquer dispositivo), suporte integrado para frações de segundo (com qualquer número de casas decimais) e deteção automática de formato com base nas opções passadas. A classe também lida corretamente com o sufixo Z (designação UTC), fusos horários no formato +HH:mm e precisão reduzida (apenas data sem hora).
De acordo com a Especificação ISO (ISO 8601-1:2019), o padrão suporta quatro níveis de precisão: ano (2024), ano-mês (2024-07), data completa (2024-07-21) e data-hora com fuso horário (2024-07-21T14:30:00+00:00). O ISO8601DateFormatter cobre todos esses níveis através de uma combinação de opções de formato, libertando o desenvolvedor da construção manual de strings dateFormat.
O princípio de funcionamento do ISO8601DateFormatter é baseado numa combinação de opções de bits (formatOptions), cada uma incluindo um componente específico de data ou hora na saída. Por exemplo, a opção .withFullDate inclui ano, mês e dia; .withTime inclui horas, minutos e segundos. Combinando opções, o desenvolvedor obtém o nível de precisão desejado sem escrever uma string dateFormat.
Internamente, o ISO8601DateFormatter utiliza a biblioteca ICU para análise, mas com regras fixas ISO 8601. Isto significa que ignora as configurações de Locale e TimeZone do dispositivo — o resultado é sempre previsível. Para definir o fuso horário, utiliza-se a propriedade timeZone, cujo valor padrão é UTC. Se timeZone for definido como nil, é utilizada a hora local do dispositivo.
| Opção | Descrição | Exemplo de saída |
|---|---|---|
| .withFullDate | Ano, mês, dia | 2024-07-21 |
| .withTime | Horas, minutos, segundos | 14:30:00 |
| .withMilliseconds | Frações de segundo (até 3 dígitos) | .123 |
| .withFractionalSeconds | Frações de segundo (qualquer precisão) | .123456 |
| .withTimeZone | Fuso horário | +03:00 |
| .withColonSeparatorInTimeZone | Separador de dois pontos no fuso horário | +03:00 (vs +0300) |
| .withInternetDateTime | Formato completo (data + hora + tz) | 2024-07-21T14:30:00+00:00 |
Combinação de opções: .withInternetDateTime é equivalente a combinar .withFullDate, .withTime e .withTimeZone. Para analisar strings com milissegundos, adicione .withFractionalSeconds. É importante lembrar que .withMilliseconds limita as frações de segundo a três dígitos, enquanto .withFractionalSeconds suporta qualquer precisão — de um a nove dígitos após o ponto decimal.
As opções de formato do ISO8601DateFormatter dividem-se em três grupos: componentes de data (withFullDate, withYear, withMonth, withDay, withWeekOfYear), componentes de hora (withTime, withHours, withMinutes, withSeconds) e configurações adicionais (withMilliseconds, withFractionalSeconds, withTimeZone, withColonSeparatorInTimeZone, withDashSeparatorInDate, withFullTime). Combinando-os, pode obter praticamente qualquer subformato ISO 8601.
Nuance importante: .withFractionalSeconds e .withMilliseconds são mutuamente exclusivos — se ambos forem definidos, .withFractionalSeconds prevalece. Para analisar milissegundos de dados do servidor, recomenda-se .withFractionalSeconds, pois muitos servidores enviam frações de segundo com três, seis ou nove dígitos, e .withFractionalSeconds lida com qualquer comprimento.
import Foundation
// Configure ISO8601DateFormatter
let formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone(secondsFromGMT: 0)
// Different format option combinations
formatter.formatOptions = [.withFullDate]
let dateOnly = formatter.string(from: Date())
print("Date: \(dateOnly)")
formatter.formatOptions = [.withFullDate, .withTime]
let dateTime = formatter.string(from: Date())
print("DateTime: \(dateTime)")
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let full = formatter.string(from: Date())
print("Full: \(full)")
// Parse string with milliseconds
let serverString = "2024-07-21T14:30:00.123456+03:00"
if let parsed = formatter.date(from: serverString) {
print("Parsed: \(parsed)")
}
Uso básico do ISO8601DateFormatter resume-se a criar uma instância, configurar timeZone (recomenda-se UTC para dados do servidor) e formatOptions, após o que pode chamar string(from:) para formatar e date(from:) para analisar. Ao contrário do DateFormatter, não precisa se preocupar com Locale — a classe ignora as configurações regionais.
import Foundation
let formatter = ISO8601DateFormatter()
// Parse different ISO 8601 formats
let strings: [String] = [
"2024-07-21T14:30:00Z",
"2024-07-21T14:30:00+03:00",
"2024-07-21T14:30:00.123Z",
"2024-07-21"
]
for str in strings {
if let autoParsed = formatter.date(from: str) {
print("Parsed '\(str)': \(autoParsed)")
} else {
// Use withFullDate for date-only strings
formatter.formatOptions = [.withFullDate]
if let fallback = formatter.date(from: str) {
print("Fallback parsed '\(str)': \(fallback)")
}
formatter.formatOptions = [.withInternetDateTime]
}
}
// Serialize to RFC 3339 (GitHub API)
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let rfc3339 = formatter.string(from: Date())
print("RFC 3339: \(rfc3339)")
Analisar datas com frações de segundo de comprimento variável é uma característica de muitas APIs modernas. Um servidor pode enviar 2024-07-21T14:30:00.123Z (3 dígitos) ou 2024-07-21T14:30:00.123456Z (6 dígitos). O ISO8601DateFormatter com a opção .withFractionalSeconds lidará corretamente com ambos, enquanto o DateFormatter com dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" apenas lidará com milissegundos de três dígitos.
import Foundation
let variantFormatter = ISO8601DateFormatter()
variantFormatter.formatOptions = [
.withInternetDateTime,
.withFractionalSeconds
]
// Different fractional second precision
let variants: [String] = [
"2024-07-21T14:30:00.1Z",
"2024-07-21T14:30:00.12Z",
"2024-07-21T14:30:00.123Z",
"2024-07-21T14:30:00.123456Z",
"2024-07-21T14:30:00.123456789Z"
]
for variant in variants {
if let parsed = variantFormatter.date(from: variant) {
print("OK: \(variant) -> \(parsed)")
} else {
print("FAIL: \(variant)")
}
}
// Use withMilliseconds (3 digits only)
variantFormatter.formatOptions = [
.withInternetDateTime,
.withMilliseconds
]
let milliParsed = variantFormatter.string(from: Date())
print("With milliseconds: \(milliParsed)")
Teste de análise de todas as variantes: o código mostrado demonstra que o ISO8601DateFormatter com .withFractionalSeconds lida com sucesso com frações de segundo de qualquer comprimento, de 1 a 9 dígitos. Isto é importante para compatibilidade com diferentes plataformas de servidor: .NET gera frequentemente 7 dígitos (ticks de 100 nanossegundos), Python — 6, Java — 3 ou 9 dependendo da versão.
DateFormatter também pode analisar ISO 8601, mas requer configuração manual de dateFormat, locale e timeZone. O principal problema é que o DateFormatter depende de Locale, e se não definir en_US_POSIX, a análise pode falhar para utilizadores de regiões com formatos de data não padrão. O ISO8601DateFormatter resolve este problema ao nível da arquitetura: não utiliza Locale.
| Parâmetro | ISO8601DateFormatter | DateFormatter |
|---|---|---|
| Configuração de Locale | Não requerida (ignora) | en_US_POSIX obrigatório |
| DateFormat | Automático (através de opções) | String de formato manual |
| Frações de segundo | Qualquer precisão (.withFractionalSeconds) | SSS fixo |
| Sufixo Z | Lida corretamente | Através de dateFormat |
| Desempenho | Maior (especializado) | Menor (geral) |
| Padrão | Apenas ISO 8601 | Qualquer formato |
| Versão iOS | iOS 10+ | iOS 2+ |
Quando usar DateFormatter: se precisar de formatar uma data num formato não ISO 8601 (por exemplo, "21 de julho de 2024" para a UI) ou se precisar de suportar iOS 9 e versões anteriores. Para todas as tarefas de troca de datas entre servidor e cliente, use ISO8601DateFormatter — é mais seguro, mais eficiente e requer menos código. DateFormatter para ISO 8601 é uma fonte de potenciais erros relacionados com a localidade e configurações regionais.
Migração de DateFormatter para ISO8601DateFormatter: substitua a criação de DateFormatter + configuração de dateFormat + locale + timeZone pela criação de ISO8601DateFormatter + configuração de formatOptions + timeZone. A análise da string permanece inalterada através de date(from:). Para compatibilidade reversa, pode usar #available(iOS 10, *) com um fallback para DateFormatter.
Configuração esquecida de formatOptions faz com que o formatador use o valor padrão — .withInternetDateTime. Se o servidor enviar uma data sem hora (2024-07-21), a análise retornará nil. Verifique sempre se formatOptions cobre todos os formatos possíveis que podem vir do servidor. Para APIs com formatos variáveis, use tentativas de fallback com diferentes combinações de opções.
Confusão entre withMilliseconds e withFractionalSeconds é um erro comum ao analisar datas com frações de segundo. withMilliseconds espera exatamente 3 dígitos após o ponto decimal. Se o servidor enviar 6 dígitos (microssegundos), a análise com withMilliseconds falhará. Use .withFractionalSeconds para compatibilidade com qualquer número de dígitos. .withFractionalSeconds está disponível desde iOS 13; para versões mais antigas, use DateFormatter com dateFormat.
Ignorar o fuso horário é outro problema comum. Se o servidor enviar uma data com fuso horário (+03:00) e o formatador estiver configurado para UTC, a análise não falhará, mas o resultado estará em UTC. Os desenvolvedores frequentemente esperam que Date preserve o fuso horário, mas Date é um momento absoluto no tempo — não armazena informações de fuso horário. Para exibição correta, guarde o fuso horário separadamente ou use ISO8601DateFormatter com o timeZone adequado.
De acordo com o Apple Forum (2024), cerca de 20% das perguntas sobre ISO8601DateFormatter estão relacionadas ao formato onde os segundos são opcionais. O padrão ISO 8601 permite um formato sem segundos: 2024-07-21T14:30+03:00. O ISO8601DateFormatter com .withInternetDateTime não suporta este formato — para analisá-lo será necessário DateFormatter com dateFormat = "yyyy-MM-dd'T'HH:mmZ". Esta limitação é importante considerar ao trabalhar com APIs que utilizam o formato de hora abreviado.
Perguntas Frequentes
ISO8601DateFormatter é uma classe especializada Foundation para formatar e analisar datas no formato ISO 8601, disponível desde iOS 10. Lida automaticamente com formatos padrão sem definir dateFormat manualmente.
ISO8601DateFormatter não depende de Locale, usa opções em vez de dateFormat e lida corretamente com frações de segundo de qualquer comprimento. DateFormatter é universal, mas requer configuração manual e é propenso a erros relacionados com configurações regionais.
Use a opção .withFractionalSeconds — suporta de 1 a 9 dígitos após o ponto decimal. Não use .withMilliseconds se a precisão puder variar. .withFractionalSeconds está disponível desde iOS 13.
UTC por padrão. Para alterar, defina a propriedade timeZone. Se timeZone = nil, é usada a hora local do dispositivo. Ao analisar uma string com fuso horário explícito no formato +HH:MM, o formatador considera-o automaticamente.
Porque formatOptions por defeito é .withInternetDateTime, que espera data + hora + fuso horário. Para analisar apenas a data, defina formatOptions = [.withFullDate]. Para suportar ambos os formatos, use fallback com diferentes opções.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também