Trabajo avanzado con fechas y horas en Swift: de lo básico a la optimización
\nTrabajar con fechas y horas es una de las tareas más comunes y, al mismo tiempo, más engañosas en el desarrollo de iOS/macOS. La aparente simplicidad de la API de Foundation (Date, DateFormatter, Calendar) a menudo conduce a errores relacionados con zonas horarias, localización o rendimiento. En este artículo, analizaremos técnicas avanzadas para trabajar con fechas en Swift: desde un análisis correcto hasta cálculos de alto rendimiento que serán útiles en proyectos de producción.
\n\n1. DateFormatter: ajuste fino y almacenamiento en caché
\nEl error más común de los principiantes es crear un nuevo objeto DateFormatter dentro de un bucle o un método llamado con frecuencia. Crear un DateFormatter es una operación costosa (hasta 1000 veces más lenta que reutilizarlo).
\n\n// ❌ Mal: creamos un formatter cada vez\nfunc parseDate(from string: String) -> Date? {\n let formatter = DateFormatter()\n formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"\n formatter.locale = Locale(identifier: "ru_RU")\n formatter.timeZone = TimeZone(secondsFromGMT: 0)\n return formatter.date(from: string)\n}\n\n// ✅ Bien: almacenamos en caché el formatter\nprivate let isoDateFormatter: DateFormatter = {\n let f = DateFormatter()\n f.dateFormat = "yyyy-MM-dd HH:mm:ss"\n f.locale = Locale(identifier: "ru_RU")\n f.timeZone = TimeZone(secondsFromGMT: 0)\n return f\n}()\n\nfunc parseDateFast(from string: String) -> Date? {\n return isoDateFormatter.date(from: string)\n}\n\nConsejo avanzado: Para trabajar con formatos fijos (por ejemplo, ISO 8601), usa ISO8601DateFormatter — es más rápido que el DateFormatter estándar. Para formatos personalizados que requieren alto rendimiento, considera DateFormatter con almacenamiento local de hilo:
extension DateFormatter {\n static let threadLocal: ThreadLocal<DateFormatter> = ThreadLocal {\n let f = DateFormatter()\n f.dateFormat = "dd.MM.yyyy"\n f.locale = Locale(identifier: "ru_RU")\n return f\n }\n}\n\n2. Calendar y DateComponents: cálculos sin errores
\nPara la aritmética de fechas (agregar un día, encontrar el inicio del mes), usa Calendar, no la suma manual de segundos. Agregar manualmente 86400 segundos (24*60*60) falla durante el cambio al horario de verano.
let calendar = Calendar.current\nlet now = Date()\n\n// ✅ Agregamos 1 día de forma segura\nif let tomorrow = calendar.date(byAdding: .day, value: 1, to: now) {\n print("Mañana: \\(tomorrow)")\n}\n\n// ✅ Encontramos el inicio del mes actual\nlet startOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: now))!\n\n// ✅ Obtenemos componentes teniendo en cuenta la configuración regional\nlet components = calendar.dateComponents([.weekday, .weekOfYear], from: now)\nprint("Día de la semana: \\(components.weekday ?? 0)") // 1 = domingo en EE. UU., 2 = lunes en RF\n\nConsejo avanzado: Para cálculos de alta carga (por ejemplo, generación de una cuadrícula de calendario), almacena en caché Calendar y TimeZone. Evita usar Calendar.current en bucles: cada vez accede a la configuración del sistema.
3. Localización y formato para el usuario
\nNunca muestres al usuario una fecha en bruto. Usa DateFormatter con estilos predefinidos (.short, .medium, .long, .full) y RelativeDateTimeFormatter para fechas relativas legibles por humanos ("ayer", "hace 3 días").
// Fecha absoluta teniendo en cuenta la configuración regional del usuario\nlet userFormatter = DateFormatter()\nuserFormatter.dateStyle = .long\nuserFormatter.timeStyle = .short\nprint(userFormatter.string(from: Date())) // "15 de junio de 2025, 14:30" (para es_ES)\n\n// Fecha relativa (iOS 13+)\nlet relativeFormatter = RelativeDateTimeFormatter()\nrelat