Création d'un DateTime dans un fuseau horaire spécifique en c #

162

J'essaie de créer un test unitaire pour tester le cas où le fuseau horaire change sur une machine parce qu'il a été mal défini puis corrigé.

Dans le test, je dois être en mesure de créer des objets DateTime dans un fuseau horaire non local pour m'assurer que les personnes exécutant le test peuvent le faire avec succès, quel que soit leur emplacement.

D'après ce que je peux voir du constructeur DateTime, je peux définir la zone horaire pour qu'elle soit le fuseau horaire local, le fuseau horaire UTC ou non spécifié.

Comment créer un DateHeure avec un fuseau horaire spécifique tel que PST?

Jack Hughes
la source
Question connexe - stackoverflow.com/questions/2532729/…
Oded le

Réponses:

216

La réponse de Jon parle de TimeZone , mais je suggérerais d'utiliser TimeZoneInfo à la place.

Personnellement, j'aime garder les choses en UTC dans la mesure du possible (du moins pour le passé; stocker UTC pour l' avenir a des problèmes potentiels ), alors je suggérerais une structure comme celle-ci:

public struct DateTimeWithZone
{
    private readonly DateTime utcDateTime;
    private readonly TimeZoneInfo timeZone;

    public DateTimeWithZone(DateTime dateTime, TimeZoneInfo timeZone)
    {
        var dateTimeUnspec = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
        utcDateTime = TimeZoneInfo.ConvertTimeToUtc(dateTimeUnspec, timeZone); 
        this.timeZone = timeZone;
    }

    public DateTime UniversalTime { get { return utcDateTime; } }

    public TimeZoneInfo TimeZone { get { return timeZone; } }

    public DateTime LocalTime
    { 
        get 
        { 
            return TimeZoneInfo.ConvertTime(utcDateTime, timeZone); 
        }
    }        
}

Vous pouvez changer les noms "TimeZone" en "TimeZoneInfo" pour rendre les choses plus claires - je préfère les noms plus courts moi-même.

Jon Skeet
la source
5
Je ne connais pas de construction SQL Server équivalente, j'en ai peur. Je suggérerais d'avoir le nom du fuseau horaire dans une colonne et la valeur UTC dans une autre colonne. Récupérez-les séparément et vous pourrez ensuite créer des instances assez facilement.
Jon Skeet
2
Je ne suis pas sûr de l'utilisation attendue du constructeur qui prend un DateTime et TimeZoneInfo, mais étant donné que vous appelez la méthode dateTime.ToUniversalTime (), je soupçonne que vous le devinez "peut-être" à l'heure locale. Dans ce cas, je pense que vous devriez vraiment utiliser le TimeZoneInfo transmis pour le convertir en UTC, car ils vous disent qu'il est censé être dans ce fuseau horaire.
IDisposable le
2
@ChrisMoschini: À ce stade, vous êtes juste en train d'inventer votre propre schéma d'identification - un schéma que personne d'autre dans le monde n'utilise. Je m'en tiendrai à la zoneinfo standard de l'industrie, merci. (Il est difficile de voir en quoi "Europe / Londres" n'a pas de sens, par exemple.)
Jon Skeet
2
@ChrisMoschini: Exemple différent alors: CST. Est-ce UTC-5 ou UTC-6? Qu'en est-il IST - est-ce Israël, l'Inde ou l'Irlande dans votre base de données? (Et même si vous connaissez le décalage en ce moment, différents pays observant la même abréviation peuvent changer à des moments différents. Il y a donc toujours une ambiguïté quant au fuseau horaire réel que cela signifie. Fuseau horaire! = Décalage.) Revenons à votre cas: vous réclamez que l'utilisation d'abréviations résout le mieux votre problème. En quoi l'utilisation des ID de fuseau horaire standard de l'industrie aurait-elle été pire?
Jon Skeet
6
@ChrisMoschini: Eh bien, je continuerai à recommander d'utiliser les ID de zoneinfo non ambigus et standard de l'industrie plutôt que les abréviations ambiguës. Ce n'est pas une question de savoir à qui la bibliothèque est préférée - la paternité de la bibliothèque n'est vraiment pas un problème. Si quelqu'un souhaite utiliser une autre bibliothèque avec un bon choix d'identifiant, c'est très bien. Le choix de l'identifiant pour un fuseau horaire est cependant important, et je pense qu'il est très important que les lecteurs sachent que les abréviations sont ambiguës, comme je l'ai montré avec l'exemple IST.
Jon Skeet
54

La structure DateTimeOffset a été créée pour exactement ce type d'utilisation.

Voir: http://msdn.microsoft.com/en-us/library/system.datetimeoffset.aspx

Voici un exemple de création d'un objet DateTimeOffset avec un fuseau horaire spécifique:

DateTimeOffset do1 = new DateTimeOffset(2008, 8, 22, 1, 0, 0, new TimeSpan(-5, 0, 0));

CleverPatrick
la source
1
Merci, c'est un bon moyen d'y parvenir. Une fois que vous avez obtenu votre objet DateTimeOffset dans le bon fuseau horaire, vous pouvez utiliser la propriété .UtcDateTime pour obtenir une heure UTC pour celle que vous avez créée. Si vous stockez vos dates au
format
2
Je ne pense pas que cela gère correctement l'heure d'été, car certaines fuseaux horaires l'honorent tandis que d'autres ne le font pas. Aussi «le jour» DST commence / se termine, certaines parties de cette journée seraient désactivées.
crokusek
14
Leçon. L'heure d'été est une règle d'un fuseau horaire particulier. DateTimeOffset n'est pas associé à aucun fuseau horaire. Ne confondez pas une valeur de décalage UTC, telle que -5, avec un fuseau horaire. Ce n'est pas un fuseau horaire, c'est un décalage. Le même décalage est souvent partagé par de nombreux fuseaux horaires, c'est donc une façon ambiguë de se référer à un fuseau horaire. Étant donné que DateTimeOffset est associé à un décalage et non à un fuseau horaire, il ne peut pas appliquer de règles DST. Donc 3h du matin sera 3h du matin tous les jours de l'année, sans exception dans une structure DateTimeOffset (par exemple dans ses propriétés Hours et TimeOfDay).
Triynko
Là où vous pouvez être confus, c'est si vous regardez la propriété LocalDateTime de DateTimeOffset. Cette propriété n'est PAS un DateTimeOffset, c'est une instance de DateTime dont le genre est DateTimeKind.Local. Cette instance EST associée à un fuseau horaire ... quel que soit le fuseau horaire du système local. Cette propriété reflétera l'heure d'été.
Triynko
4
Donc, le vrai problème avec DateTimeOffset est qu'il n'inclut pas suffisamment d'informations. Il comprend un décalage, pas un fuseau horaire. Le décalage est ambigu avec plusieurs fuseaux horaires.
Triynko
41

Les autres réponses ici sont utiles, mais elles ne couvrent pas spécifiquement comment accéder à Pacific - c'est parti:

public static DateTime GmtToPacific(DateTime dateTime)
{
    return TimeZoneInfo.ConvertTimeFromUtc(dateTime,
        TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"));
}

Curieusement, bien que "Pacific Standard Time" signifie normalement quelque chose de différent de "Pacific Daylight Time", dans ce cas, il se réfère à l'heure du Pacifique en général. En fait, si vous utilisez FindSystemTimeZoneByIdpour le récupérer, l'une des propriétés disponibles est un booléen vous indiquant si ce fuseau horaire est actuellement en heure d'été ou non.

Vous pouvez voir des exemples plus généralisés de cela dans une bibliothèque que j'ai fini par lancer ensemble pour gérer les DateTimes dont j'ai besoin dans différentes TimeZones en fonction de l'endroit où l'utilisateur demande, etc.

https://github.com/b9chris/TimeZoneInfoLib.Net

Cela ne fonctionnera pas en dehors de Windows (par exemple Mono sous Linux) car la liste des heures provient du registre Windows: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\

En dessous, vous trouverez des clés (icônes de dossier dans l'éditeur de registre); les noms de ces clés sont ce à quoi vous passez FindSystemTimeZoneById. Sous Linux, vous devez utiliser un ensemble distinct de définitions de fuseau horaire standard Linux, que je n'ai pas suffisamment exploré.

Chris Moschini
la source
1
De plus, il y a ConvertTimeBySystemTimeZoneId () ex: TimeZoneInfo.ConvertTimeBySystemTimeZoneId (DateTime.UtcNow, "Central Standard Time")
Brent
Dans Windows TimeZone Id List peut également voir cette réponse: stackoverflow.com/a/24460750/4573839
yu yang Jian
7

J'ai modifié un peu la réponse de Jon Skeet pour le Web avec la méthode d'extension. Il fonctionne également sur l'azur comme un charme.

public static class DateTimeWithZone
{

private static readonly TimeZoneInfo timeZone;

static DateTimeWithZone()
{
//I added web.config <add key="CurrentTimeZoneId" value="Central Europe Standard Time" />
//You can add value directly into function.
    timeZone = TimeZoneInfo.FindSystemTimeZoneById(ConfigurationManager.AppSettings["CurrentTimeZoneId"]);
}


public static DateTime LocalTime(this DateTime t)
{
     return TimeZoneInfo.ConvertTime(t, timeZone);   
}
}
Jernej Novak
la source
2

Vous devrez créer un objet personnalisé pour cela. Votre objet personnalisé contiendra deux valeurs:

Je ne sais pas s'il existe déjà un type de données fourni par CLR qui en a, mais au moins le composant TimeZone est déjà disponible.

Jon Limjap
la source
2

J'aime la réponse de Jon Skeet, mais j'aimerais ajouter une chose. Je ne sais pas si Jon s'attendait à ce que le ctor soit toujours passé dans le fuseau horaire local. Mais je veux l'utiliser pour les cas où c'est autre chose que local.

Je lis les valeurs d'une base de données, et je sais dans quel fuseau horaire cette base de données se trouve. Donc, dans le ctor, je passerai dans le fuseau horaire de la base de données. Mais alors j'aimerais la valeur en heure locale. Le LocalTime de Jon ne renvoie pas la date d'origine convertie en date de fuseau horaire local. Il renvoie la date convertie dans le fuseau horaire d'origine (tout ce que vous avez passé dans le ctor).

Je pense que ces noms de propriétés éclaircissent les choses ...

public DateTime TimeInOriginalZone { get { return TimeZoneInfo.ConvertTime(utcDateTime, timeZone); } }
public DateTime TimeInLocalZone    { get { return TimeZoneInfo.ConvertTime(utcDateTime, TimeZoneInfo.Local); } }
public DateTime TimeInSpecificZone(TimeZoneInfo tz)
{
    return TimeZoneInfo.ConvertTime(utcDateTime, tz);
}
Gabe Halsmer
la source
0

L'utilisation de la classe TimeZones facilite la création d'une date spécifique au fuseau horaire.

TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById(TimeZones.Paris.Id));
Meghs Dhameliya
la source
1
Désolé, mais ce n'est pas disponible sur Asp .NET Core 2.2 ici, VS2017 me suggère d'installer un package Outlook Nuget.
Machado
example => TimeZoneInfo.ConvertTime (DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById ("Pacific Standard Time"))
AZ_