J'ai un site Web hébergé dans un fuseau horaire différent de celui des utilisateurs utilisant l'application. En plus de cela, les utilisateurs peuvent avoir un fuseau horaire spécifique. Je me demandais comment les autres utilisateurs et applications SO abordent cela? La partie la plus évidente est qu'à l'intérieur de la base de données, la date / l'heure sont stockées en UTC. Sur le serveur, toutes les dates / heures doivent être traitées en UTC. Cependant, je vois trois problèmes que j'essaie de surmonter:
Obtenir l'heure actuelle en UTC (résolu facilement avec
DateTime.UtcNow
).Extraire les dates / heures de la base de données et les afficher à l'utilisateur. Il y a potentiellement beaucoup d'appels pour imprimer des dates sur différentes vues. Je pensais à une couche entre la vue et les contrôleurs qui pourraient résoudre ce problème. Ou avoir une méthode d'extension personnalisée
DateTime
(voir ci-dessous). L'inconvénient majeur est qu'à chaque emplacement d'utilisation d'un datetime dans une vue, la méthode d'extension doit être appelée!Cela ajouterait également de la difficulté à utiliser quelque chose comme le
JsonResult
. Vous ne pourriez plus appeler facilementJson(myEnumerable)
, il faudrait que ce soit le casJson(myEnumerable.Select(transformAllDates))
. Peut-être qu'AutoMapper pourrait vous aider dans cette situation?Obtention de l'entrée de l'utilisateur (local à UTC). Par exemple, publier un formulaire avec une date nécessiterait de convertir la date en UTC avant. La première chose qui me vient à l'esprit est de créer une coutume
ModelBinder
.
Voici les extensions que j'ai pensé utiliser 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 gérer les fuseaux horaires serait une chose si courante étant donné que de nombreuses applications sont désormais basées sur le cloud, où l'heure locale du serveur pourrait être très différente du fuseau horaire prévu.
Cela a-t-il été résolu avec élégance auparavant? Y a-t-il quelque chose qui me manque? Les idées et les pensées sont très appréciées.
EDIT: Pour dissiper une certaine confusion, j'ai pensé ajouter quelques détails supplémentaires. Le problème à l'heure actuelle n'est pas de savoir comment stocker les heures UTC dans la base de données, mais plutôt le processus de passage de UTC-> Local et Local-> UTC. Comme le souligne @Max Zerbini, il est évidemment judicieux de mettre le code UTC-> Local dans la vue, mais est-ce DateTimeExtensions
vraiment la réponse? Lors de l'obtention des entrées de l'utilisateur, est-il judicieux d'accepter les dates comme heure locale de l'utilisateur (puisque c'est ce que JS utiliserait), puis d'utiliser a ModelBinder
pour transformer en UTC? Le fuseau horaire de l'utilisateur est stocké dans la base de données et est facilement récupéré.
la source
ModelBinder
.Réponses:
Non pas que ce soit une recommandation, il partage plus un paradigme, mais la manière la plus agressive que j'ai vue de gérer les informations de fuseau horaire dans une application Web (qui n'est pas exclusive à ASP.NET MVC) était la suivante:
Toutes les dates et heures sur le serveur sont en UTC. Cela signifie utiliser, comme vous l'avez dit
DateTime.UtcNow
,.Essayez de faire le moins possible confiance au client qui transmet les dates au serveur. Par exemple, si vous avez besoin de "maintenant", ne créez pas de date sur le client et ne le transmettez pas au serveur. Créez une date dans votre GET et transmettez-la au ViewModel ou au POST do
DateTime.UtcNow
.Jusqu'à présent, un tarif assez standard, mais c'est là que les choses deviennent «intéressantes».
Si vous devez accepter une date du client, utilisez javascript pour vous assurer que les données que vous publiez sur le serveur sont en UTC. Le client sait dans quel fuseau horaire il se trouve, il peut donc, avec une précision raisonnable, convertir les heures en UTC.
Lors du rendu des vues, ils utilisaient l'
<time>
élément HTML5 , ils ne rendraient jamais les datetimes directement dans le ViewModel. Il a été implémenté comme uneHtmlHelper
extension, quelque chose commeHtml.Time(Model.when)
. Cela rendrait<time datetime='[utctime]' data-date-format='[datetimeformat]'></time>
.Ensuite, ils utiliseraient javascript pour traduire l'heure UTC en heure locale du client. Le script trouverait tous les
<time>
éléments et utiliserait ladate-format
propriété data pour formater la date et remplir le contenu de l'élément.De cette façon, ils n'ont jamais eu à suivre, stocker ou gérer le fuseau horaire d'un client. Le serveur ne se souciait pas du fuseau horaire dans lequel se trouvait le client et n'avait pas à effectuer de traductions de fuseau horaire. Il crache simplement UTC et laisse le client le convertir en quelque chose de raisonnable. Ce qui est facile à partir du navigateur, car il sait dans quel fuseau horaire il se trouve. Si le client modifiait son fuseau horaire, l'application Web se mettrait automatiquement à jour. La seule chose qu'ils stockaient était la chaîne de format datetime pour les paramètres régionaux de l'utilisateur.
Je ne dis pas que c'était la meilleure approche, mais c'était une approche différente que je n'avais jamais vue auparavant. Peut-être que vous en tirerez des idées intéressantes.
la source
<time>
idée est plutôt cool. Le seul inconvénient est que cela signifie que je dois interroger l'ensemble du DOM pour ces éléments, ce qui n'est pas vraiment sympa juste de transformer les dates (qu'en est-il de JS désactivé?). Merci encore!Après plusieurs retours, voici ma solution finale que je trouve propre et simple et qui couvre les problèmes d'heure d'été.
1 - Nous gérons la conversion au niveau du modèle. Donc, dans la classe Model, nous écrivons:
2 - Dans un helper global nous créons notre fonction personnalisée "ToLocalTime":
3 - Nous pouvons encore améliorer cela, en enregistrant l'identifiant du fuseau horaire dans chaque profil utilisateur afin que nous puissions récupérer de la classe d'utilisateurs au lieu d'utiliser la constante "Heure standard de Chine":
4 - Ici, nous pouvons obtenir la liste des fuseaux horaires à montrer à l'utilisateur pour sélectionner dans une liste déroulante:
Donc, maintenant à 9h25 en Chine, site Web hébergé aux USA, date enregistrée en UTC dans la base de données, voici le résultat final:
ÉDITER
Merci à Matt Johnson d' avoir souligné les points faibles de la solution originale, et désolé d'avoir supprimé le message original, mais j'ai eu des problèmes pour obtenir le bon format d'affichage du code ... il s'est avéré que l'éditeur a des problèmes pour mélanger les "puces" avec le "pré-code", alors je enlevé les bulles et c'était ok.
la source
Dans la section événements sur sf4answers , les utilisateurs saisissent une adresse pour un événement, ainsi qu'une date de début et une date de fin facultative. Ces heures sont traduites en un
datetimeoffset
serveur SQL qui tient compte du décalage par rapport à UTC.C'est le même problème que vous rencontrez (bien que vous adoptiez une approche différente, en ce que vous l'utilisez
DateTime.UtcNow
); vous avez un emplacement et vous devez traduire une heure d'un fuseau horaire à un autre.Il y a deux choses principales que j'ai faites qui ont fonctionné pour moi. Tout d'abord, utilisez toujours la
DateTimeOffset
structure . Cela représente un décalage par rapport à UTC et si vous pouvez obtenir ces informations de votre client, cela vous facilite un peu la vie.Deuxièmement, lorsque vous effectuez les traductions, en supposant que vous connaissez l'emplacement / le fuseau horaire dans lequel se trouve le client, vous pouvez utiliser la base de données de fuseaux horaires d'informations publiques pour traduire une heure UTC vers un autre fuseau horaire (ou trianguler, si vous voulez, entre deux fuseaux horaires). L'avantage de la base de données tz (parfois appelée base de données Olson ) est qu'elle tient compte des changements de fuseaux horaires au cours de l'histoire; obtenir un décalage est fonction de la date à laquelle vous souhaitez obtenir le décalage (il suffit de regarder la loi de 2005 sur la politique énergétique qui a modifié les dates d'entrée en vigueur de l'heure d'été aux États-Unis ).
Avec la base de données en main, vous pouvez utiliser l' API .NET ZoneInfo (base de données tz / base de données Olson) .NET . Notez qu'il n'y a pas de distribution binaire, vous devrez télécharger la dernière version et la compiler vous-même.
Au moment d'écrire ces lignes, il analyse actuellement tous les fichiers de la dernière distribution de données (je l'ai en fait exécuté avec le fichier ftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gz le 25 septembre 2011; en mars 2017, vous l'obteniez via https://iana.org/time-zones ou sur ftp://fpt.iana.org/tz/releases/tzdata2017a.tar.gz ).
Ainsi, sur sf4answers, après avoir obtenu l'adresse, elle est géocodée dans une combinaison latitude / longitude, puis envoyée à un service Web tiers pour obtenir un fuseau horaire qui correspond à une entrée dans la base de données tz. À partir de là, les heures de début et de fin sont converties en
DateTimeOffset
instances avec le décalage UTC approprié, puis stockées dans la base de données.Quant à la traiter sur SO et sur les sites Web, cela dépend de l'audience et de ce que vous essayez d'afficher. Si vous remarquez, la plupart des sites Web sociaux (et SO, et la section des événements sur sf4answers) affichent les événements en temps relatif , ou, si une valeur absolue est utilisée, c'est généralement UTC.
Cependant, si votre public s'attend à des heures locales, utiliser
DateTimeOffset
avec une méthode d'extension qui prend le fuseau horaire à convertir serait très bien; le type de données SQLdatetimeoffset
se traduirait par le .NETDateTimeOffset
que vous pouvez alors obtenir le temps universel pour utiliser laGetUniversalTime
méthode . À partir de là, vous utilisez simplement les méthodes de laZoneInfo
classe pour convertir l'heure UTC en heure locale (vous devrez faire un peu de travail pour la mettre en aDateTimeOffset
, mais c'est assez simple à faire).Où faire la transformation? C'est un coût que vous allez devoir payer quelque part , et il n'y a pas de «meilleur» moyen. J'opterais cependant pour la vue, avec le décalage du fuseau horaire dans le cadre du modèle de vue présenté à la vue. De cette façon, si les exigences de la vue changent, vous n'avez pas à modifier votre modèle de vue pour s'adapter à la modification. Votre
JsonResult
contiendrait simplement un modèle avec le et le décalage.IEnumerable<T>
Du côté de l'entrée, en utilisant un classeur de modèle? Je dirais absolument aucun moyen. Vous ne pouvez pas garantir que toutes les dates (actuelles ou futures) devront être transformées de cette manière, cela devrait être une fonction explicite de votre contrôleur pour effectuer cette action. Encore une fois, si les exigences changent, vous n'avez pas à modifier une ou plusieurs
ModelBinder
instances pour ajuster votre logique métier; et il est logique d'affaires, ce qui signifie qu'il devrait être dans le contrôleur.la source
datetimeoffset
SQL Server etDateTimeOffset
.NET ici, ils simplifient vraiment énormément les choses) que je pense que .NET ne gère pas correctement. du tout pour les raisons exposées ci-dessus. Si vous avez une date à New York qui a été entrée en 2003 et que vous souhaitez ensuite la traduire en une date à Los Angeles en 2011, .NET échoue dans ce cas.DateTimeOffset
vous atténuerez beaucoup cela, OMI).C'est juste mon avis, je pense que l'application MVC devrait séparer le problème de présentation des données de puits de la gestion du modèle de données. Une base de données peut stocker des données à l'heure du serveur local, mais il est du devoir de la couche de présentation de rendre la date / heure à l'aide du fuseau horaire de l'utilisateur local. Cela me semble le même problème que le I18N et le format des nombres pour différents pays. Dans votre cas, votre application doit détecter le
Culture
fuseau horaire et le fuseau horaire de l'utilisateur et modifier la vue en affichant différentes présentations de texte, de nombre et de date, mais les données stockées peuvent avoir le même format.la source
DateTimeExtensions
.Pour la sortie, créez un modèle d'affichage / éditeur comme celui-ci
Vous pouvez les lier en fonction des attributs de votre modèle si vous souhaitez que seuls certains modèles utilisent ces modèles.
Voir ici et ici pour plus de détails sur la création de modèles d'éditeur personnalisés.
Alternativement, puisque vous voulez qu'il fonctionne à la fois pour l'entrée et la sortie, je suggérerais d'étendre un contrôle ou même de créer le vôtre. De cette façon, vous pouvez intercepter à la fois l'entrée et les sorties et convertir le texte / la valeur selon vos besoins.
J'espère que ce lien vous poussera dans la bonne direction si vous souhaitez emprunter cette voie.
Quoi qu'il en soit, si vous voulez une solution élégante, ce sera un peu de travail. Du bon côté, une fois que vous l'avez fait, vous pouvez le conserver dans votre bibliothèque de code pour une utilisation future!
la source
DisplayFor
etEditorFor
il fonctionne à chaque fois. +1Il s'agit probablement d'un marteau pour casser une noix, mais vous pouvez injecter une couche entre les couches UI et Business qui convertit de manière transparente les datetimes en heure locale sur les graphiques d'objets renvoyés et en UTC sur les paramètres datetime en entrée.
J'imagine que cela pourrait être réalisé en utilisant PostSharp ou une inversion du conteneur de contrôle.
Personnellement, je voudrais simplement convertir explicitement vos datetimes dans l'interface utilisateur ...
la source