web-dev-qa-db-fra.com

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

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

Lors du test, je dois pouvoir créer des objets DateTime dans un fuseau horaire non local pour que les personnes qui exécutent le test puissent 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 le fuseau horaire comme étant le fuseau horaire local, le fuseau horaire UTC ou non spécifié.

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

133
Jack Hughes

La réponse de Jon parle de TimeZone , mais je suggérerais plutôt d'utiliser TimeZoneInfo .

Personnellement, j'aime garder les choses en UTC autant que possible (du moins pour le passé; stocker l'UTC pour le futur a des problèmes potentiels ) , donc 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 voudrez peut-être changer les noms de "TimeZone" en "TimeZoneInfo" pour clarifier les choses - je préfère les noms plus courts moi-même.

187
Jon Skeet

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));

45
Clever Human

Les autres réponses ici sont utiles mais elles ne couvrent pas comment accéder spécifiquement à Pacific - voilà:

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

Curieusement, bien que "Heure normale du Pacifique" signifie normalement quelque chose de différent de "Heure avancée du Pacifique", dans ce cas, il fait référence à l'heure du Pacifique en général. En fait, si vous utilisez FindSystemTimeZoneById pour le récupérer, l’une des propriétés disponibles est un booléen vous indiquant si le fuseau horaire est actuellement en heure avancée ou non.

Vous pouvez voir des exemples plus généraux de cela dans une bibliothèque que j'ai finalement jeté ensemble pour traiter DateTimes dont j'ai besoin dans différents fuseaux horaires 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 sur Linux) car la liste des heures provient du registre Windows: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\

Vous trouverez ci-dessous des clés (icônes de dossiers dans l’Éditeur du Registre); les noms de ces clés sont ce que 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é.

33
Chris Moschini

J'ai modifié réponse Jon Skeet un peu pour le Web avec la méthode d'extension. Cela fonctionne aussi sur Azure 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);   
}
}
6
Jernej Novak

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 cteur soit toujours transmis 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 se trouve cette base de données. Donc, dans l'éditeur, je vais passer dans le fuseau horaire de la base de données. Mais alors je voudrais la valeur en heure locale. Jon's LocalTime ne renvoie pas la date d'origine convertie en date de fuseau horaire local. Il retourne 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é éclaircir ...

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);
}
2
Gabe Halsmer

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

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

2
Jon Limjap