DateCivile.swift
86 lignes · 3317 octets
import Foundation /// Date sans heure ni fuseau — équivalent Swift de `java.time.LocalDate`, que /// Foundation n'offre pas. /// /// Le calendrier de planification raisonne en jours, jamais en instants : un /// `Date` (qui est un instant UTC) donnerait des résultats dépendants du fuseau /// de l'appareil. Toute l'arithmétique passe donc par un calendrier grégorien /// figé sur UTC. struct DateCivile: Equatable, Comparable, CustomStringConvertible { let annee: Int let mois: Int let jour: Int /// Calendrier grégorien en UTC : même résultat quel que soit le réglage de l'appareil. private static let calendrier: Calendar = { var c = Calendar(identifier: .gregorian) c.timeZone = TimeZone(secondsFromGMT: 0)! return c }() /// `nil` si la chaîne n'est pas un `AAAA-MM-JJ` valide (donnée abîmée par une /// synchronisation partielle). init?(iso: String) { let parts = iso.split(separator: "-", omittingEmptySubsequences: false) guard parts.count == 3, parts[0].count == 4, parts[1].count == 2, parts[2].count == 2, let a = Int(parts[0]), let m = Int(parts[1]), let j = Int(parts[2]) else { return nil } // Rejette les dates inexistantes (31/02) : le calendrier les normaliserait en silence. let composantes = DateComponents(year: a, month: m, day: j) guard composantes.isValidDate(in: Self.calendrier) else { return nil } annee = a mois = m jour = j } private init(annee: Int, mois: Int, jour: Int) { self.annee = annee self.mois = mois self.jour = jour } private var instantUtc: Date { Self.calendrier.date(from: DateComponents(year: annee, month: mois, day: jour))! } private init(instantUtc: Date) { let c = Self.calendrier.dateComponents([.year, .month, .day], from: instantUtc) self.init(annee: c.year!, mois: c.month!, jour: c.day!) } /// 1 = lundi … 7 = dimanche (numérotation `java.time.DayOfWeek`). var jourDeSemaine: Int { // `Calendar.weekday` est 1 = dimanche … 7 = samedi. let weekday = Self.calendrier.component(.weekday, from: instantUtc) return weekday == 1 ? 7 : weekday - 1 } func plusJours(_ n: Int) -> DateCivile { DateCivile(instantUtc: Self.calendrier.date(byAdding: .day, value: n, to: instantUtc)!) } /// « 09/01 » — format court des cartes kanban, identique au web. var jourMois: String { "\(Self.pad(jour, 2))/\(Self.pad(mois, 2))" } var iso: String { "\(Self.pad(annee, 4))-\(Self.pad(mois, 2))-\(Self.pad(jour, 2))" } /// Zéros de tête à la main : `String(format: "%02d", …)` mélange `Int` /// 64 bits et `%d` 32 bits dans les varargs, et décale les arguments suivants. private static func pad(_ valeur: Int, _ largeur: Int) -> String { let chiffres = String(valeur) guard chiffres.count < largeur else { return chiffres } return String(repeating: "0", count: largeur - chiffres.count) + chiffres } var description: String { iso } static func < (lhs: DateCivile, rhs: DateCivile) -> Bool { (lhs.annee, lhs.mois, lhs.jour) < (rhs.annee, rhs.mois, rhs.jour) } }
GitRust