RelativeDateTimeFormatter: esencia, fechas relativas y Swift

Autor: IT Sectr Publicado: 2026-07-13 Tiempo de lectura: 10 min

RelativeDateTimeFormatter es una clase de Foundation en iOS y macOS que convierte fechas absolutas en frases relativas legibles por humanos: "hace 5 minutos", "ayer", "en 3 días". Según Apple Developer Documentation, 2024, RelativeDateTimeFormatter selecciona automáticamente la unidad adecuada (segundos, minutos, horas, días) y localiza la salida en el idioma de la configuración regional actual del dispositivo. A diferencia del cálculo manual de la diferencia entre fechas mediante Calendar, esta clase tiene en cuenta las características lingüísticas de cada idioma: para algunos idiomas se declinan los numerales, para otros se usa una forma especial para la palabra "ayer". La clase está disponible desde iOS 13 y macOS 10.15.

Puntos clave

  • RelativeDateTimeFormatter — una clase para mostrar fechas relativas en iOS y macOS (iOS 13+)
  • Salida localizada — elige automáticamente las frases en el idioma de la configuración regional actual
  • Tres tipos de contexto — pasado (hace), futuro (en), presente (ahora) con diferentes frases
  • Selección automática de unidad — segundos, minutos, horas, días, semanas, meses, años
  • Personalización de estilo — numeric (en 3 días) o abbreviated (en 3 d.)

¿Qué es RelativeDateTimeFormatter?

RelativeDateTimeFormatter es una subclase de Formatter en Foundation que toma una Date (o una diferencia en segundos) y devuelve una cadena localizada con tiempo relativo. Por ejemplo, para una fecha 5 minutos antes de la actual, devuelve "hace 5 minutos" para es_ES. La clase admite tres contextos temporales: pasado, futuro y presente.

La lógica interna de RelativeDateTimeFormatter utiliza Calendar y Locale para calcular la diferencia entre fechas y seleccionar la forma gramatical correcta. Para el español, elige entre "hace 1 minuto" y "hace N minutos". Esta funcionalidad se basa en los datos de ICU (International Components for Unicode) y no requiere configuración adicional por parte del desarrollador.

Según Apple WWDC 2019, RelativeDateTimeFormatter se convirtió en parte del framework para simplificar la localización — antes de su aparición, los desarrolladores tenían que calcular manualmente la diferencia de fechas y sustituir cadenas localizadas mediante String.localizedStringWithFormat. Esto provocaba errores de declinación (especialmente para idiomas eslavos y árabes) y una selección incorrecta de las unidades de medida.

¿Cómo muestra RelativeDateTimeFormatter "hace 5 minutos"?

El algoritmo de RelativeDateTimeFormatter consta de tres pasos: calcular la diferencia entre la fecha pasada y el momento actual, seleccionar la unidad adecuada (la más grande que no dé cero) y formatear según la configuración regional. Por ejemplo, para una diferencia de 3720 segundos (1 hora 2 minutos), se selecciona la unidad "hora" y el resultado es "hace 1 hora", no "hace 62 minutos".

Las unidades se seleccionan según el principio de "la más grande no nula": si la diferencia es mayor de 86400 segundos (1 día), se usan días; si es mayor de 604800 (1 semana) — semanas, y así sucesivamente. Este algoritmo garantiza que el resultado siempre se lea de forma natural: en lugar de "hace 518400 segundos", el usuario ve "hace 6 días". Los límites exactos de las unidades los determina el calendario de la configuración regional actual.

Rango de diferenciaUnidadEjemplo para es_ES
0–59 segundosSecondshace 30 segundos
1–59 minutosMinuteshace 5 minutos
1–23 horasHourshace 3 horas
1–6 díasDayshace 2 días
7–27 díasWeekshace 1 semana
28 días–11 mesesMonthshace 3 meses
12+ mesesYearshace 1 año

El contexto de formato determina la terminación de la frase. Para el pasado: "hace" (español). Para el futuro: "en 3 días" (español). Para el presente: "ahora" (español). El contexto se establece mediante el método localizeString(fromTimeInterval:) o directamente a través de string(from: Date).

Configuración de unidades y estilos

RelativeDateTimeFormatter proporciona varias opciones para controlar la salida: la propiedad unitsStyle determina el estilo de formato (numeric, abbreviated, full, spellOut), y maximumUnitCount limita el número de unidades mostradas. Por ejemplo, con maximumUnitCount = 1, una diferencia de 1 hora 30 minutos se muestra como "hace 1 hora" en lugar de "hace 1 hora 30 minutos".

Estilos de formato

  • .numeric — valor numérico completo: "hace 3 días", "en 2 semanas". Recomendado para UI por defecto
  • .abbreviated — forma abreviada: "hace 3 d.", "en 2 sem.". Para visualización compacta en tablas y listas
  • .full — forma verbal sin dígitos: "hace tres días". Para Accesibilidad e interfaces de voz
  • .spellOut — forma literal con ortografía alternativa: "hace three days". Se usa raramente, principalmente para usos especializados

Limitación de unidades: por defecto, RelativeDateTimeFormatter muestra solo una unidad (la más grande). Establecer maximumUnitCount = 2 incluye la siguiente unidad para una descripción más precisa: "hace 1 hora 30 minutos". Sin embargo, esto puede hacer que la cadena sea excesivamente larga para mensajes cortos (notificaciones push, alertas). Para la UI, se recomienda mantener 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()))")

Elección de estilo para diferentes contextos: para un feed de noticias, usa .numeric con maximumUnitCount = 1 — este es el estándar para Twitter, Instagram y Facebook. Para Accesibilidad (VoiceOver), usa .full — los números escritos se leen de forma más natural. Para elementos compactos (insignia de notificación, barra de estado), usa .abbreviated para ahorrar espacio.

RelativeDateTimeFormatter en Swift: ejemplos

Uso básico de RelativeDateTimeFormatter se reduce a crear una instancia, configurar propiedades y llamar a uno de los métodos de formato. Los métodos principales son: localizedString(for:relativeTo:) — para un par de fechas, localizedString(fromTimeInterval:) — para una diferencia en segundos, y string(for:) — para Date con contexto automático (pasado/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)")

Manejo del escenario "justo ahora" — RelativeDateTimeFormatter no tiene soporte incorporado para la frase "justo ahora" para intervalos muy pequeños. Para una diferencia de menos de 5 segundos, devuelve "hace 0 segundos", lo que se ve mal en la UI. Se recomienda envolver la llamada al formateador en una lógica condicional: si la diferencia es menor que un umbral establecido (por ejemplo, 5 segundos) — mostrar "justo ahora" manualmente, de lo contrario pasar la fecha al formateador.

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)))

El método string(fromTimeInterval:) acepta una diferencia en segundos y determina automáticamente el contexto (valor positivo — futuro, negativo — pasado). Esto es útil cuando la diferencia ya se conoce (por ejemplo, recibida del servidor como un timestamp unix). En este caso, no es necesario crear una Date — la diferencia se pasa directamente.

Localización de fechas relativas

RelativeDateTimeFormatter localiza automáticamente la salida basándose en Locale.current. Para cambiar el idioma de formato, establece la propiedad locale — a diferencia de DateFormatter, para RelativeDateTimeFormatter la locale no es fija y se puede cambiar para cada llamada. Esto permite mostrar fechas relativas en un idioma diferente al del idioma de la interfaz (por ejemplo, contenido en el idioma original).

La complejidad de la localización de fechas relativas radica en las características gramaticales de los diferentes idiomas. El español requiere diferentes formas de numerales: "1 minuto", "2 minutos". El árabe usa la forma plural para números del 3 al 10 y formas especiales para 11+. El chino no tiene declinación en absoluto, lo que simplifica la tarea. RelativeDateTimeFormatter cubre todos estos casos a través de las reglas de ICU, sin necesidad 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 Spanish 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()))")
}

Un matiz importante: RelativeDateTimeFormatter ignora TimeZone al calcular la diferencia para la configuración .numeric — usa la diferencia absoluta en segundos. Sin embargo, para el estilo .full (con números escritos) y casos especiales (ayer, hoy), TimeZone se tiene en cuenta. Establece siempre TimeZone explícitamente para mantener la coherencia, especialmente si la aplicación trabaja con fechas del servidor en UTC.

Errores comunes de formato

Ignorar TimeZone al calcular fechas relativas — un error común al trabajar con fechas del servidor. Si el servidor envía una Date en UTC, y RelativeDateTimeFormatter usa TimeZone.current, la diferencia puede calcularse incorrectamente para fechas cercanas al momento actual. Se recomienda establecer siempre formatter.timeZone = TimeZone(secondsFromGMT: 0) para datos del servidor.

Selección incorrecta de unidad para intervalos cortos — RelativeDateTimeFormatter redondea la diferencia a la unidad más grande. Para 25 horas, el resultado será "hace 1 día", lo que puede engañar al usuario. Si se necesita alta precisión (por ejemplo, para temporizadores de cuenta regresiva), usa DateComponentsFormatter en lugar de RelativeDateTimeFormatter — permite mostrar varias unidades simultáneamente.

Falta de verificación de TimeInterval negativo — si una fecha futura se pasa como pasada (valor negativo en string(fromTimeInterval:)), el formateador puede devolver una cadena incorrecta. Verifica siempre el signo del intervalo antes de pasarlo al formateador, especialmente al trabajar con datos del servidor donde la zona horaria puede distorsionar el cálculo.

Según Hacker News (2024), uno de los problemas más discutidos de RelativeDateTimeFormatter es la falta de soporte incorporado para "ayer" y "hoy" en inglés. En lugar de "ayer", el formateador para una diferencia de 90000 segundos devuelve "hace 1 día". Para el español no existe tal problema — "hace 1 día" suena natural, pero para la UI en inglés "yesterday" es preferible. Esta funcionalidad no es compatible y requiere una verificación manual mediante Calendar.isDateInToday/Yesterday.

Preguntas Frecuentes

¿Qué es RelativeDateTimeFormatter?

RelativeDateTimeFormatter es una clase de Foundation para mostrar fechas en formato relativo: "hace 5 minutos", "en 2 días". Disponible desde iOS 13 y macOS 10.15.

¿Cómo selecciona RelativeDateTimeFormatter las unidades?

Por el principio de la unidad no nula más grande — segundos, minutos, horas, días, semanas, meses o años. Por ejemplo, para una diferencia de 3720 segundos (1 hora 2 minutos), se selecciona la unidad "hora", no "minutos".

¿Cómo cambiar el idioma de salida?

Establece la propiedad locale en la instancia de Locale deseada. Por defecto se usa Locale.current. Ejemplo: formatter.locale = Locale(identifier: "de_DE") para alemán.

¿Cuál es la diferencia entre .numeric y .abbreviated?

.numeric — forma completa ("hace 3 días"), .abbreviated — forma abreviada ("hace 3 d."). La elección depende del contexto: numeric para la UI principal, abbreviated para elementos compactos.

¿Cómo mostrar "justo ahora" en lugar de "hace 0 segundos"?

Añade una verificación manual para un intervalo de menos de 5-10 segundos. RelativeDateTimeFormatter no admite "justo ahora" — para intervalos pequeños devuelve "hace 0 segundos". Usa lógica condicional con un umbral.

Resumen

  • RelativeDateTimeFormatter — una clase conveniente para mostrar fechas relativas en iOS 13+
  • Localización automática — declinación correcta para todos los idiomas admitidos mediante ICU
  • Tres estilos — .numeric (estándar), .abbreviated (compacto), .full (en letras)
  • Selección de unidad — automática basada en el valor no nulo más grande
  • Configuración de TimeZone — obligatoria para la coherencia al trabajar con fechas del servidor
  • Umbral "justo ahora" — no compatible de forma nativa; requiere verificación manual del intervalo
  • Sin soporte para "ayer" — el formateador no usa la forma yesterday para inglés

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también