web-dev-qa-db-fra.com

DateTime.Now and Culture/Timezone spécifique

Notre application a été conçue pour gérer les utilisateurs de différents emplacements géographiques. 

Nous sommes incapables de détecter quelle est l'heure locale actuelle de l'utilisateur final et fuseau horaire opère dessus. Ils sélectionnent différentes cultures comme sv-se, en-us, ta-In même s'ils ont accès depuis le fuseau horaire Europe/London.

Nous l'avons hébergé sur un serveur d'hébergement aux États-Unis, les utilisateurs de l'application sont de Norway/Denmark/Sweden/UK/USA/India

Le problème est que nous avons utilisé DateTime.Now pour stocker la date de création/mise à jour de l'enregistrement, etc.

Depuis le serveur exécuté aux États-Unis, toutes les données utilisateur sont enregistrées en tant que temps américain :(

Après des recherches dans SO, nous avons décidé de stocker toutes les dates de l'historique dans la base de données sous le nom DateTime.UtcNow.

PROBLEME:

enter image description here

Il existe un enregistrement créé sur 29 Dec 2013, 3:15 P.M Swedish time

 public ActionResult Save(BookingViewModel model)
    {
        Booking booking = new Booking();
        booking.BookingDateTime = model.BookingDateTime; //10 Jan 2014 2:00 P.M
        booking.Name = model.Name;
        booking.CurrentUserId = (User)Session["currentUser"].UserId;
        //USA Server runs in Pacific Time Zone, UTC-08:00
        booking.CreatedDateTime = DateTime.UtcNow; //29 Dec 2013, 6:15 A.M
        BookingRepository.Save(booking);
        return View("Index");
    }

Nous souhaitons afficher la même heure d’historique à l’utilisateur qui s’est connecté en Inde/Suède/États-Unis.

A partir de maintenant, nous utilisons un utilisateur de culture actuel connecté et choisissons le fuseau horaire à partir d'un fichier de configuration. Nous l'utilisons pour la conversion avec la classe TimeZoneInfo.

<appSettings>
    <add key="sv-se" value="W. Europe Standard Time" />
    <add key="ta-IN" value="India Standard Time" />
</appSettings>

    private DateTime ConvertUTCBasedOnCuture(DateTime utcTime)
    {
        //utcTime is 29 Dec 2013, 6:15 A.M
        string TimezoneId =                  
                System.Configuration.ConfigurationManager.AppSettings
                [System.Threading.Thread.CurrentThread.CurrentCulture.Name];
        // if the user changes culture from sv-se to ta-IN, different date is shown
        TimeZoneInfo tZone = TimeZoneInfo.FindSystemTimeZoneById(TimezoneId);

        return TimeZoneInfo.ConvertTimeFromUtc(utcTime, tZone);
    }
    public ActionResult ViewHistory()
    {
        List<Booking> bookings = new List<Booking>();
        bookings=BookingRepository.GetBookingHistory();
        List<BookingViewModel> viewModel = new List<BookingViewModel>();
        foreach (Booking b in bookings)
        {
            BookingViewModel model = new BookingViewModel();
            model.CreatedTime = ConvertUTCBasedOnCuture(b.CreatedDateTime);
            viewModel.Add(model);
        }
        return View(viewModel);
    }

Voir le code

   @Model.CreatedTime.ToString("dd-MMM-yyyy - HH':'mm")

REMARQUE: l'utilisateur peut changer de culture/langue avant de se connecter. C'est une application basée sur la localisation, fonctionnant sur un serveur américain. 

J'ai vu NODATIME , mais je ne comprenais pas en quoi cela pouvait aider avec une application Web multiculturelle hébergée dans un emplacement différent.

Question

Comment puis-je afficher une même date de création d'enregistrement 29 Dec 2013, 3:15 P.M pour les utilisateurs connectés dans INDIA/USA/Anywhere`? 

A partir de maintenant, ma logique dans ConvertUTCBasedOnCuture est basée sur la culture de l'utilisateur connecté. Cela devrait être indépendant de la culture, car l'utilisateur peut se connecter en utilisant n'importe quelle culture indienne/américaine.

COLONNE DE BASE DE DONNEES

CreatedTime: SMALLDATETIME

(MISE À JOUR: SOLUTION TENTÉE:}

DATABASE COLUMN TYPE: DATETIMEOFFSET

UI

Enfin, j'envoie l'heure locale de l'utilisateur actuel en utilisant le code Momento.js ci-dessous dans chaque demande.

$.ajaxSetup({
    beforeSend: function (jqXHR, settings) {
        try {
      //moment.format gives current user date like 2014-01-04T18:27:59+01:00
            jqXHR.setRequestHeader('BrowserLocalTime', moment().format());
        }
        catch (e) {
        }
    }
});

APPLICATION

public static DateTimeOffset GetCurrentUserLocalTime()
{
    try
    {
      return 
      DateTimeOffset.Parse(HttpContext.Current.Request.Headers["BrowserLocalTime"]);
    }
    catch
    {
        return DateTimeOffset.Now;
    }
}

puis appelé dans

 model.AddedDateTime = WebAppHelper.GetCurrentUserLocalTime();

En vue

@Model.AddedDateTime.Value.LocalDateTime.ToString("dd-MMM-yyyy - HH':'mm")

Dans la vue, il montre l'heure locale à l'utilisateur, mais je veux voir comme dd-MMM-yyyy CET/PST (il y a 2 heures).

Il y a 2 heures devraient être calculées à partir de l'heure locale de l'utilisateur final. Exactement la même chose que l'heure de création/de modification de la question de dépassement de pile avec l'affichage du fuseau horaire et le calcul de l'utilisateur local.

Exemple:answered Jan 25 '13 at 17:49 CST (6 hours/days/month ago) Ainsi, l'autre utilisateur de USA/INDIA peut vraiment comprendre que cet enregistrement a été créé exactement à 6 heures de l'heure actuelle de INDIA/USA.

Presque je pense avoir tout accompli, sauf le format d'affichage et le calcul. Comment puis-je faire ceci? 

25
Billa

Il semble que vous ayez besoin de stocker une DateTimeOffset au lieu de DateTime. Vous pouvez simplement stocker la variable locale DateTime pour l'utilisateur qui crée la valeur, mais cela signifie que vous ne pouvez effectuer aucune opération de commande, etc. Vous ne pouvez pas simplement utiliser DateTime.UtcNow, car cela ne stocke rien pour indiquer la date/heure de l'utilisateur lorsque l'enregistrement a été créé.

Vous pouvez également stocker un instant dans le fuseau horaire de l'utilisateur, ce qui est plus difficile à obtenir, mais vous donnerait plus d'informations, car vous pourriez alors dire des choses telles que "quelle est l'heure locale de l'utilisateur une heure plus tard?"

Le hébergement du serveur doit être sans importance - vous ne devez jamais utiliser le fuseau horaire du serveur. Cependant, vous devrez connaître le décalage UTC (ou le fuseau horaire) approprié pour l'utilisateur. Ceci ne peut pas doit être fait uniquement en fonction de la culture - vous voudrez utiliser Javascript sur la machine de l'utilisateur pour déterminer le décalage UTC au moment qui vous intéresse (pas nécessairement "maintenant").

Une fois que vous avez déterminé comment stocker la valeur, la récupération est simple: si vous avez déjà stocké l’instant UTC et un décalage, vous appliquez simplement ce décalage et vous revenez à l’heure locale de l’utilisateur. Vous n'avez pas dit comment vous convertissez les valeurs en texte, mais cela devrait simplement être supprimé - formatez simplement la valeur et vous devriez obtenir l'heure locale d'origine.

Si vous décidez d'utiliser Noda Time, vous utiliserez simplement OffsetDateTime au lieu de DateTimeOffset.

13
Jon Skeet

L’approche standard consiste à toujours stocker toutes les données temporelles au format UTC si un moment précis est important. Cette heure n'est pas affectée par les changements de fuseau horaire et les cultures.

L’approche la plus courante pour afficher l’heure avec le fuseau horaire consiste à enregistrer l’heure au format UTC et à la convertir en une combinaison culture/fuseau horaire de l’utilisateur actuel lors de l’affichage de la valeur. Cette approche n’exige que la date et l’heure archivées. 

Notez que pour les requêtes Web (telles que ASP.Net), vous devrez peut-être commencer par déterminer la culture/le fuseau horaire de l'utilisateur et l'envoyer au serveur (car ces informations ne sont pas nécessairement disponibles pour les demandes GET) ou formater l'heure dans le navigateur.

Selon ce que "afficher la même heure d'historique", vous devrez peut-être stocker des informations supplémentaires telles que la culture actuelle et/ou le décalage actuel. Si vous avez besoin d'afficher l'heure exactement comme l'utilisateur d'origine l'a vue, vous pouvez également enregistrer la représentation sous forme de chaîne (car les formats/traductions peuvent changer plus tard et la valeur sera différente, ce qui est inhabituel).

Remarque: la culture et le fuseau horaire ne sont pas liés, vous devrez donc décider comment gérer des cas tels que la culture IN-IN dans le fuseau horaire PST des États-Unis.

11
Alexei Levenkov

Je suis un peu dérouté par le libellé de votre question, mais il semble que vous souhaitiez déterminer le fuseau horaire de votre utilisateur.

  • Avez-vous essayé leur demandant? Dans de nombreuses applications, l'utilisateur choisit son fuseau horaire dans ses paramètres.

  • Vous pouvez choisir dans une liste déroulante ou une paire de listes (pays, puis fuseau horaire dans le pays) ou parmi un sélecteur de fuseau horaire basé sur une carte .

  • Vous pouvez deviner et l'utiliser comme valeur par défaut, à moins que votre utilisateur ne la modifie.

Si vous suivez cette voie, vous devrez être en mesure d’utiliser les fuseaux horaires IANA/Olson, c’est là que Noda Time entre en jeu. Vous pouvez y accéder à partir de DateTimeZoneProviders.Tzdb.

Le lieu d'hébergement n'est pas pertinent si vous utilisez UTC. C'est une bonne chose.

De plus, si vous utilisez Noda Time, vous devriez probablement utiliser SystemClock.Instance.Now au lieu de DateTime.UtcNow.

Voir aussi ici et ici .

En outre, une solution alternative consisterait simplement à transmettre l'heure UTC au navigateur et à la charger dans un objet JavaScript Date. Le navigateur peut convertir cela en heure locale de l'utilisateur. Vous pouvez également utiliser une bibliothèque comme moment.js pour rendre cela plus facile.


Mettre à jour

Concernant votre approche de la correspondance des codes de culture avec les fuseaux horaires:

<appSettings>
    <add key="sv-se" value="W. Europe Standard Time" />
    <add key="ta-IN" value="India Standard Time" />
</appSettings>

Ce ne fonctionnera pas, pour plusieurs raisons:

  • Beaucoup les gens utilisent sur leur ordinateur une culture différente de celle dans laquelle ils se trouvent physiquement. Par exemple, je pourrais être un anglophone américain vivant en Allemagne, mon code de culture est probablement toujours en-USde-DE.

  • Un code de culture contenant un pays permet de distinguer dialectes d'une langue. Lorsque vous voyez es-MX, cela signifie "espagnol, tel que parlé au Mexique". Cela ne signifie pas que l'utilisateur est réellement in Mexico. Cela signifie simplement que l'utilisateur parle ce dialecte de l'espagnol, par rapport à es-ES qui signifie "espagnol, tel qu'il est parlé en Espagne".

  • Même si la partie pays du code de la culture pourrait être fiable, il existe plusieurs pays qui ont plusieurs fuseaux horaires! Par exemple, que mettriez-vous dans votre liste de mappage pour en-US? Vous ne pouvez pas simplement supposer que nous sommes tous à l'heure normale de l'Est.

Maintenant, j'ai expliqué pourquoi que votre approche actuelle ne fonctionnera pas, je vous suggère fortement de suivre mon conseil initial. Très simplement:

  1. Déterminez le fuseau horaire de l'utilisateur, de préférence en le lui demandant, éventuellement avec l'aide de l'un des utilitaires que j'ai liés ci-dessus.

  2. Vous stockez le temps UTC, il vous suffit donc de convertir ce fuseau horaire en affichage.

    Utilisation des fuseaux horaires Microsoft
    TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
    DateTime localDatetime = TimeZoneInfo.ConvertTimeFromUtc(yourUTCDateTime, tz);
    
    Utilisation des fuseaux horaires IANA et de l'heure Noda
    DateTimeZone tz = DateTimeZoneProviders.Tzdb["Europe/Stockholm"];
    Instant theInstant = Instant.FromDateTimeUtc(yourUTCDateTime);
    LocalDateTime localDateTime = theInstant.InZone(tz);
    
7
Matt Johnson

Nous avons rencontré un problème similaire avec une application sur laquelle j'ai travaillé récemment. Pendant le développement, tout le monde était dans le même fuseau horaire et le problème n'avait pas été remarqué. Et dans tous les cas, il y aurait eu beaucoup de code hérité qu'il aurait été difficile de changer, sans parler de la conversion de toutes les informations de date et heure déjà présentes dans la base de données. Donc, passer à DateTimeOffset n'était pas une option. Cependant, nous avons réussi à rendre le tout cohérent en convertissant l'heure du serveur en heure d'utilisateur et l'heure de l'utilisateur en passant. Il était également important de le faire avec toutes les comparaisons de date/heure qui étaient une limite. Donc, si un utilisateur s'attend à ce que quelque chose expire à minuit, nous le convertirions en heure du serveur et ferions toutes les comparaisons à la fois. Cela ressemble à beaucoup de travail, mais cela demandait beaucoup moins de travail que de convertir l’application entière et la base de données pour utiliser DateTimeOffsets. 

Hear est un fil qui ressemble à quelques bonnes solutions au problème de fuseau horaire. 

Déterminer le fuseau horaire d'un utilisateur

1
SzabV

Si vous souhaitez afficher un historique de date/heure cohérent pour l'utilisateur, quels que soient les paramètres régionaux à partir duquel il affiche l'historique, procédez comme suit:

  1. Au cours de Save, stockez non seulement la date/heure de "création" UTC, mais également les paramètres régionaux détectés 
  2. Utilisez le saved from locale stocké pour calculer la date/heure d'origine et émettre une chaîne à afficher (c.-à-d. N'utilisez pas les paramètres régionaux de l'utilisateur actuel lorsque vous les affichez)

Si vous n'avez pas la possibilité de modifier votre stockage, vous pouvez peut-être modifier l'envoi en envoyant "l'heure actuelle du client", le stocker littéralement (ne pas convertir en UTC) puis l'afficher littéralement (ne pas convertir en culture détectée). )

Mais comme je l'ai dit dans le commentaire de votre question, je ne suis pas certain d'avoir bien répondu à vos besoins.

0
G. Stoynev