Quelques bonnes pratiques sur la manipulation des dates dans vos applications
Cela fait quelques temps que je me pose la question de l’écriture d’un post comme celui-ci et de plus en plus je me dis qu’il pourra être utile à pas mal de gens, étant donné les problématiques que je rencontre assez souvent dans les diverses missions que je fais au quotidien. Dans ce post, je vais tenter de vous donner un certain nombre de pistes / bonnes pratiques pour résoudre certaines problématiques en rapport avec la gestion des dates dans vos applications.
La première question qu’il faut se poser lorsque l’on travaille avec une date, c’est comment celle-ci doit être stockée (en base de données ou ailleurs) pour être re-exploitée par la suite. Le premier conseil que je donnerai, est de toujours stocker les dates dans un format UTC, qui comme son nom l’indique représente le temps universel ! Si jamais votre code est à destination d’Azure, la question ne se pose même pas, les serveurs sont configurés pour tourner en UTC !
Quelques rappels basiques
Pour récupérer la date UTC à un instant t en C#, on utilise :
DateTime utcNow = DateTime.UtcNow;
Il arrive assez souvent que l’on souhaite connaitre le type de la date que l’on manipule et surtout la time zone dans laquelle celle-ci a été créée. Par exemple, si l’on fait appelle à l’instruction DateTime.Now, la time zone qui sera utilisée sera celle correspondant à la région configurée sur la machine qui exécute le code, alors que si l’on utilise DateTime.UtcNow, la time zone sera celle de l’UTC (+ 00:00:00).
La structure DateTime expose une propriété Kind, qui permet dans un premier temps de savoir si la date correspond à une date Utc ou une date Local :
class Program { static void Main(string[] args) { DateTime utcNow = DateTime.UtcNow; Console.WriteLine(utcNow.Kind); DateTime now = DateTime.Now; Console.WriteLine(now.Kind); Console.ReadLine(); } }
Voilà le résultat de l’application console ci-dessus :
Si l’on souhaite obtenir plus d’information sur la date, il est possible de faire appelle à la structure DateTimeOffset, qui peut d’ailleurs prendre en paramètre une date et qui sera capable de vous indiquer 3 informations importantes :
- LocalDateTime : la date pour la time zone de l’ordinateur qui exécute le code
- UtcDateTime : la date pour la time zone UTC
- Offset : le décalage horaire
class Program { static void Main(string[] args) { Console.WriteLine("## UTC NOW ##"); DateTime utcNow = DateTime.UtcNow; DateTimeOffset utcNowOffset = new DateTimeOffset(utcNow); Console.WriteLine("Local Time : {0}", utcNowOffset.LocalDateTime); Console.WriteLine("Utc Time : {0}", utcNowOffset.UtcDateTime); Console.WriteLine("Offset : {0}", utcNowOffset.Offset); Console.WriteLine(); Console.WriteLine("## NOW ##"); DateTime now = DateTime.Now; DateTimeOffset nowOffset = new DateTimeOffset(now); Console.WriteLine("Local Time : {0}", nowOffset.LocalDateTime); Console.WriteLine("Utc Time : {0}", nowOffset.UtcDateTime); Console.WriteLine("Offset : {0}", nowOffset.Offset); Console.ReadLine(); } }
Par exemple en France en ce moment, il y a deux heures de décalage avec le temps UTC, on obtient donc l’affichage suivant :
Travailler avec les time zones
Aujourd’hui, il est de plus en plus fréquent d’avoir des utilisateurs dans différentes régions du monde (via les applications web, les applications mobiles diffusées sur les stores etc…) rendant la bonne gestion des dates encore plus importante ! Il n’y a rien de plus pénible pour un utilisateur d’avoir une date faussée tout simplement parce qu’il n’est pas sur le même fuseau horaire que le développeur. D’où l’importance de toujours travailler avec des dates en UTC !
Le .NET Framework fourni un ensemble de classes et structures qui permettent de passer une date d’une time zone à l’autre. La première est la classe TimeZone.
class Program { static void Main(string[] args) { TimeZone timeZone = TimeZone.CurrentTimeZone; DateTime localTime = timeZone.ToLocalTime(DateTime.Now); DateTime utcTime = timeZone.ToUniversalTime(DateTime.Now); Console.WriteLine("Time Zone : {0}", timeZone.StandardName); Console.WriteLine("Local time : {0}", localTime.ToString("o")); Console.WriteLine("Utc time : {0}", utcTime.ToString("o")); Console.ReadLine(); } }
Voilà l’affichage que l’on obtient :
La classe TimeZoneInfo, permet quand à elle de récupérer des informations sur toutes les time zones disponibles au niveau système mais également d’effectuer des opérations de conversions de time zone entre dates.
foreach (TimeZoneInfo info in TimeZoneInfo.GetSystemTimeZones()) { Console.WriteLine("### Time Zone : {0} ###", info.StandardName); Console.WriteLine("Base utc offset : {0}", info.BaseUtcOffset); Console.WriteLine("############################################"); Console.WriteLine(); }
Pour effectuer des conversions de time zone entre date, des méthodes statiques sont à disposition sur la classe TimeZoneInfo :
- ConvertTimeFromUtc : permet de convertir une date UTC dans une time zone donnée
- ConvertTimeToUtc : permet de convertir une date dans une time zone donnée vers une date UTC
- ConvertTime : permet de convertir une date entre deux time zones
Par exemple, pour convertir une date UTC en pacific time (Seattle) on pourra utiliser le code suivant :
class Program { static void Main(string[] args) { var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); DateTime seattleTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, timeZone); Console.ReadLine(); } }
Heure d’été, heure d’hiver : comment savoir ?
La notion d’heure d’été et d’heure d’hiver est courante dans certains pays, dont la France. Comme cette notion n’est pas universelle, il est possible de tester n’importe quelle date à partir de sa time zone, pour savoir si on est en heure d’hiver ou en heure d’été (daylight saving time) :
class Program { static void Main(string[] args) { TimeZoneInfo currentZone = TimeZoneInfo.Local; DateTime dt1 = new DateTime(2013, 02, 02); bool dt1IsDaylightSavingTime = currentZone.IsDaylightSavingTime(dt1); DateTime dt2 = new DateTime(2013, 09, 02); bool dt2IsDaylightSavingTime = currentZone.IsDaylightSavingTime(dt2); } }
Dans l’exemple ci-dessus, dt1IsDaylightSavingTime est faux (février = heure d’hiver) alors que dt2IsDaylightSavingTime est vrai (septembre = heure d’été).
Conclusion
Dans cet article j’ai tenté de démontrer les différents usages et manipulations des dates qui reviennent assez souvent sur les différents projets sur lesquels j’ai l’occasion de travailler. Si vous avez d’autres astuces à partager, n’hésitez pas à le faire dans les commentaires
A bientôt !
Julien
Commentaires