Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

La question des fuseaux horaires

Poster un commentaire

Ceux qui travaillent sous Azure savent que les serveurs sont paramétré en UTC. Mais il est fréquent qu’une application Web doivent servir des clients de fuseaux horaires variés.

Comment gérer les choses au mieux sans s’emmeler dans les dates.

La stratégie que je recommande est assez simple:

Choisir un fuseau de référence et convertir toutes les dates et heures dans ce fuseau.

Le plus simple est donc de choisir UTC.

D’un point de vue pratique la règle est la suivante:

  • dès que la date est sur la page Web (saisie ou écriture) elle est par convention dans le fuseau horaire du client.
  • Dès que la date arrive côté serveur elle DOIT être en UTC.

Si vous suivez avec rigueur ces deux règles, il n’y a aucun risque de comparer une date UTC avec une date dans un autre fuseau ou des choses de ce genre.

Le besoin maintenant est donc:

  1. Obtenir le fuseau horaire du client
  2.  Obtenir des dates en UTC
  3. Faire des conversions entre UTC et un autre fuseau.

Le premier point démarre mal. Il n’y a pas (à ma connaissance) de moyen fiable de récupérer cette information depuis une requête Http reçue. Il faudra faire des hypothèses du genre: c’est le site Web pour la France, donc on utilise le fuseau horaire français. Ne perdez donc pas de temps à analyser les heders Http, les localisations d’adresse IP… c’est pas 100% fiable et quand bien même, comment repérez vous un australien, en visite en France qui se connecte certes sur un site australien, mais préfère avoir l’heure locale?

Dans le cas où le pays s’étend sur plusiers fuseaux (11 en Russie) une difficulté se présente. L’usage est de choisir un fuseau dit de référence, souvent celui de la capitale.

On peut aussi permettre au client de choisir lui-même son profil ce qui règle la question.

Le deuxième point est moins problématique.

Je pense qu’il ne faut pas s’embêter à faire des conversions de date ou des générations de dates dans SQL. Mieux vaut un environnement unifié: C#. En plus c’est plus facile.

Pour obtenir la date courante en UTC vous disposez de: DateTime.UtcNow

Reste la dernière question: passage d’un format de date à un autre.  Là aussi C# est équipé, mais il y a tout de même un peu de travail.

C’est pourquoi pour un projet j’ai décidé de créer une classe qui fait tout le boulot. La voici. Ca fait au moins un an qu’on la teste tous les jours, à priori il n’y a pas de bugs!!!

Je vais pas faire de gros commentaires, il y a l’aide en ligne et franchement rien de compliqué là dedans.

Tout d’abord le blog qui m’a aidé:

http://blog.dezfowler.com/2010/07/utc-gotchas-in-net-and-sql-server.html

 

Et le code. La classe au coeur de ce code est TimeZoneInfo. Cette classe est responsable de la prise en charge des fuseaux horaires dans .Net.

Note: on peu également récupérer ce code dans mon espace Github:

https://github.com/DeLeneMirouze/TimeHelper

 

/// <summary>
///Utilitaire conversion de date
///</summary>
public static class TimeHelper
{
    #region FromUtcToLocal
    ///<summary>
    /// Conversion d'une date UTC vers une date locale
    ///</summary>
    ///<param name="timeZone">Id du fuseau horaire de la date</param>
    ///<param name="dateTimeUtc">Date UTC</param>
    ///<returns></returns>
    public static DateTime FromUtcToLocal(string timeZone, DateTime dateTimeUtc)
    {
        if (dateTimeUtc.Kind == DateTimeKind.Unspecified)
        {
            dateTimeUtc = DateTime.SpecifyKind(dateTimeUtc, DateTimeKind.Utc);
        }

        var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
        var date = TimeZoneInfo.ConvertTimeFromUtc(dateTimeUtc, timeZoneInfo);

        return date;
    }
    #endregion

    #region FromLocalToUtc
    ///<summary>
    /// Conversion d'une date locale vers une date UTC
    ///</summary>
    ///<param name="timeZone">Id du fuseau horaire de la date</param>
    ///<param name="dateTimeLocale">Date locale</param>
    ///<returns>date UTC</returns>
    public static DateTime FromLocalToUtc(string timeZone, DateTime dateTimeLocale)
    {
        // la date locale peut ne pas être la même date que la zone system
        dateTimeLocale = DateTime.SpecifyKind(dateTimeLocale, DateTimeKind.Unspecified);

        var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
        var date = TimeZoneInfo.ConvertTimeToUtc(dateTimeLocale, timeZoneInfo);

        return date;
    }
    #endregion

    #region GetLocalNow
    ///<summary>
    /// Obtient la date en cours (côté serveur) exprimée en date locale du fuseau horaire du groupe fournit, donc destinée à être affichée
    ///</summary>
    ///<param name="timeZone">Id du fuseau horaire de la date</param>
    ///<returns>Date dans le format local pour affichage</returns>
    public static DateTime GetLocalNow(string timeZone)
    {
        DateTime dateTime = DateTime.UtcNow;

        return FromUtcToLocal(timeZone, dateTime);
    }
    #endregion

    #region ToIso8601
    ///<summary>
    /// Sérialise le <see cref="DateTime"/> fournit en ISO 8601
    ///</summary>
    ///<param name="date">DateTime à sérialiser</param>
    ///<returns>DateTime en ISO 8601</returns>
    public static string ToIso8601(DateTime date)
    {
        // pour en savoir plus:
        // http://fr.wikipedia.org/wiki/ISO_8601
        // ex: 2012-03-28T12:08:16.2277876+02:00
        return date.ToString(Iso8601Format);
    }
    #endregion

    #region GetOffsetString
    ///<summary>
    /// Obtient sous une forme formatée l'offset de fuseau horaire entre la date UTC et la date fournie
    ///</summary>
    ///<param name="date">Date fournie</param>
    ///<param name="timeZone">Id du fuseau horaire de la date</param>
    ///<returns></returns>
    public static string GetOffsetString(DateTime date, string timeZone)
    {
        // pas trouvé de moyen plus simple

        TimeZoneInfo currentTimeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
        var offset = currentTimeZone.GetUtcOffset(date);
        var formated = string.Format("{0}:{1}", offset.Hours, offset.Minutes);
        if (offset < TimeSpan.Zero)
        {
            formated = string.Concat("-", formated);
        }
        else
        {
            formated = string.Concat("+", formated);
        }

        return formated;
    }
    #endregion

    #region GetOffset
    ///<summary>
    /// Obtient sous une forme formatée l'offset de fuseau horaire entre la date UTC et la date fournie
    ///</summary>
    ///<param name="date">Date fournie</param>
    ///<param name="timeZone">Id du fuseau horaire de la date</param>
    ///<returns></returns>
    public static int GetOffset(DateTime date, string timeZone)
    {
        // pas trouvé de moyen plus simple
        TimeZoneInfo currentTimeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZone);

        var offset = currentTimeZone.GetUtcOffset(date);

        return offset.Hours;
    }
    #endregion

    ///<summary>
    /// Format ISO 8601 utilisé
    ///</summary>
    public const string Iso8601Format = @"yyyy-MM-ddTHH\:mm\:ss.fffffffzzz";
}
Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s