156 votes

Comment traiter avec élégance avec fuseaux horaires

J'ai un site web qui est hébergé dans un autre fuseau horaire que les utilisateurs à l'aide de l'application. En plus de cela, les utilisateurs peuvent avoir un fuseau horaire spécifique. Je me demandais comment les autres de SORTE que les utilisateurs et les applications de cette approche? Le plus évident, c'est que, à l'intérieur de la DB, la date/temps sont stockés au format UTC. Lorsque sur le serveur, toutes les dates/heures devraient être traitées dans l'heure UTC. Cependant, je vois trois problèmes que j'essaie de surmonter:

  1. Obtenir l'heure courante au format UTC (résolu facilement avec DateTime.UtcNow).

  2. Tirant date/heure à partir de la base de données et d'afficher à l'utilisateur. Il y a potentiellement beaucoup d'appels à l'impression de dates sur les différents points de vue. Je pensais à un calque entre la vue et les contrôleurs qui pourrait résoudre ce problème. Ou d'avoir une méthode d'extension personnalisée sur DateTime (voir ci-dessous). Le principal inconvénient, c'est qu'à chaque emplacement de l'aide d'un datetime en vue, l'extension de la méthode doit être appelée!

    Cela permettrait également d'ajouter de la difficulté à l'aide de quelque chose comme l' JsonResult. Vous ne pouvez plus facilement appel Json(myEnumerable), il faudrait Json(myEnumerable.Select(transformAllDates)). Peut-être AutoMapper pourrait aider dans cette situation?

  3. L'obtention d'entrée de l'utilisateur (du Local à l'UTC). Par exemple, l'Affichage d'un formulaire avec une date d'exiger la conversion de la date au format UTC avant. La première chose qui vient à l'esprit est la création d'une coutume ModelBinder.

Voici les extensions que je pensais de l'aide dans les vues:

public static class DateTimeExtensions
{
    public static DateTime UtcToLocal(this DateTime source, 
        TimeZoneInfo localTimeZone)
    {
        return TimeZoneInfo.ConvertTimeFromUtc(source, localTimeZone);
    }

    public static DateTime LocalToUtc(this DateTime source, 
        TimeZoneInfo localTimeZone)
    {
        source = DateTime.SpecifyKind(source, DateTimeKind.Unspecified);
        return TimeZoneInfo.ConvertTimeToUtc(source, localTimeZone);
    }
}

Je pense que le fait de traiter avec les fuseaux horaires qui serait une chose commune en considérant un grand nombre d'applications sont maintenant basés sur le cloud où l'heure locale du serveur pourrait être très différent que le temps prévu de la zone.

Cela a été élégamment résolu avant? Est-il quelque chose que je suis absent? Les idées et les pensées sont beaucoup apprécié.

EDIT: Pour effacer une certaine confusion, je pensais ajouter un peu plus de détails. La question maintenant n'est pas la façon de stocker UTC fois dans la db, c'est plus sur le processus d'aller de l'UTC->Local et Local->UTC. @Max Zerbini points, c'est évidemment intelligent de mettre de l'UTC->code Local dans la vue, mais en utilisant le DateTimeExtensions vraiment la réponse? Lors de la prise d'entrée de l'utilisateur, est-il judicieux d'accepter dates que le local de l'utilisateur en temps (puisque c'est ce que JS serait de l'utiliser) et ensuite utiliser un ModelBinder pour les transformer à l'UTC? Le fuseau horaire de l'utilisateur est stocké dans la base de données et il est facile de les récupérer.

114voto

32bitkid Points 11851

Pas que c'est une recommandation, ses plus le partage d'un paradigme, mais le plus agressif , j'ai vu de la manipulation des informations de fuseau horaire dans une application web (qui n'est pas exclusif à ASP.NET MVC) est le suivant:

  • Tous date de fois sur le serveur sont en UTC. Ce qui signifie, comme vous l'avez dit, DateTime.UtcNow.

  • Essayez de ne pas faire confiance au client les dates de passage pour le serveur aussi peu que possible. Par exemple, si vous avez besoin de "maintenant", ne créez pas de date sur le client, puis de les transmettre au serveur. Créer une date dans votre OBTENIR et de transmettre à ce Dernier ou sur des POST n' DateTime.UtcNow.

Jusqu'à présent, assez standard tarif, mais c'est là où les choses deviennent intéressantes.

  • Si vous avez d'accepter une date à partir du client, puis utiliser javascript pour vous assurer que les données que vous envoyez au serveur est en UTC. Le client sait à quel fuseau horaire, de sorte qu'il peut avec une précision raisonnable convertir temps en temps UTC.

  • Lors du rendu de points de vue, ils ont été en utilisant le HTML5 <time> élément, ils ne seraient jamais rendu datetimes directement dans le ViewModel. Il a été mis en œuvre en tant que HtmlHelper extension, quelque chose comme Html.Time(Model.when). Elle rendrait <time datetime='[utctime]' data-date-format='[datetimeformat]'></time>.

    Ensuite, ils utilisent le javascript pour traduire l'heure UTC dans les clients, heure locale. Le script trouverez tous les <time> - éléments et utiliser l' date-format propriété de données pour le format de la date et de remplir le contenu de l'élément.

De cette façon, ils n'ont jamais eu à suivre, stocker, gérer les clients de fuseau horaire. Le serveur n'a pas de soins de quel fuseau horaire du client, ni eu à faire n'importe quel fuseau horaire traductions. Il a simplement cracher UTC et laissez le client de les convertir en quelque chose de raisonnable. Ce qui est facile à partir du navigateur, car il sait quel fuseau horaire. Si le client a changé de fuseau horaire, l'application web serait automatiquement mise à jour elle-même. La seule chose qu'ils ont été stockées au format datetime chaîne pour les paramètres régionaux de l'utilisateur.

Je ne dis pas que c'est la meilleure approche, mais c'était un autre que je n'avais pas vu avant. Vous verrez peut-être glaner quelques idées intéressantes.

18voto

Nestor Points 872

Après plusieurs retours, voici ma solution finale, je pense que c'est propre et simple, et couvre l'heure d'été à des questions.

1 - Nous prenons en charge la conversion au niveau du modèle. Ainsi, dans la classe du Modèle, nous écrivons:

    public class Quote
    {
        ...
        public DateTime DateCreated
        {
            get { return CRM.Global.ToLocalTime(_DateCreated); }
            set { _DateCreated = value.ToUniversalTime(); }
        }
        private DateTime _DateCreated { get; set; }
        ...
    }

2 - Dans une assistance global que nous faisons de notre fonction personnalisée "ToLocalTime":

    public static DateTime ToLocalTime(DateTime utcDate)
    {
        var localTimeZoneId = "China Standard Time";
        var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById(localTimeZoneId);
        var localTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, localTimeZone);
        return localTime;
    }

3 - Nous pouvons améliorer cette autre, par la sauvegarde de l'id de fuseau horaire dans chaque profil d'Utilisateur afin que nous puissions récupérer à partir de la classe d'utilisateur au lieu d'utiliser la constante "de la Chine Standard Time":

public class Contact
{
    ...
    public string TimeZone { get; set; }
    ...
}

4 - Ici, nous pouvons obtenir la liste de fuseau horaire pour montrer à l'utilisateur de sélectionner à partir d'un dropdownbox:

public class ListHelper
{
    public IEnumerable<SelectListItem> GetTimeZoneList()
    {
        var list = from tz in TimeZoneInfo.GetSystemTimeZones()
                   select new SelectListItem { Value = tz.Id, Text = tz.DisplayName };

        return list;
    }
}

Donc, aujourd'hui à 9:25 AM en Chine, le Site est hébergé aux etats-unis, la date enregistrée en heure UTC à la base de données, voici le résultat final:

5/9/2013 6:25:58 PM (Server - in USA) 
5/10/2013 1:25:58 AM (Database - Converted UTC)
5/10/2013 9:25:58 AM (Local - in China)

MODIFIER

Merci à Matt Johnson pour souligner les parties faibles de la solution originale, et désolé pour la suppression de post original, mais a eu des problèmes à obtenir droit de l'affichage du code de format... s'est avéré que l'éditeur a des problèmes de mélange "balles" avec "pré-code", j'ai donc enlevé les bulles et c'était ok.

9voto

casperOne Points 49736

Dans la section des événements sur sf4answers, aux utilisateurs de saisir une adresse pour un événement, ainsi qu'une date de début et éventuellement une date de fin. Ces temps sont traduits en datetimeoffset dans SQL server qui représente le décalage par rapport à UTC.

C'est le même problème auquel vous êtes confronté (même si vous prenez une approche différente, en ce que vous utilisez DateTime.UtcNow); vous disposez d'un emplacement et vous avez besoin de traduire une heure d'un fuseau horaire à un autre.

Il y a deux choses principales que j'ai fait qui a travaillé pour moi. Tout d'abord, utilisez DateTimeOffset structure, toujours. Il représente décalage de l'heure UTC et si vous pouvez obtenir ces renseignements auprès de votre client, il vous rend la vie un peu plus facile.

Deuxièmement, lors de la réalisation de traductions, en supposant que vous connaissez l'emplacement/zone de temps que le client est dans l', vous pouvez utiliser l' information de fuseau horaire de base de données pour traduire un temps UTC à un autre fuseau horaire (ou trianguler, si vous voulez, entre les deux zones). La grande chose au sujet de la tz de la base de données (parfois appelé le Olson base de données), c'est qu'il explique les changements de fuseaux horaires tout au long de l'histoire; à obtenir un décalage est fonction de la date que vous souhaitez pour obtenir le décalage sur (il suffit de regarder l' Energy policy Act de 2005 qui a changé les dates de changement d'heure, va en effet dans le NOUS).

Avec la base de données dans la main, vous pouvez utiliser le ZoneInfo (tz base de données / Olson base de données) .NET DE L'API. Notez qu'il n'y a pas une distribution binaire, vous devez télécharger la dernière version et le compiler vous-même.

Au moment d'écrire ces lignes, il analyse tous les fichiers dans les données les plus récentes distrubition (en fait, je couru à l'encontre de la ftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gz fichier le 25 septembre 2011).

Donc sur sf4answers, après l'obtention de l'adresse, il est géocodé dans une latitude/longitude combinaison et ensuite envoyé à un service web tiers pour obtenir un fuseau horaire qui correspond à une entrée dans la base de données tz. À partir de là, le début et la fin sont convertis en DateTimeOffset des cas avec le bon décalage UTC, puis stockées dans la base de données.

Comme pour traiter avec elle sur de SI et de sites web, cela dépend du public et de ce que vous essayez d'affichage. Si vous remarquez, la plupart des sites de réseaux sociaux (et, DONC, et la section des événements sur sf4answers) affiche une relative du temps, ou, si la valeur absolue est utilisée, il est généralement à l'UTC.

Toutefois, si votre public attend de l'heure locale, puis à l'aide de DateTimeOffset avec une méthode d'extension qui prend le fuseau horaire pour convertir serait juste l'amende; le type de données SQL datetimeoffset serait traduit la .NET DateTimeOffset que vous pouvez ensuite obtenir le temps universel pour l'utilisation de l' GetUniversalTime méthode. À partir de là, il vous suffit d'utiliser les méthodes sur l' ZoneInfo classe de convertir de l'heure UTC en heure locale (vous aurez à faire un peu de travail pour arriver à une DateTimeOffset, mais il est assez simple à faire).

Où faire de la transformation? C'est un coût que vous allez avoir à payer quelque part, et il n'y a pas de "meilleur". Je préfère la vue, avec le décalage horaire dans le cadre de la vue modèle présenté à la vue. De cette façon, si les conditions requises pour la modification de la vue, vous n'avez pas à changer votre point de vue modèle pour tenir compte du changement. Votre JsonResult serait tout simplement contenir un modèle à l' IEnumerable<T> et le décalage.

Sur le côté de l'entrée, à l'aide d'un modèle de classeur? Je dirais absolument aucun moyen. Vous ne pouvez pas garantir que toutes les dates (maintenant ou dans l'avenir) devra être transformé de cette façon, il devrait être une fonction explicite de votre contrôleur pour effectuer cette action. Encore une fois, si les besoins changent, vous n'avez pas à ajuster un ou plusieurs ModelBinder instances pour régler votre logique métier; et il est logique d'entreprise, ce qui signifie qu'il devrait être dans le contrôleur.

6voto

Max Zerbini Points 1731

C'est juste mon avis, je pense que l'application MVC convient de séparer les données de puits problème de présentation du modèle de données de gestion. Une base de données peut stocker des données dans le serveur local de temps, mais c'est un devoir de la couche de présentation pour rendre datetime utilisation de locaux de fuseau horaire. Ce qui me semble être le même problème que I18N et le format de nombre pour les différents pays. Dans votre cas, votre demande doit détecter l' Culture et le fuseau horaire de l'utilisateur et de modifier la Vue montrant un autre texte, nombre et datime présentation, mais les données stockées peuvent avoir le même format.

1voto

link664 Points 3536

Pour la sortie, créer un affichage/de l'éditeur de template comme ceci

@inherits System.Web.Mvc.WebViewPage<System.DateTime>
@Html.Label(Model.ToLocalTime().ToLongTimeString()))

Vous pouvez les lier basée sur les attributs du modèle si vous ne souhaitez que certains modèles à l'utilisation de ces modèles.

Voir ici et ici pour plus de détails sur la création personnalisée de l'éditeur de modèles.

Sinon, puisque vous voulez qu'il fonctionne à la fois comme entrée et de sortie, je vous suggère d'étendre un contrôle ou même de créer votre propre. De cette façon, vous pouvez intercepter à la fois les entrées et les sorties et de convertir le texte/valeur en tant que de besoin.

Ce lien sera, nous l'espérons vous pousser dans la bonne direction si vous voulez aller dans cette voie.

De toute façon, si vous voulez une solution élégante, sa va être un peu de travail. Sur le côté positif, une fois que vous avez fait une fois, vous pouvez le garder dans votre bibliothèque de code pour une utilisation future!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X