RelativeDateTimeFormatter: essência, datas relativas e Swift

Autor: IT Sectr Publicado: 2026-07-13 Tempo de leitura: 10 min

RelativeDateTimeFormatter é uma classe Foundation no iOS e macOS que converte datas absolutas em frases relativas legíveis por humanos: "5 minutos atrás", "ontem", "em 3 dias". De acordo com Apple Developer Documentation, 2024, RelativeDateTimeFormatter seleciona automaticamente a unidade apropriada (segundos, minutos, horas, dias) e localiza a saída no idioma da localidade atual do dispositivo. Ao contrário do cálculo manual da diferença entre datas através de Calendar, esta classe leva em conta as características linguísticas de cada idioma: para alguns idiomas os numerais são flexionados, para outros uma forma especial para a palavra "ontem" é usada. A classe está disponível a partir do iOS 13 e macOS 10.15.

Pontos-chave

  • RelativeDateTimeFormatter — uma classe para exibir datas relativas em iOS e macOS (iOS 13+)
  • Saída localizada — escolhe automaticamente as frases no idioma da localidade atual
  • Três tipos de contexto — passado (atrás), futuro (em), presente (agora) com frases diferentes
  • Seleção automática de unidade — segundos, minutos, horas, dias, semanas, meses, anos
  • Personalização de estilo — numeric (em 3 dias) ou abbreviated (em 3 d.)

O que é RelativeDateTimeFormatter?

RelativeDateTimeFormatter é uma subclasse de Formatter no Foundation que recebe uma Date (ou uma diferença em segundos) e retorna uma string localizada com tempo relativo. Por exemplo, para uma data 5 minutos antes da atual, retorna "5 minutos atrás" para pt_BR. A classe suporta três contextos temporais: passado, futuro e presente.

A lógica interna do RelativeDateTimeFormatter usa Calendar e Locale para calcular a diferença entre datas e selecionar a forma gramatical correta. Para o português, ele escolhe entre "1 minuto atrás" e "5 minutos atrás". Esta funcionalidade é baseada em dados ICU (International Components for Unicode) e não requer configuração adicional do desenvolvedor.

De acordo com a Apple WWDC 2019, o RelativeDateTimeFormatter tornou-se parte do framework para simplificar a localização — antes de sua introdução, os desenvolvedores tinham que calcular manualmente a diferença de datas e substituir strings localizadas através de String.localizedStringWithFormat. Isso causava erros de flexão (especialmente para idiomas eslavos e árabes) e seleção incorreta de unidades de medida.

Como o RelativeDateTimeFormatter exibe "5 minutos atrás"?

O algoritmo do RelativeDateTimeFormatter consiste em três etapas: calcular a diferença entre a data passada e o momento atual, selecionar a unidade apropriada (a maior que não dá zero) e formatar de acordo com a localidade. Por exemplo, para uma diferença de 3720 segundos (1 hora 2 minutos), a unidade "hora" é selecionada e o resultado é "1 hora atrás", não "62 minutos atrás".

As unidades são selecionadas pelo princípio da "maior não nula": se a diferença for maior que 86400 segundos (1 dia), dias são usados; se maior que 604800 (1 semana) — semanas, e assim por diante. Este algoritmo garante que o resultado sempre seja lido naturalmente: em vez de "518400 segundos atrás", o usuário vê "6 dias atrás". Os limites exatos das unidades são determinados pelo calendário da localidade atual.

Intervalo de diferençaUnidadeExemplo para pt_BR
0–59 segundosSeconds30 segundos atrás
1–59 minutosMinutes5 minutos atrás
1–23 horasHours3 horas atrás
1–6 diasDays2 dias atrás
7–27 diasWeeks1 semana atrás
28 dias–11 mesesMonths3 meses atrás
12+ mesesYears1 ano atrás

O contexto de formatação determina a terminação da frase. Para o passado: "atrás" (português). Para o futuro: "em 3 dias" (português). Para o presente: "agora" (português). O contexto é definido através do método localizeString(fromTimeInterval:) ou diretamente através de string(from: Date).

Configurações de unidades e estilos

RelativeDateTimeFormatter fornece várias configurações para controlar a saída: a propriedade unitsStyle determina o estilo de formatação (numeric, abbreviated, full, spellOut), e maximumUnitCount limita o número de unidades exibidas. Por exemplo, com maximumUnitCount = 1, uma diferença de 1 hora 30 minutos é mostrada como "1 hora atrás" em vez de "1 hora 30 minutos atrás".

Estilos de formatação

  • .numeric — valor numérico completo: "3 dias atrás", "em 2 semanas". Recomendado para UI por padrão
  • .abbreviated — forma abreviada: "3 d. atrás", "em 2 sem.". Para exibição compacta em tabelas e listas
  • .full — forma verbal sem dígitos: "três dias atrás". Para Acessibilidade e interfaces de voz
  • .spellOut — forma literal com ortografia alternativa: "three days ago". Raramente usado, principalmente para usos especializados

Limitação de unidades: por padrão, RelativeDateTimeFormatter exibe apenas uma unidade (a maior). Definir maximumUnitCount = 2 inclui a próxima unidade para uma descrição mais precisa: "1 hora 30 minutos atrás". No entanto, isso pode tornar a string excessivamente longa para mensagens curtas (notificações push, alertas). Para a UI, é recomendado manter maximumUnitCount = 1.

swift
import Foundation

let formatter = RelativeDateTimeFormatter()

// Configure styles
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1

// Examples with different dates
let fiveMinAgo = Date().addingTimeInterval(-300)
print("5 min ago: \(formatter.localizedString(for: fiveMinAgo, relativeTo: Date()))")

let twoDaysLater = Date().addingTimeInterval(172800)
print("2 days later: \(formatter.localizedString(for: twoDaysLater, relativeTo: Date()))")

// Abbreviated style
formatter.unitsStyle = .abbreviated
let oneWeekAgo = Date().addingTimeInterval(-604800)
print("Abbreviated: \(formatter.localizedString(for: oneWeekAgo, relativeTo: Date()))")

// Full style (spelled out)
formatter.unitsStyle = .full
let threeHours = Date().addingTimeInterval(10800)
print("Full: \(formatter.localizedString(for: threeHours, relativeTo: Date()))")

Escolha de estilo para diferentes contextos: para um feed de notícias, use .numeric com maximumUnitCount = 1 — este é o padrão para Twitter, Instagram e Facebook. Para Acessibilidade (VoiceOver), use .full — números por extenso são lidos mais naturalmente. Para elementos compactos (ícone de notificação, barra de status), use .abbreviated para economizar espaço.

RelativeDateTimeFormatter em Swift: exemplos

Uso básico do RelativeDateTimeFormatter se resume a criar uma instância, configurar propriedades e chamar um dos métodos de formatação. Os métodos principais são: localizedString(for:relativeTo:) — para um par de datas, localizedString(fromTimeInterval:) — para uma diferença em segundos, e string(for:) — para Date com contexto automático (passado/futuro).

swift
import Foundation

let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1

// Social network UI examples
let postDates: [(title: String, date: Date)] = [
    ("Just now", Date().addingTimeInterval(-30)),
    ("5 min ago", Date().addingTimeInterval(-300)),
    ("Yesterday", Date().addingTimeInterval(-90000)),
    ("Last week", Date().addingTimeInterval(-700000)),
    ("Last year", Date().addingTimeInterval(-32000000))
]

for (title, postDate) in postDates {
    let relative = formatter.localizedString(
        for: postDate,
        relativeTo: Date()
    )
    print("\(title): \(relative)")
}

// Future dates
let reminderFormatter = RelativeDateTimeFormatter()
reminderFormatter.unitsStyle = .abbreviated
let inOneHour = Date().addingTimeInterval(3600)
let reminderText = reminderFormatter.localizedString(
    for: inOneHour,
    relativeTo: Date()
)
print("Reminder: \(reminderText)")

Tratamento do cenário "agora mesmo" — RelativeDateTimeFormatter não tem suporte embutido para a frase "agora mesmo" para intervalos muito pequenos. Para uma diferença de menos de 5 segundos, retorna "0 segundos atrás", o que fica feio na UI. Recomenda-se envolver a chamada do formatador em lógica condicional: se a diferença for menor que um limite definido (por exemplo, 5 segundos) — exibir "agora mesmo" manualmente, caso contrário passar a data para o formatador.

swift
import Foundation

func relativeTimeString(from date: Date) -> String {
    let interval = Date().timeIntervalSince(date)

    // "Just now" threshold
    if interval < 5 {
        return "just now"
    }

    // "Today" threshold
    if interval < 60 {
        return "just now"
    }

    let formatter = RelativeDateTimeFormatter()
    formatter.unitsStyle = .numeric
    formatter.maximumUnitCount = 1

    // Display without "ago" suffix
    return formatter.localizedString(
        for: date,
        relativeTo: Date()
    )
}

print(relativeTimeString(from: Date().addingTimeInterval(-3)))
print(relativeTimeString(from: Date().addingTimeInterval(-120)))
print(relativeTimeString(from: Date().addingTimeInterval(-3600)))

O método string(fromTimeInterval:) aceita uma diferença em segundos e determina automaticamente o contexto (valor positivo — futuro, negativo — passado). Isso é conveniente quando a diferença já é conhecida (por exemplo, recebida do servidor como um timestamp unix). Neste caso, não é necessário criar uma Date — a diferença é passada diretamente.

Localização de datas relativas

RelativeDateTimeFormatter localiza automaticamente a saída com base em Locale.current. Para alterar o idioma de formatação, defina a propriedade locale — ao contrário de DateFormatter, para RelativeDateTimeFormatter a localidade não é fixa e pode ser alterada para cada chamada. Isso permite exibir datas relativas em um idioma diferente do idioma da interface (por exemplo, conteúdo no idioma original).

A complexidade da localização de datas relativas está nas características gramaticais de diferentes idiomas. O português requer diferentes formas de numerais: "1 minuto", "2 minutos". O árabe usa a forma plural para números de 3 a 10 e formas especiais para 11+. O chinês não tem flexão alguma, o que simplifica a tarefa. RelativeDateTimeFormatter cobre todos esses casos através das regras ICU, sem necessidade de código adicional.

swift
import Foundation

let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1

let targetDate = Date().addingTimeInterval(-7200) // 2 hours ago

// Different locales
let locales: [String] = ["ru_RU", "en_US", "de_DE", "fr_FR", "ja_JP", "ar_SA"]

for identifier in locales {
    formatter.locale = Locale(identifier: identifier)
    let result = formatter.localizedString(
        for: targetDate,
        relativeTo: Date()
    )
    print("\(identifier): \(result)")
}

// Check Portuguese pluralization
formatter.locale = Locale(identifier: "ru_RU")
let intervals: [TimeInterval] = [-60, -120, -180, -300]
for interval in intervals {
    let date = Date().addingTimeInterval(interval)
    print("\(-Int(interval / 60)) min: \(formatter.localizedString(for: date, relativeTo: Date()))")
}

Uma nuance importante: RelativeDateTimeFormatter ignora TimeZone ao calcular a diferença para configurações .numeric — ele usa a diferença absoluta em segundos. No entanto, para o estilo .full (com números por extenso) e casos especiais (ontem, hoje), TimeZone é levado em conta. Sempre defina TimeZone explicitamente para consistência, especialmente se o aplicativo trabalha com datas do servidor em UTC.

Erros comuns de formatação

Ignorar TimeZone ao calcular datas relativas — um erro comum ao trabalhar com datas do servidor. Se o servidor enviar uma Date em UTC, e RelativeDateTimeFormatter usar TimeZone.current, a diferença pode ser calculada incorretamente para datas próximas ao momento atual. Recomenda-se sempre definir formatter.timeZone = TimeZone(secondsFromGMT: 0) para dados do servidor.

Seleção incorreta de unidade para intervalos curtos — RelativeDateTimeFormatter arredonda a diferença para a maior unidade. Para 25 horas, o resultado será "1 dia atrás", o que pode enganar o usuário. Se for necessária alta precisão (por exemplo, para temporizadores de contagem regressiva), use DateComponentsFormatter em vez de RelativeDateTimeFormatter — ele permite exibir várias unidades simultaneamente.

Falta de verificação de TimeInterval negativo — se uma data futura for passada como passada (valor negativo em string(fromTimeInterval:)), o formatador pode retornar uma string incorreta. Sempre verifique o sinal do intervalo antes de passá-lo ao formatador, especialmente ao trabalhar com dados do servidor onde o fuso horário pode distorcer o cálculo.

De acordo com o Hacker News (2024), um dos problemas mais discutidos do RelativeDateTimeFormatter é a falta de suporte embutido para "ontem" e "hoje" para o idioma inglês. Em vez de "ontem", o formatador para uma diferença de 90000 segundos retorna "1 dia atrás". Para o português não existe esse problema — "1 dia atrás" soa natural, mas para a UI em inglês "yesterday" é preferível. Esta funcionalidade não é suportada e requer verificação manual através de Calendar.isDateInToday/Yesterday.

Perguntas Frequentes

O que é RelativeDateTimeFormatter?

RelativeDateTimeFormatter é uma classe Foundation para exibir datas em formato relativo: "5 minutos atrás", "em 2 dias". Disponível desde iOS 13 e macOS 10.15.

Como o RelativeDateTimeFormatter seleciona as unidades?

Pelo princípio da maior unidade não nula — segundos, minutos, horas, dias, semanas, meses ou anos. Por exemplo, para uma diferença de 3720 segundos (1 hora 2 minutos), a unidade "hora" é selecionada, não "minutos".

Como alterar o idioma de saída?

Defina a propriedade locale para a instância Locale desejada. Por padrão, Locale.current é usado. Exemplo: formatter.locale = Locale(identifier: "de_DE") para alemão.

Qual a diferença entre .numeric e .abbreviated?

.numeric — forma completa ("3 dias atrás"), .abbreviated — forma abreviada ("3 d. atrás"). A escolha depende do contexto: numeric para UI principal, abbreviated para elementos compactos.

Como exibir "agora mesmo" em vez de "0 segundos atrás"?

Adicione uma verificação manual para um intervalo de menos de 5-10 segundos. RelativeDateTimeFormatter não suporta "agora mesmo" — para intervalos pequenos retorna "0 segundos atrás". Use lógica condicional com um limite.

Resumo

  • RelativeDateTimeFormatter — uma classe conveniente para exibir datas relativas em iOS 13+
  • Localização automática — flexão correta para todos os idiomas suportados via ICU
  • Três estilos — .numeric (padrão), .abbreviated (compacto), .full (por extenso)
  • Seleção de unidade — automática baseada no maior valor não nulo
  • Configuração de TimeZone — obrigatória para consistência ao trabalhar com datas do servidor
  • Limite "agora mesmo" — não suportado nativamente; requer verificação manual do intervalo
  • Sem suporte para "ontem" — o formatador não usa a forma yesterday para inglês

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.

Discutir o projeto

Leia também