1. Une date n’est pas une chaine
Quand nous commencons a ecrire des programmes, nous avons presque toujours envie de penser a une date comme a du texte : "2026-01-16" semble clair, on peut l’afficher a l’ecran — et voila. Mais le texte n’a pas de « sens temporel » integre : une chaine peut etre ecrite par erreur dans un autre format, comparee « par ordre alphabetique » et donner un resultat surement faux.
En Swift (plus precisement, dans Foundation), il existe le type Date, qui represente un moment sur la ligne du temps. Ce n’est pas un « jour calendaire » ni une « chaine de date », mais bien un point « ici » sur la ligne temporelle. Un tel objet peut etre compare, trie et utilise pour calculer la difference entre deux instants — et c’est exactement ce qu’il nous faut si nous voulons ecrire une logique fiable.
Un petit repere pour ne pas se melanger :
| Ce que c’est | Exemple | A quoi ca sert | Risque principal |
|---|---|---|---|
|
|
affichage a l’utilisateur, saisie utilisateur | comparaison de chaines et formats differents |
|
|
comparaison « plus tot / plus tard », tri, intervalles | semble « moche » sans formateur |
|
|
duree en secondes | confondre secondes et millisecondes |
Dans cette lecon, nous gardons volontairement le focus sur Date comme sur une valeur pour la logique. L’affichage joli et le parsing des chaines, ce sont les lecons suivantes du jour.
2. import Foundation et creation de Date()
Avant de commencer a coder, il est important de comprendre pourquoi Foundation est necessaire. Swift, en tant que langage et bibliotheque standard, nous fournit les types de base (Int, String, Array, etc.). En revanche, les types de la « vie reelle » — dates, URL, fichiers et bien d’autres — vivent dans Foundation. C’est pourquoi presque tout exemple avec des dates commence de la meme maniere : import Foundation.
Le moyen le plus simple d’obtenir une date est d’appeler Date() sans argument. Cela cree un objet correspondant au « moment actuel ».
import Foundation
let now = Date()
print(now) // par exemple : 2026-01-16 18:42:10 +0000
Faites attention a deux choses. D’abord, l’affichage a l’air « technique » et peut sembler etrange — c’est normal. Ensuite, Date() cree un moment, pas une « date de calendrier ». Nous ne parlons pas encore de « janvier / fevrier / jour de la semaine » — nous parlons de « maintenant ».
Si vous voulez visualiser cela, vous pouvez imaginer ce schema :
flowchart LR
A["... passe ..."] --> B["Date: now"] --> C["... futur ..."]
Date est le point B sur cette ligne.
Au passage, un fait utile du monde Foundation : Date en Swift est un value type (une structure), et elle stocke a l’interieur un nombre (secondes / intervalle), donc elle est « petite » en taille et pratique a transmettre comme valeur.
3. Comparer Date : qui est plus tot, qui est plus tard
Quand on parle d’un « moment dans le temps », l’etape logique suivante est d’apprendre a comparer ces moments. Et ici, Date se comporte tres bien : on peut le comparer avec les operateurs habituels <, >, ==.
Imaginons que nous voulions verifier : « un moment qui arrive 5 secondes plus tard que maintenant ». Pour cela, nous devons ajouter un intervalle au moment courant.
import Foundation
let now = Date()
let inFiveSeconds = now.addingTimeInterval(5)
print(now < inFiveSeconds) // true
print(now == inFiveSeconds) // false
Ici se cache immediatement une idee importante : addingTimeInterval(_:) ajoute des secondes, et non des « jours / mois calendaires ». Autrement dit, c’est de la pure mathematique sur la ligne du temps. Aujourd’hui, cela suffit et c’est meme ideal : nous parlons d’un « point dans le temps », pas d’un « ajout de 1 jour calendaire » (cela viendra avec Calendar dans la lecon suivante).
Encore un exemple pour fixer l’idee : « quoi arrive d’abord — le depart ou l’arrivee ? »
import Foundation
let start = Date()
let finish = start.addingTimeInterval(2)
print(start < finish) // true
print(finish > start) // true
Si vous avez deja compare des chaines comme "2026-1-9" et "2026-01-10" et obtenu des resultats etranges, vous avez deja vu pourquoi Date est plus utile qu’une chaine.
4. Intervalles : TimeInterval et difference entre moments
Presque toujours, avec les dates, apparait la question « combien de temps s’est ecoule ? ». Pour cela, l’API utilise le type TimeInterval. En pratique, c’est un Double exprime en secondes : 1.0 signifie une seconde, 2.5 signifie deux secondes et demie. Dans Foundation, cette approche est etablie depuis tres longtemps, donc vous rencontrerez TimeInterval litteralement partout.
La difference entre deux dates se calcule commodement avec la methode timeIntervalSince(_:).
import Foundation
let start = Date()
let end = start.addingTimeInterval(2.5)
let seconds = end.timeIntervalSince(start)
print(seconds) // 2.5
C’est l’un des exemples les plus « honnetes » : nous ne parsions pas de chaines, nous ne pensons pas aux calendriers — nous mesurons la distance entre deux points sur la ligne du temps.
Il est tres important de noter que si vous changez l’ordre, le signe change.
import Foundation
let start = Date()
let end = start.addingTimeInterval(10)
print(start.timeIntervalSince(end)) // -10.0
Une valeur negative n’est pas une erreur, mais un sens : « start etait 10 secondes avant end ».
5. Timestamp : timeIntervalSince1970 et unites de mesure
Parfois, une date doit etre stockee sous une forme « brute » — par exemple, enregistree dans un fichier, envoyee sur le reseau ou simplement affichee dans un log pour pouvoir etre comparee sans formateurs. Pour cela, on utilise souvent un timestamp : le nombre de secondes ecoulees depuis le 1er janvier 1970 (UTC). En Swift, cela est disponible via timeIntervalSince1970.
import Foundation
let now = Date()
let ts = now.timeIntervalSince1970
print(ts) // par exemple : 1768588930.12345
Pourquoi c’est utile : un nombre est facile a stocker et a comparer, il ne depend pas du format d’affichage. Pourquoi c’est dangereux : il est tres facile de confondre secondes et millisecondes.
Dans le monde des API, on rencontre souvent des timestamps en millisecondes (surtout en JavaScript). La, « maintenant » ressemble a un nombre de l’ordre de 1700000000000, c’est-a-dire mille fois plus grand. En Swift, timeIntervalSince1970 est en secondes. Si vous recevez des millisecondes et les passez comme des secondes, vous partirez vers un futur tres lointain (parfois si lointain que meme votre bug report aura vieilli).
Faisons une petite protection : une fonction qui « devine » si l’on a des secondes ou des millisecondes. Ce n’est pas une strategie ideale pour la production, mais comme exercice pedagogique, cela montre parfaitement le probleme.
import Foundation
func dateFromPossiblyMilliseconds(_ value: Double) -> Date {
// Si le nombre est trop grand, nous supposons que ce sont des millisecondes
if value > 10_000_000_000 {
return Date(timeIntervalSince1970: value / 1000.0)
}
return Date(timeIntervalSince1970: value)
}
Remarquez que nous utilisons Date(timeIntervalSince1970:) — c’est un constructeur qui cree un moment a partir d’un timestamp.
6. Date et tri : dates et evenements
Nous savons deja comparer des dates. Le tri, c’est simplement une « comparaison a grande echelle », quand nous voulons remettre de l’ordre dans un tableau : des plus anciens aux plus recents, ou inversement.
Puisque Date prend en charge la comparaison, un tableau de dates peut etre trie tres simplement :
import Foundation
let a = Date(timeIntervalSince1970: 100)
let b = Date(timeIntervalSince1970: 50)
let c = Date(timeIntervalSince1970: 200)
let dates = [a, b, c]
let sortedDates = dates.sorted()
print(sortedDates[0].timeIntervalSince1970) // 50.0
Ici, sorted() renvoie un nouveau tableau sans modifier l’ancien. C’est pratique lorsque vous ne voulez pas « casser » les donnees d’origine.
Si l’on veut trier « sur place », on utilise sort() :
import Foundation
var dates = [
Date(timeIntervalSince1970: 3),
Date(timeIntervalSince1970: 1),
Date(timeIntervalSince1970: 2)
]
dates.sort()
print(dates.map { $0.timeIntervalSince1970 }) // [1.0, 2.0, 3.0]
Et si l’on veut l’ordre inverse (les plus recents en haut), on peut definir la regle de tri avec une closure — vous savez deja faire cela avec sorted(by:) :
import Foundation
let dates = [
Date(timeIntervalSince1970: 10),
Date(timeIntervalSince1970: 30),
Date(timeIntervalSince1970: 20)
]
let newestFirst = dates.sorted(by: >)
print(newestFirst.map { $0.timeIntervalSince1970 }) // [30.0, 20.0, 10.0]
Il faut sentir ici que le tri n’est pas une « magie des dates ». C’est un tri ordinaire, simplement les elements du tableau sont des Date.
Tri d’evenements par heure
Parfois, les etudiants butent sur cette idee : « Je sais trier des dates, mais comment trier des evenements ? » La reponse est : ce n’est pas « l’evenement » qui est trie, mais le tableau, et c’est a nous de definir la regle selon le champ a comparer.
Le schema de base ressemble a ceci : nous prenons un tuple / un modele et comparons sa partie. Par exemple, si un evenement stocke at: Date, alors le tri « par heure » consiste a comparer at.
Pour mettre les « plus recents en haut », il suffit de changer le sens de comparaison :
import Foundation
func printEventsNewestFirst(_ events: [(at: Date, text: String)]) {
let sorted = events.sorted { $0.at > $1.at }
for e in sorted {
print("\(e.at.timeIntervalSince1970): \(e.text)")
}
}
Si nous ecrivions un vrai logger maintenant, « les plus recents en haut » serait souvent plus pratique. Mais dans les exemples pedagogiques, il est utile de garder les deux versions en tete et de voir que la difference tient a un seul symbole.
7. Mini-application : journal d’evenements avec heure
Pour ne pas rester au niveau du « simple type », integrons Date dans une petite application console. Aujourd’hui, notre objectif est simple : apprendre a enregistrer des evenements avec leur heure et a les afficher dans le bon ordre.
Nous n’utilisons pas encore struct (cela viendra plus tard dans le cours), donc prenons un outil familier et leger : un tableau de tuples. Chaque element est (at: Date, text: String).
Structure de stockage et ajout d’un evenement
Commencons par declarer un tableau et ecrivons une fonction qui ajoute un evenement « maintenant ».
import Foundation
var events: [(at: Date, text: String)] = []
func addEvent(_ text: String) {
events.append((at: Date(), text: text))
}
Ici, tout est volontairement minimaliste : une action — un evenement — nous enregistrons un instant.
Afficher la liste des evenements dans l’ordre chronologique
Creons maintenant une fonction qui affiche les evenements tries par heure. Puisque Date est comparable, le tri se fait sans douleur.
import Foundation
func printEventsOldestFirst() {
let sorted = events.sorted { $0.at < $1.at }
for e in sorted {
print("\(e.at.timeIntervalSince1970): \(e.text)")
}
}
Nous affichons un timestamp parce que nous n’avons pas encore vu le formatage d’une « belle » date. Oui, ce n’est pas « pour un humain », mais c’est honnete et stable pour la logique.
8. Boucle CLI simple : "add", "list", "exit"
Nous voulons maintenant que l’application soit « vivante » : l’utilisateur tape des commandes et le programme reagit. Nous ne faisons pas un parseur de commandes complexe (cela sera un grand sujet separe sur le CLI parsing), donc nous nous accordons sur un format simple :
- add <texte de l’evenement> — ajouter un evenement avec l’heure actuelle,
- list — afficher les evenements,
- exit — quitter.
Ecrivons une boucle de lecture des commandes. Remarquez que nous utilisons ici readLine() et que nous traitons Optional avec soin.
import Foundation
while true {
print("Commande (add/list/exit):", terminator: " ")
guard let line = readLine() else { break }
let parts = line.split(separator: " ", maxSplits: 1)
let cmd = parts.first?.lowercased() ?? ""
if cmd == "add" {
let text = parts.count > 1 ? String(parts[1]) : ""
addEvent(text.isEmpty ? "Evenement vide" : text)
} else if cmd == "list" {
printEventsOldestFirst()
} else if cmd == "exit" {
break
} else {
print("Commande inconnue")
}
}
Ici, il y a beaucoup de petites « habitudes de bon ton ». Nous ne forcons pas readLine()!, nous ne lisons pas parts[1] sans verification, nous ne plantons pas a cause d’une chaine vide. Le programme peut etre simple, mais qu’il soit simple et robuste.
Nuance de pensee : Date est un moment, et « l’apparence de la date » est separee
Il est tres facile de tomber dans le piege et de commencer a attendre de Date des reponses du style « quel jour sommes-nous ? » ou « quel mois est-ce maintenant ? ». Mais Date a un autre role : stocker et comparer un moment. Les composants humains (annee / mois / jour), les fuseaux horaires et le formatage sont des outils distincts, volontairement places dans d’autres types.
Si l’on garde cette frontiere, le code devient plus calme. Date, c’est ce que vous stockez, comparez et triez. Et tout ce qui concerne « comment le montrer a un humain » se decide plus tard et de maniere centralisee. Dans Foundation, cette separation est consideree comme fondamentale : le moment temporel d’un cote, l’interpretation calendaire de l’autre.
Aujourd’hui, nous ne formatons pas « joliment » — non pas parce que nous ne savons pas le faire, mais parce que nous apprenons a bien separer les responsabilites dans le code.
9. Erreurs typiques lors du travail avec Date
Erreur n°1 : confondre Date et String et comparer des chaines au lieu de dates.
Quand vous stockez les dates sous forme de chaines, vous vous retrouvez tres vite avec deux formats (« ici YYYY-MM-DD, et la DD.MM.YYYY »), et la comparaison devient une loterie. Si la logique depend du temps, stockez le moment comme Date, et ne faites une chaine que pour l’entree / sortie.
Erreur n°2 : croire que addingTimeInterval(86400) signifie « +1 jour ».
Cela signifie « +86400 secondes », et dans la vie courante cela ressemble souvent a un jour, mais ce n’est pas la meme chose qu’un jour calendaire. Aujourd’hui, nous retenons simplement la regle : addingTimeInterval concerne les secondes et les intervalles, tandis que les ajouts calendaires se font avec d’autres outils.
Erreur n°3 : confondre secondes et millisecondes dans un timestamp.
timeIntervalSince1970 renvoie des secondes. Beaucoup de systemes externes donnent des millisecondes. Si vous ne verifiez pas les unites, la date paraitra « valide », mais elle ne sera pas la ou vous l’attendiez, et ce bug est particulierement trompeur parce que le compilateur ne le detectera pas.
Erreur n°4 : forcer readLine()! et planter sur une saisie vide.
Le travail avec les dates va souvent de pair avec l’entree / sortie : l’utilisateur saisit un timestamp, une commande — n’importe quoi. Si, dans ce genre d’endroits, vous ecrivez !, vous obtenez un programme qui plante non pas a cause de « dates complexes », mais a cause d’une chaine vide. Utilisez guard let et des valeurs par defaut avec ??.
Erreur n°5 : trier les evenements « comme ca » sans fixer la regle de tri.
Quand vous avez un tableau de tuples, un tri sans regle explicite peut etre peu clair (et cela vaut aussi pour d’autres types). Bonne habitude : dans sorted { ... }, montrer explicitement que vous comparez at, et non text. Le code devient alors lisible meme pour quelqu’un qui revient un mois plus tard (y compris vous-meme).
GO TO FULL VERSION