Erstellen einer DateTime in einer bestimmten Zeitzone in c #

162

Ich versuche, einen Komponententest zu erstellen, um den Fall zu testen, wenn sich die Zeitzone auf einer Maschine ändert, weil sie falsch eingestellt und dann korrigiert wurde.

Im Test muss ich in der Lage sein, DateTime-Objekte in einer nicht lokalen Zeitzone zu erstellen, um sicherzustellen, dass Personen, die den Test ausführen, dies erfolgreich tun können, unabhängig davon, wo sie sich befinden.

Nach dem, was ich im DateTime-Konstruktor sehen kann, kann ich die Zeitzone entweder als lokale Zeitzone, als UTC-Zeitzone oder nicht angegeben festlegen.

Wie erstelle ich eine DateTime mit einer bestimmten Zeitzone wie PST?

Jack Hughes
quelle

Antworten:

216

Jons Antwort spricht von TimeZone , aber ich würde vorschlagen, stattdessen TimeZoneInfo zu verwenden.

Persönlich mag ich es, Dinge in UTC zu behalten, wo dies möglich ist (zumindest für die Vergangenheit; das Speichern von UTC für die Zukunft hat potenzielle Probleme ), daher würde ich eine Struktur wie diese vorschlagen:

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

Vielleicht möchten Sie die "TimeZone" -Namen in "TimeZoneInfo" ändern, um die Dinge klarer zu machen - ich bevorzuge die kürzeren Namen selbst.

Jon Skeet
quelle
5
Ich fürchte, ich kenne kein gleichwertiges SQL Server-Konstrukt. Ich würde vorschlagen, den Zeitzonennamen als eine Spalte und den UTC-Wert in einer anderen Spalte zu haben. Holen Sie sie separat und dann können Sie ziemlich einfach Instanzen erstellen.
Jon Skeet
2
Ich bin mir nicht sicher über die erwartete Verwendung des Konstruktors, der DateTime und TimeZoneInfo benötigt, aber angesichts der Tatsache, dass Sie die Methode dateTime.ToUniversalTime () aufrufen, vermute ich, dass Sie davon ausgehen, dass sie "möglicherweise" in der Ortszeit liegt. In diesem Fall sollten Sie die übergebene TimeZoneInfo wirklich verwenden, um sie in UTC zu konvertieren, da sie Ihnen sagen, dass sie in dieser Zeitzone liegen soll.
IDisposable
2
@ChrisMoschini: Zu diesem Zeitpunkt erfinden Sie jedoch nur Ihr eigenes ID-Schema - ein Schema, das sonst niemand auf der Welt verwendet. Ich bleibe beim Industriestandard zoneinfo, danke. (Es ist zum Beispiel schwer zu erkennen, wie bedeutungslos "Europa / London" ist.)
Jon Skeet
2
@ ChrisMoschini: Anderes Beispiel als: CST. Ist das UTC-5 oder UTC-6? Wie wäre es mit IST - ist das Israel, Indien oder Irland in Ihrer Datenbank? (Und selbst wenn Sie den Versatz jetzt kennen, können sich verschiedene Länder, die dieselbe Abkürzung beobachten, zu unterschiedlichen Zeiten ändern. Es besteht also immer noch Unklarheit darüber, welche tatsächliche Zeitzone dies bedeutet. Zeitzone! = Versatz.) Zurück zu Ihrem Fall: Sie behaupten dass die Verwendung von Abkürzungen Ihr Problem am besten gelöst hat. Wie wäre die Verwendung von Zeitzonen-IDs nach Industriestandard schlechter gewesen?
Jon Skeet
6
@ChrisMoschini: Nun, ich werde weiterhin empfehlen, die branchenüblichen, eindeutigen Zoneinfo-IDs anstelle der mehrdeutigen Abkürzungen zu verwenden. Dies ist keine Frage, wessen Bibliothek bevorzugt wird - die Urheberschaft der Bibliothek ist wirklich kein Problem. Wenn jemand eine andere Bibliothek mit einer guten Auswahl an Bezeichnern verwenden möchte , ist das in Ordnung. Die Wahl des Bezeichners für eine Zeitzone ist jedoch wichtig, und ich denke, es ist sehr wichtig, dass die Leser wissen, dass die Abkürzungen nicht eindeutig sind, wie ich anhand des IST-Beispiels gezeigt habe.
Jon Skeet
54

Die DateTimeOffset-Struktur wurde für genau diese Art der Verwendung erstellt.

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

Hier ist ein Beispiel für das Erstellen eines DateTimeOffset-Objekts mit einer bestimmten Zeitzone:

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

CleverPatrick
quelle
1
Vielen Dank, dies ist ein guter Weg, um dies zu erreichen. Nachdem Sie Ihr DateTimeOffset-Objekt innerhalb der richtigen Zeitzone erhalten haben, können Sie die .UtcDateTime-Eigenschaft verwenden, um eine UTC-Zeit für das von Ihnen erstellte Objekt abzurufen. Wenn Sie Ihre Daten in UTC speichern, ist die Umrechnung in Ortszeit für jeden Benutzer keine große Sache :)
Redth
2
Ich denke nicht, dass dies die Sommerzeit korrekt handhabt, da einige Zeitzonen dies berücksichtigen, während andere dies nicht tun. Auch "am Tag" beginnt / endet die Sommerzeit, Teile dieses Tages wären aus.
Crokusek
14
Lektion. Die Sommerzeit ist eine Regel einer bestimmten Zeitzone. DateTimeOffset ist keiner Zeitzone nicht zugeordnet. Verwechseln Sie einen UTC-Versatzwert wie -5 nicht mit einer Zeitzone. Es ist keine Zeitzone, es ist ein Versatz. Der gleiche Versatz wird häufig von vielen Zeitzonen geteilt, daher ist es eine mehrdeutige Art, sich auf eine Zeitzone zu beziehen. Da DateTimeOffset einem Offset und nicht einer Zeitzone zugeordnet ist, können möglicherweise keine Sommerzeitregeln angewendet werden. 3 Uhr morgens ist also 3 Uhr morgens an jedem einzelnen Tag des Jahres, ausnahmslos in einer DateTimeOffset-Struktur (z. B. in den Eigenschaften Hours und TimeOfDay).
Triynko
Wenn Sie sich die LocalDateTime-Eigenschaft des DateTimeOffset ansehen, können Sie verwirrt sein. Diese Eigenschaft ist KEIN DateTimeOffset, sondern eine DateTime-Instanz, deren Art DateTimeKind.Local ist. Diese Instanz ist einer Zeitzone zugeordnet ... unabhängig von der Zeitzone des lokalen Systems. Diese Eigenschaft wird Sommerzeit widerspiegeln.
Triynko
4
Das eigentliche Problem mit DateTimeOffset ist also, dass es nicht genügend Informationen enthält. Es enthält einen Versatz, keine Zeitzone. Der Versatz ist bei mehreren Zeitzonen nicht eindeutig.
Triynko
41

Die anderen Antworten hier sind nützlich, behandeln jedoch nicht speziell den Zugriff auf Pacific - los geht's:

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

Seltsamerweise bezieht sich "Pacific Standard Time" normalerweise auf etwas anderes als "Pacific Daylight Time", in diesem Fall jedoch auf die Pacific Time im Allgemeinen. Wenn Sie es FindSystemTimeZoneByIdzum Abrufen verwenden, ist eine der verfügbaren Eigenschaften ein Bool, der Ihnen sagt, ob diese Zeitzone derzeit in der Sommerzeit ist oder nicht.

Sie können allgemeinere Beispiele dafür in einer Bibliothek sehen, die ich letztendlich zusammengestellt habe, um mit DateTimes umzugehen, die ich in verschiedenen Zeitzonen benötige, je nachdem, woher der Benutzer fragt, usw.:

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

Dies funktioniert außerhalb von Windows nicht (z. B. Mono unter Linux), da die Liste der Zeiten aus der Windows-Registrierung stammt: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\

Darunter finden Sie Schlüssel (Ordnersymbole im Registrierungseditor); Die Namen dieser Schlüssel sind das, woran Sie übergeben FindSystemTimeZoneById. Unter Linux müssen Sie einen separaten Satz von Zeitzonendefinitionen nach Linux-Standard verwenden, die ich nicht ausreichend untersucht habe.

Chris Moschini
quelle
1
Zusätzlich gibt es ConvertTimeBySystemTimeZoneId () von: TimeZoneInfo.ConvertTimeBySystemTimeZoneId (DateTime.UtcNow, "Central Standard Time")
Brent
In Windows TimeZone Id List kann auch diese Antwort angezeigt werden
yu yang Jian
7

Ich habe die Antwort von Jon Skeet für das Web mit der Erweiterungsmethode ein wenig geändert . Es funktioniert auch auf Azurblau wie ein Zauber.

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
quelle
2

Dafür müssen Sie ein benutzerdefiniertes Objekt erstellen. Ihr benutzerdefiniertes Objekt enthält zwei Werte:

Ich bin nicht sicher, ob es bereits einen von der CLR bereitgestellten Datentyp gibt, der diesen hat, aber zumindest die TimeZone-Komponente ist bereits verfügbar.

Jon Limjap
quelle
2

Ich mag Jon Skeets Antwort, möchte aber eines hinzufügen. Ich bin mir nicht sicher, ob Jon erwartet hat, dass der Ctor immer in der lokalen Zeitzone übergeben wird. Aber ich möchte es für Fälle verwenden, in denen es etwas anderes als lokal ist.

Ich lese Werte aus einer Datenbank und weiß, in welcher Zeitzone sich diese Datenbank befindet. Im ctor übergebe ich also die Zeitzone der Datenbank. Aber dann möchte ich den Wert in Ortszeit. Jons LocalTime gibt nicht das ursprüngliche Datum zurück, das in ein lokales Zeitzonendatum konvertiert wurde. Es gibt das Datum zurück, das in die ursprüngliche Zeitzone konvertiert wurde (was auch immer Sie an den ctor übergeben haben).

Ich denke, diese Eigenschaftsnamen klären es auf ...

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
quelle
0

Die Verwendung der TimeZones- Klasse erleichtert das Erstellen eines zeitzonenspezifischen Datums.

TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById(TimeZones.Paris.Id));
Meghs Dhameliya
quelle
1
Entschuldigung, aber es ist hier nicht für Asp .NET Core 2.2 verfügbar. VS2017 schlägt mir vor, ein Outlook Nuget-Paket zu installieren.
Machado
Beispiel => TimeZoneInfo.ConvertTime (DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById ("Pacific Standard Time"))
AZ_