Creazione di un DateTime in un fuso orario specifico in c #


162

Sto cercando di creare un test unitario per testare il caso in cui il fuso orario cambia su una macchina perché è stato impostato in modo errato e quindi corretto.

Nel test devo essere in grado di creare oggetti DateTime in un fuso orario nessuno locale per assicurarmi che le persone che eseguono il test possano farlo con successo indipendentemente da dove si trovano.

Da quello che posso vedere dal costruttore DateTime posso impostare TimeZone come fuso orario locale, fuso orario UTC o non specificato.

Come faccio a creare un DateTime con un fuso orario specifico come PST?


Risposte:


216

La risposta di Jon parla di TimeZone , ma suggerirei invece di utilizzare TimeZoneInfo .

Personalmente mi piace tenere le cose in UTC dove possibile (almeno per il passato; la memorizzazione di UTC per il futuro ha potenziali problemi ), quindi suggerirei una struttura come questa:

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

Potresti voler cambiare i nomi "TimeZone" in "TimeZoneInfo" per rendere le cose più chiare - Preferisco io stesso i nomi più brevi.


5
Non conosco alcun costrutto equivalente di SQL Server, temo. Suggerirei di avere il nome del fuso orario come una colonna e il valore UTC in un'altra colonna. Recuperali separatamente e quindi puoi creare istanze abbastanza facilmente.
Jon Skeet,

2
Non sono sicuro dell'uso previsto del costruttore che accetta un DateTime e TimeZoneInfo, ma dato che stai chiamando il metodo dateTime.ToUniversalTime (), sospetto che stai indovinando che "forse" sia nell'ora locale. In tal caso, penso che dovresti davvero utilizzare TimeZoneInfo passato per convertirlo in UTC poiché ti stanno dicendo che dovrebbe essere in quel fuso orario.
IDisposibile il

2
@ChrisMoschini: A quel punto stai solo inventando il tuo schema ID, uno schema che nessun altro al mondo usa. Continuerò con il zoneinfo standard del settore, grazie. (È difficile vedere come "Europa / Londra" non abbia senso, per esempio.)
Jon Skeet,

2
@ChrisMoschini: Esempio diverso quindi: CST. È UTC-5 o UTC-6? Che ne dici di IST: Israele, India o Irlanda sono nel tuo database? (E anche se conosci l'offset in questo momento, diversi paesi che osservano la stessa abbreviazione possono cambiare in momenti diversi. Quindi c'è ancora ambiguità su quale fuso orario reale significhi. Fuso orario! = Offset.) Tornando al tuo caso: rivendichi che l'uso delle abbreviazioni ha risolto meglio il tuo problema. In che modo l'uso degli ID di fuso orario standard del settore sarebbe stato peggio?
Jon Skeet,

6
@ChrisMoschini: Bene, continuerò a raccomandare di usare gli ID zoneinfo standard non ambigui piuttosto che le ambigue abbreviazioni. Non è una questione di chi è la biblioteca preferita - la paternità della biblioteca non è davvero un problema. Se qualcuno desidera utilizzare un'altra libreria con una buona scelta di identificatore, va bene. La scelta dell'identificatore per un fuso orario è tuttavia importante e penso che sia molto importante che i lettori siano consapevoli che le abbreviazioni sono ambigue, come ho mostrato con l'esempio IST.
Jon Skeet,

54

La struttura DateTimeOffset è stata creata esattamente per questo tipo di utilizzo.

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

Ecco un esempio di creazione di un oggetto DateTimeOffset con un fuso orario specifico:

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


1
Grazie, questo è un buon modo per realizzarlo. Dopo aver ottenuto l'oggetto DateTimeOffset nel fuso orario corretto, è possibile utilizzare la proprietà .UtcDateTime per ottenere un'ora UTC per quella creata. Se memorizzi le tue date in UTC, convertirle in ora locale per ogni utente non è un grosso problema :)
Red

2
Non credo che questo gestisca correttamente l'ora legale poiché alcune fasce orarie lo onorano mentre altre no. Anche "il giorno" dell'ora legale inizia / finisce, parti di quel giorno sarebbero fuori.
crokusek,

14
Lezione. L'ora legale è una regola di un particolare fuso orario. DateTimeOffset non è non associato a nessun fuso orario. Non confondere un valore di offset UTC, come -5, con un fuso orario. Non è un fuso orario, è un offset. Lo stesso offset è spesso condiviso da molti fusi orari, quindi è un modo ambiguo di fare riferimento a un fuso orario. Poiché DateTimeOffset è associato a un offset, non a un fuso orario, non è possibile applicare regole DST. Quindi le 3 del mattino saranno le 3 del mattino in ogni singolo giorno dell'anno, senza eccezioni in una struttura DateTimeOffset (ad es. Nelle proprietà Hours e TimeOfDay).
Triynko,

Dove potresti confonderti è se guardi la proprietà LocalDateTime di DateTimeOffset. Quella proprietà NON è un DateTimeOffset, è un'istanza DateTime il cui tipo è DateTimeKind.Local. Quell'istanza È associata a un fuso orario ... qualunque sia il fuso orario del sistema locale. Quella proprietà rifletterà l'ora legale.
Triynko,

4
Quindi, il vero problema con DateTimeOffset è che non include informazioni sufficienti. Include un offset, non un fuso orario. L'offset è ambiguo con più fusi orari.
Triynko,

41

Le altre risposte qui sono utili ma non descrivono come accedere in modo specifico a Pacific - ecco qui:

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

Stranamente, anche se "Pacific Standard Time" normalmente significa qualcosa di diverso da "Pacific Daylight Time", in questo caso si riferisce all'ora del Pacifico in generale. In effetti, se lo usi FindSystemTimeZoneByIdper recuperarlo, una delle proprietà disponibili è un bool che ti dice se quel fuso orario è attualmente in ora legale o meno.

Puoi vedere esempi più generalizzati di questo in una libreria che ho finito per mettere insieme per gestire DateTimes di cui ho bisogno in diversi fusi orari in base a dove l'utente chiede, ecc:

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

Questo non funzionerà al di fuori di Windows (ad esempio Mono su Linux) poiché l'elenco delle volte proviene dal registro di Windows: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\

Sotto troverai le chiavi (icone delle cartelle nell'Editor del Registro di sistema); i nomi di quelle chiavi sono ciò a cui passi FindSystemTimeZoneById. Su Linux devi usare una serie separata di definizioni di fuso orario standard Linux, che non ho esplorato adeguatamente.


1
Inoltre c'è ConvertTimeBySystemTimeZoneId () ex: TimeZoneInfo.ConvertTimeBySystemTimeZoneId (DateTime.UtcNow, "Central Standard Time")
Brent

Nell'elenco ID TimeZone di Windows è inoltre possibile visualizzare questa risposta: stackoverflow.com/a/24460750/4573839
yu yang Jian

7

Ho modificato un po ' Jon Skeet per il web con il metodo di estensione. Funziona anche su azzurro come un fascino.

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

2

Dovrai creare un oggetto personalizzato per quello. Il tuo oggetto personalizzato conterrà due valori:

  • un valore DateTime
  • un oggetto TimeZone

Non sono sicuro che esista già un tipo di dati fornito da CLR, ma almeno il componente TimeZone è già disponibile.


2

Mi piace la risposta di Jon Skeet, ma vorrei aggiungere una cosa. Non sono sicuro che Jon si aspettasse che il ctor venisse sempre passato nel fuso orario locale. Ma voglio usarlo per i casi in cui è qualcosa di diverso dal locale.

Sto leggendo i valori da un database e so in quale fuso orario si trova quel database. Quindi nel ctor passerò il fuso orario del database. Ma poi vorrei il valore in ora locale. LocalTime di Jon non restituisce la data originale convertita in una data del fuso orario locale. Restituisce la data convertita nel fuso orario originale (qualunque cosa tu abbia passato nel ctor).

Penso che questi nomi di proprietà chiariscano ...

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

0

L'uso della classe TimeZones semplifica la creazione di una data specifica per il fuso orario.

TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById(TimeZones.Paris.Id));

1
Siamo spiacenti, ma non è disponibile su Asp .NET Core 2.2 qui, VS2017 mi sta suggerendo di installare un pacchetto di Outlook Nuget.
Machado,

esempio => TimeZoneInfo.ConvertTime (DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById ("Pacific Standard Time"))
AZ_16
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.