DateFormatter é uma classe Foundation projetada para conversão bidirecional entre objetos Date e suas representações em string. A classe leva em conta a localidade, o fuso horário e o calendário do usuário, garantindo a exibição correta de datas em qualquer região do mundo. De acordo com a Documentação para Desenvolvedores Apple (2025), o DateFormatter suporta quatro estilos predefinidos de data e hora, bem como formatos totalmente personalizados através de uma string de modelo. Sem o DateFormatter é impossível exibir corretamente uma data para o usuário em uma aplicação internacionalizada.
Principais conclusões
DateFormatter é uma classe do framework Foundation que implementa a conversão bidirecional entre Date e string. Apareceu pela primeira vez no OpenStep como NSDateFormatter e desde então permaneceu a principal ferramenta para formatação de datas em todas as plataformas Apple. A classe herda de Formatter e fornece uma API conveniente para exibição localizada de datas.
O DateFormatter funciona com base em padrões Unicode LDML — os mesmos usados no ICU (International Components for Unicode). O padrão é definido através da propriedade dateFormat, onde os símbolos y, M, d, H, m, s correspondem a ano, mês, dia, horas, minutos, segundos. A repetição de um símbolo determina o formato: "y" — ano de dois dígitos, "yyyy" — ano de quatro dígitos.
Criar um DateFormatter é uma operação cara, pois durante a inicialização os dados de localidade e calendário são carregados. A Apple recomenda criar um formatador uma vez para cada tipo de formatação e reutilizá-lo. No SwiftUI e UIKit, os formatadores são frequentemente armazenados em cache em propriedades estáticas ou criados lentamente no primeiro acesso.
DateFormatter é usado em muitos componentes do sistema iOS. O UIDatePicker usa DateFormatter internamente para exibir datas no modo countDownTimer. Um TextField com um formatador pode validar automaticamente as datas inseridas pelo usuário. O Core Data suporta atributos do tipo Date, mas sua representação em string é sempre tratada através do DateFormatter.
Segurança em threads — DateFormatter não é seguro para threads. Modificar as propriedades do formatador de diferentes threads leva a comportamento indefinido. Para uso multi-thread, crie instâncias separadas do formatador para cada thread ou use sincronização através de NSLock ou uma fila serial.
dateStyle e timeStyle são as formas mais simples de configurar a exibição de datas. Cada estilo tem quatro variantes: .short, .medium, .long, .full. A combinação de dateStyle e timeStyle permite configurar independentemente o formato de data e hora, e a propriedade .none desativa a parte correspondente.
Para a localidade dos EUA, .short formata a data como "7/21/26", e para a russa como "21.07.2026". O estilo .long para a localidade russa exibe "21 de julho de 2026", e .full — "terça-feira, 21 de julho de 2026" com o dia da semana. Todos os quatro estilos se adaptam automaticamente aos padrões regionais, incluindo a ordem dos componentes e separadores.
SFDateFormatter no iOS 15+ fornece uma abordagem alternativa através do RelativeDateFormatter e DateIntervalFormatter. O RelativeDateFormatter exibe "hoje", "ontem", "em 3 dias" para contexto imediato. O DateIntervalFormatter exibe intervalos de datas: "21–25 de julho de 2026" — para reservas e planejamento.
| Estilo | Exemplo (ru_RU) | Exemplo (en_US) |
|---|---|---|
| .short | 21.07.2026 | 7/21/26 |
| .medium | 21 jul 2026 | Jul 21, 2026 |
| .long | 21 de julho de 2026 | July 21, 2026 |
| .full | terça-feira, 21 de julho de 2026 | Tuesday, July 21, 2026 |
Ao combinar estilos, o DateFormatter seleciona automaticamente o separador: para .short.date + .short.time o resultado pode ser "21.07.2026, 14:30". Para .full.date + .full.time — "terça-feira, 21 de julho de 2026, 14:30:00 MSK". O separador é gerenciado pela localidade, não pelo desenvolvedor — isso garante a conformidade com as expectativas regionais do usuário.
dateFormat permite definir um padrão de formatação arbitrário usando símbolos de especificação Unicode LDML. Isso dá controle total sobre a exibição: você pode mostrar apenas o ano e mês, ou o dia da semana sem a data, ou a hora sem segundos. O formato personalizado é indispensável para requisitos de design específicos.
Símbolos principais — yyyy (ano: 2026), MM (mês: 07), dd (dia: 21), HH (horas: 14), mm (minutos: 30), ss (segundos: 00). Para o nome completo do mês use MMMM (julho), para abreviado — MMM (jul). Dia da semana — EEEE (terça-feira), abreviado — E (ter).
Ao usar dateFormat é importante definir a localidade do formatador. Se a localidade não for definida, o formatador usa a localidade do sistema, o que pode ser indesejável para um formato fixo em uma API. A Apple recomenda definir locale = Locale(identifier: "en_US_POSIX") para um formato fixo entre regiões, especialmente ao analisar datas de respostas do servidor.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.dateFormat = "d MMMM yyyy"
let customString = formatter.string(from: Date())
// "21 July 2026"
// Analisando uma string personalizada
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = formatter.date(from: "2026-07-21 14:30:00")!
Um erro no dateFormat é uma das causas comuns de falhas do aplicativo. Se o formato não corresponder à string, o método date(from:) retorna nil. Use guard let ou ?? para desembrulho seguro de opcionais. Para validar o formato, teste-o em todos os idiomas suportados — alguns símbolos LDML funcionam de maneira diferente em diferentes localidades.
Locale determina como os nomes dos meses, dias da semana e separadores são exibidos. O DateFormatter usa Locale.current por padrão, mas em alguns cenários é necessário especificar uma localidade específica: para um formato fixo em logs use en_US_POSIX, para datas do servidor — a localidade que corresponde ao servidor.
A propriedade TimeZone determina o fuso horário para exibição. Por padrão, o fuso horário do sistema é usado, mas para aplicações com público internacional, muitas vezes é necessário exibir datas no fuso horário do usuário ou em UTC. Alterar o timeZone afeta apenas a exibição — o valor Date permanece inalterado.
Uma característica importante: se DateFormatter for usado para analisar uma string e a string contiver uma indicação de fuso horário (por exemplo, "2026-07-21T14:30:00Z" com Z para UTC), a propriedade timeZone é ignorada — o formatador usa o fuso horário da string. Se o fuso horário estiver ausente na string, o timeZone do formatador é aplicado.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.timeZone = TimeZone(identifier: "Europe/Moscow")
formatter.dateStyle = .long
formatter.timeStyle = .short
let moscowTime = formatter.string(from: Date())
// "21 July 2026, 14:30"
// Analisando sem fuso horário na string
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd HH:mm"
let utcDate = formatter.date(from: "2026-07-21 10:30")!
AutoupdatingCurrentLocale — um tipo especial de localidade que é atualizada automaticamente quando as configurações do sistema do usuário mudam. O DateFormatter a suporta por padrão. Se o aplicativo estiver rodando em segundo plano e o usuário mudar o idioma do sistema, um formatador criado antes da mudança continuará usando a localidade antiga — para atualizar é necessário criar uma nova instância.
ISO8601DateFormatter é um formatador especializado para trabalhar com datas no formato ISO 8601. Este formato é o padrão de fato para APIs REST, JSON e troca de dados. O ISO8601DateFormatter funciona significativamente mais rápido que o DateFormatter porque não depende de localidade e usa uma gramática de análise fixa.
Opções principais do formatador — .withInternetDateTime (2026-07-21T14:30:00Z), .withFractionalSeconds (adiciona milissegundos), .withTimeZone (inclui o offset do fuso horário). Combinando opções, você pode obter qualquer variante ISO 8601: com milissegundos, com fuso horário, apenas com data.
JSONEncoder.DateEncodingStrategy permite configurar globalmente a codificação de datas para todos os modelos Codable. Opções — .iso8601 (usa ISO8601DateFormatter), .formatted(DateFormatter), .millisecondsSince1970, .secondsSince1970. A escolha da estratégia afeta todo o ciclo de vida da serialização e deve ser consistente em todos os endpoints da API.
// ISO8601DateFormatter
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let isoString = isoFormatter.string(from: Date())
// "2026-07-21T14:30:00.000Z"
// JSONEncoder com ISO8601
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
// Alternativa: JSONEncoder com formatador personalizado
let customEncoder = JSONEncoder()
customEncoder.dateEncodingStrategy = .formatted(myFormatter)
DateFormatter vs ISO8601DateFormatter — escolha ISO8601DateFormatter para serializar e analisar datas em API, pois é 5-10 vezes mais rápido que o DateFormatter e não está sujeito a erros de localização. Use DateFormatter para a interface do usuário onde é necessária exibição localizada com nomes de meses e dias no idioma nativo do usuário.
Vamos ver cenários reais de uso do DateFormatter em um aplicativo iOS: exibição em um feed de notícias, inserção de data de nascimento e exportação de um relatório com datas em diferentes fusos horários.
RelativeDateFormatter é ideal para feeds de notícias. Ele exibe "agora mesmo", "há 5 minutos", "ontem" para notícias recentes e muda para a data completa para as antigas. O limite de mudança é configurado através do calendar: para notícias use um limite de 24 horas, para mensageiros — uma semana.
func formatRelativeDate(_ date: Date) -> String {
let relative = RelativeDateFormatter()
relative.unitsStyle = .full
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
let daysDiff = Calendar.current.dateComponents(
[.day], from: date, to: Date()
).day ?? 0
return daysDiff < 1
? relative.localizedString(for: date, relativeTo: Date())
: formatter.string(from: date)
}
Inserção de data de nascimento — outro cenário comum. O DateFormatter é configurado com um dateFormat específico "dd.MM.yyyy" e locale "ru_RU". Ao analisar a string inserida, é importante tratar possíveis erros: o formatador retorna nil para uma string inválida. Após a análise bem-sucedida, a data é verificada para estar dentro de um intervalo aceitável — não antes de 1900, não depois de hoje.
Exportação de relatório com datas requer um formato fixo independente da localidade do usuário. Use dateFormat "yyyy-MM-dd HH:mm:ss" com locale en_US_POSIX e fuso horário UTC. Esta abordagem garante que o arquivo será aberto corretamente em qualquer país, independentemente das configurações regionais do sistema.
| Cenário | Formatador | Configuração chave |
|---|---|---|
| Feed de notícias | RelativeDateFormatter | unitsStyle = .full |
| Inserção de data | DateFormatter | dateFormat + fallback |
| Serialização API | ISO8601DateFormatter | withInternetDateTime |
| Exportação de relatório | DateFormatter | en_US_POSIX + UTC |
Perguntas frequentes
A razão mais comum — uma incompatibilidade entre dateFormat e o formato da string. Por exemplo, o formato "dd.MM.yyyy" não analisará a string "2026-07-21". A segunda razão — incompatibilidade de localidade: a string "July 21, 2026" não será analisada com a localidade ru_RU. A terceira — erros de digitação nos símbolos LDML: use yyyy, não YYYY (significado diferente).
Não. DateFormatter é um objeto pesado, sua inicialização inclui o carregamento de dados de localidade. Crie uma instância por tipo de formatação e reutilize-a. Em um ambiente multi-thread, use armazenamento local de thread ou um pool de formatadores com uma fila serial para sincronização.
DateFormatter exibe uma data absoluta (21 de julho de 2026), enquanto RelativeDateFormatter exibe uma data relativa (hoje, ontem, em 3 dias). O RelativeDateFormatter foi introduzido no iOS 15+ e usa o mesmo modelo LDML, mas seleciona automaticamente a exibição relativa.
Defina o timeZone do formatador para UTC antes de analisar. Se o servidor retornar uma data em horário local sem indicação de fuso horário, verifique a especificação da API — muito provavelmente o UTC está implícito. Para ISO 8601 com Z no final, timeZone não é necessário — o formatador analisa o offset da string.
Não use uma única instância de diferentes threads sem sincronização. Crie uma nova instância em cada thread ou use Thread.current.threadDictionary para armazenamento. Uma alternativa é o NSLock com bloqueio durante a duração de string(from:) e date(from:).
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