C# : DateOnly et TimeOnly
Les types dédiés à une date seule ou une heure seule (.NET 6+) : pourquoi ils existent, et comment les manipuler.
C# : DateOnly et TimeOnly
Depuis .NET 6, deux types répondent à un vrai manque : représenter une date sans heure ou une heure sans date. Avant, on bricolait avec un DateTime à minuit, ce qui traînait une heure et un fuseau parasites, source de bugs.
DateOnly: une date pure (anniversaire, échéance, date de facture).TimeOnly: une heure d'horloge pure (ouverture d'un magasin, alarme).
Aucun des deux n'a de Kind ni de notion de fuseau : ils sont non ambigus par construction.
# DateOnly
DateOnly date = new DateOnly(2026, 9, 1);
DateOnly aujourdhui = DateOnly.FromDateTime(DateTime.Now); // on jette l'heure
int annee = date.Year; // 2026
DayOfWeek jour = date.DayOfWeek; // DayOfWeek.Tuesday
int jourAnnee = date.DayOfYear; // 244
int numero = date.DayNumber; // nombre de jours depuis le 0001-01-01DateOnly date = new DateOnly(2026, 9, 1);DateOnly aujourdhui = DateOnly.FromDateTime(DateTime.Now); // on jette l'heure int annee = date.Year; // 2026DayOfWeek jour = date.DayOfWeek; // DayOfWeek.Tuesdayint jourAnnee = date.DayOfYear; // 244int numero = date.DayNumber; // nombre de jours depuis le 0001-01-01L'arithmétique se fait en jours, mois ou années, et retourne une nouvelle valeur :
DateOnly echeance = date.AddDays(30);
DateOnly anniversaireProchain = date.AddYears(1);
int joursRestants = echeance.DayNumber - aujourdhui.DayNumber; // un écart simple, en joursDateOnly echeance = date.AddDays(30);DateOnly anniversaireProchain = date.AddYears(1); int joursRestants = echeance.DayNumber - aujourdhui.DayNumber; // un écart simple, en joursLa comparaison utilise les opérateurs habituels :
if (echeance < aujourdhui) { /* en retard */ }if (echeance < aujourdhui) { /* en retard */ }# TimeOnly
TimeOnly ouverture = new TimeOnly(9, 0); // 09:00
TimeOnly fermeture = new TimeOnly(18, 30); // 18:30
TimeOnly maintenant = TimeOnly.FromDateTime(DateTime.Now);
int heure = ouverture.Hour; // 9
int minute = ouverture.Minute; // 0TimeOnly ouverture = new TimeOnly(9, 0); // 09:00TimeOnly fermeture = new TimeOnly(18, 30); // 18:30TimeOnly maintenant = TimeOnly.FromDateTime(DateTime.Now); int heure = ouverture.Hour; // 9int minute = ouverture.Minute; // 0Particularité : l'heure tourne sur 24 h. Ajouter du temps qui dépasse minuit repart de zéro :
TimeOnly tard = new TimeOnly(23, 30);
TimeOnly apres = tard.AddHours(2); // 01:30, pas 25:30TimeOnly tard = new TimeOnly(23, 30);TimeOnly apres = tard.AddHours(2); // 01:30, pas 25:30IsBetween gère même les plages qui passent par minuit :
bool ouvert = maintenant.IsBetween(ouverture, fermeture);
TimeOnly debutNuit = new TimeOnly(22, 0);
TimeOnly finNuit = new TimeOnly(6, 0);
bool estLaNuit = maintenant.IsBetween(debutNuit, finNuit); // vrai à 23h comme à 3hbool ouvert = maintenant.IsBetween(ouverture, fermeture); TimeOnly debutNuit = new TimeOnly(22, 0);TimeOnly finNuit = new TimeOnly(6, 0);bool estLaNuit = maintenant.IsBetween(debutNuit, finNuit); // vrai à 23h comme à 3h# Combiner les deux
On reconstitue un DateTime complet en associant une date et une heure :
DateOnly date = new DateOnly(2026, 9, 1);
TimeOnly heure = new TimeOnly(14, 30);
DateTime rendezVous = date.ToDateTime(heure); // 2026-09-01 14:30:00DateOnly date = new DateOnly(2026, 9, 1);TimeOnly heure = new TimeOnly(14, 30); DateTime rendezVous = date.ToDateTime(heure); // 2026-09-01 14:30:00Et inversement, on décompose un DateTime :
DateTime dt = DateTime.Now;
DateOnly d = DateOnly.FromDateTime(dt);
TimeOnly t = TimeOnly.FromDateTime(dt);DateTime dt = DateTime.Now;DateOnly d = DateOnly.FromDateTime(dt);TimeOnly t = TimeOnly.FromDateTime(dt);# Formater et parser
Mêmes symboles que DateTime, mais limités à la partie pertinente :
date.ToString("yyyy-MM-dd"); // "2026-09-01"
heure.ToString("HH:mm"); // "14:30"
DateOnly.Parse("2026-09-01");
TimeOnly.Parse("14:30");
// Robuste pour une entrée externe
if (DateOnly.TryParse(saisie, out DateOnly resultat)) { /* ... */ }date.ToString("yyyy-MM-dd"); // "2026-09-01"heure.ToString("HH:mm"); // "14:30" DateOnly.Parse("2026-09-01");TimeOnly.Parse("14:30"); // Robuste pour une entrée externeif (DateOnly.TryParse(saisie, out DateOnly resultat)) { /* ... */ } Choisir DateOnly pour une date de naissance ou TimeOnly pour un horaire, c'est rendre l'intention explicite dans le type : le compilateur t'empêche d'y coller une heure ou un fuseau qui n'ont rien à y faire.
# La suite
- Dates et temps - le hub : quel type pour quel besoin
- DateTime en profondeur - le type complet date + heure
- TimeSpan - les durées et les écarts