Travail avancé avec les dates et l'heure en Swift : des bases à l'optimisation
\nTravailler avec les dates et l'heure est l'une des tâches les plus fréquentes et les plus délicates en développement iOS/macOS. La simplicité apparente de l'API Foundation (Date, DateFormatter, Calendar) conduit souvent à des erreurs liées aux fuseaux horaires, à la localisation ou aux performances. Dans cet article, nous allons explorer les techniques avancées de travail avec les dates en Swift : du parsing correct aux calculs haute performance utiles dans les projets de production.
\n\n1. DateFormatter : réglage fin et mise en cache
\nL'erreur la plus courante des débutants est de créer un nouvel objet DateFormatter à l'intérieur d'une boucle ou d'une méthode fréquemment appelée. La création d'un DateFormatter est une opération coûteuse (jusqu'à 1000 fois plus lente que sa réutilisation).
\n\n// ❌ Mauvais : création d'un formatter à chaque fois\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// ✅ Bon : mise en cache du 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\nConseil avancé : Pour travailler avec des formats fixes (par exemple, ISO 8601), utilisez ISO8601DateFormatter — il est plus rapide que le DateFormatter standard. Pour les formats personnalisés nécessitant des performances élevées, envisagez d'utiliser DateFormatter avec un stockage thread-local :
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 et DateComponents : calculs sans erreurs
\nPour l'arithmétique des dates (ajouter un jour, trouver le début du mois), utilisez Calendar plutôt que l'addition manuelle de secondes. L'ajout manuel de 86400 secondes (24*60*60) échoue lors du passage à l'heure d'été.
let calendar = Calendar.current\nlet now = Date()\n\n// ✅ Ajout sécurisé d'un jour\nif let tomorrow = calendar.date(byAdding: .day, value: 1, to: now) {\n print("Demain : \\(tomorrow)")\n}\n\n// ✅ Trouver le début du mois en cours\nlet startOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: now))!\n\n// ✅ Obtenir les composants en tenant compte de la locale\nlet components = calendar.dateComponents([.weekday, .weekOfYear], from: now)\nprint("Jour de la semaine : \\(components.weekday ?? 0)") // 1 = dimanche aux États-Unis, 2 = lundi en France\n\nConseil avancé : Pour les calculs à forte charge (par exemple, la génération d'une grille de calendrier), mettez en cache Calendar et TimeZone. Évitez d'utiliser Calendar.current dans les boucles — il sollicite les paramètres système à chaque fois.
3. Localisation et formatage pour l'utilisateur
\nNe montrez jamais une date brute à l'utilisateur. Utilisez DateFormatter avec des styles prédéfinis (.short, .medium, .long, .full) et RelativeDateTimeFormatter pour les dates relatives lisibles par l'humain ("hier", "il y a 3 jours").
// Date absolue tenant compte de la locale de l'utilisateur\nlet userFormatter = DateFormatter()\nuserFormatter.dateStyle = .long\nuserFormatter.timeStyle = .short\nprint(userFormatter.string(from: Date())) // "15 juin 2025, 14:30" (pour fr_FR)\n\n// Date relative (iOS 13+)\nlet relativeFormatter = RelativeDateTimeFormatter()\nrelat