Konwertuj czas UTC / GMT na czas lokalny

301

Tworzymy aplikację C # dla klienta usługi sieciowej. Będzie działać na komputerach z systemem Windows XP.

Jednym z pól zwracanych przez usługę internetową jest pole DateTime. Serwer zwraca pole w formacie GMT, tj. Z „Z” na końcu.

Odkryliśmy jednak, że .NET wydaje się dokonywać jakiejś niejawnej konwersji, a czas zawsze wynosił 12 godzin.

Poniższy przykładowy kod rozwiązuje to w pewnym stopniu, ponieważ minęła 12 godzinna różnica, ale nie uwzględnia ona czasu letniego w NZ.

CultureInfo ci = new CultureInfo("en-NZ");
string date = "Web service date".ToString("R", ci);
DateTime convertedDate = DateTime.Parse(date);            

Zgodnie z tą datą strona :

Przesunięcie UTC / GMT

Standardowa strefa czasowa: UTC / GMT +12 godzin
Czas letni: +1 godzina
Bieżące przesunięcie strefy czasowej: UTC / GMT +13 godzin

Jak dostosowujemy się do dodatkowej godziny? Czy można to zrobić programowo, czy jest to jakieś ustawienie na komputerze?

rbrayb
źródło
2
Zczas odnosi się do UTC, a nie GMT. Oba mogą się różnić o maksymalnie 0,9 sekundy.
mc0e

Odpowiedzi:

374

Ciągów, takich jak 2012-09-19 01:27:30.000, DateTime.Parsenie może powiedzieć, co strefa czasowa data i czas są od.

DateTimema właściwość Kind , która może mieć jedną z trzech opcji strefy czasowej:

  • Nieokreślony
  • Lokalny
  • Utc

UWAGA Jeśli chcesz podać datę / godzinę inną niż UTC lub lokalną strefę czasową, powinieneś użyć DateTimeOffset.


Więc dla kodu w twoim pytaniu:

DateTime convertedDate = DateTime.Parse(dateStr);

var kind = convertedDate.Kind; // will equal DateTimeKind.Unspecified

Mówisz, że wiesz, jaki to rodzaj, więc powiedz to.

DateTime convertedDate = DateTime.SpecifyKind(
    DateTime.Parse(dateStr),
    DateTimeKind.Utc);

var kind = convertedDate.Kind; // will equal DateTimeKind.Utc

Teraz, gdy system pozna czas UTC, możesz po prostu zadzwonić ToLocalTime:

DateTime dt = convertedDate.ToLocalTime();

To da ci wynik, którego potrzebujesz.

Drew Noakes
źródło
19
po prostu inny sposób na określenie rodzaju:DateTime convertedTime = new DateTime(DateTime.Parse(dateStr).Ticks), DateTimeKind.Utc);
Brad
czy to nie ToLocalTime ()? @Brad - twoje pareny się nie zgadzają.
TrueWill,
2
Czy to rozwiązanie zapewnia oszczędności w świetle dziennym? Kiedy spróbuję, nie mam godziny.
Bob Horn
7
Etap zmieniając Kindz DateTimeod Unspecifiedcelu UTCnie jest konieczne. Unspecifiedzakłada się, że jest UTCdo celów ToLocalTime: msdn.microsoft.com/en-us/library/…
CJ7
16
@ CJ7: Tak, ale jawność jest szczególnie pomocna dla innych programistów, którzy mogą mieć obowiązek obsługi kodu.
Ryan
121

Chciałbym skorzystać z klasy System.TimeZoneInfo, jeśli jesteś w .NET 3.5. Zobacz http://msdn.microsoft.com/en-us/library/system.timezoneinfo.aspx . Powinno to prawidłowo uwzględniać zmiany czasu letniego.

// Coordinated Universal Time string from 
// DateTime.Now.ToUniversalTime().ToString("u");
string date = "2009-02-25 16:13:00Z"; 
// Local .NET timeZone.
DateTime localDateTime = DateTime.Parse(date); 
DateTime utcDateTime = localDateTime.ToUniversalTime();

// ID from: 
// "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Time Zone"
// See http://msdn.microsoft.com/en-us/library/system.timezoneinfo.id.aspx
string nzTimeZoneKey = "New Zealand Standard Time";
TimeZoneInfo nzTimeZone = TimeZoneInfo.FindSystemTimeZoneById(nzTimeZoneKey);
DateTime nzDateTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, nzTimeZone);
Daniel Ballinger
źródło
Jeśli pracujesz we własnej strefie czasowej (w tym przypadku en-NZ), nie musisz zajmować się TimeZoneInfo. To po prostu niepotrzebna złożoność. Zobacz moją odpowiedź po więcej szczegółów.
Drew Noakes
11
A jeśli ktoś potrzebuje, oto lista stref czasowych, które znalazłem dla TimeZoneInfo.FindSystemTimeZoneById - codeproject.com/Messages/3867850/…
nikib3ro
Znakomity! Dzięki za ten post Dan. Szukałem tej poprawki przez 3 dni.
Kevin Moore,
58
TimeZone.CurrentTimeZone.ToLocalTime(date);
koder1
źródło
8
Działa to tylko wtedy, gdy system wie, że konwertowana data jest w UTC. Proszę zobaczyć moją odpowiedź.
Drew Noakes
1
Ale UTC jest wartością domyślną, prawda? Dlatego działa dla „nieokreślonego”, jak w odpowiedzi CJ7.
NickG,
25

DateTimeobiekty mają Kindz Unspecifieddomyślnie, który dla celów ToLocalTimezakłada się UTC.

Aby uzyskać czas lokalny Unspecified DateTimeobiektu, musisz po prostu to zrobić:

convertedDate.ToLocalTime();

Etap zmieniając Kindz DateTimeod Unspecifiedcelu UTCnie jest konieczne. Unspecifiedzakłada się, że jest UTCdo celów ToLocalTime: http://msdn.microsoft.com/en-us/library/system.datetime.tolocaltime.aspx

CJ7
źródło
6
I na odwrót: convertedDate.FromLocalTime();przekonwertuje na UTC.
R. Schreurs,
16

Wiem, że to starsze pytanie, ale spotkałem się z podobną sytuacją i chciałem podzielić się tym, co znalazłem dla przyszłych poszukiwaczy, w tym również siebie :).

DateTime.Parse()może być trudne - patrz tutaj na przykład.

Jeśli DateTimepochodzi z usługi sieci Web lub innego źródła o znanym formacie, możesz rozważyć coś takiego

DateTime.ParseExact(dateString, 
                   "MM/dd/yyyy HH:mm:ss", 
                   CultureInfo.InvariantCulture, 
                   DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)

lub nawet lepiej

DateTime.TryParseExact(...)

AssumeUniversalFlaga informuje parser, że data / czas jest już UTC; kombinacja AssumeUniversali AdjustToUniversalmówi, aby nie konwertował wyniku na czas „lokalny”, co spróbuje wykonać domyślnie. (Osobiście staram się radzić sobie wyłącznie z UTC w warstwie biznesowej / aplikacji / usług. Ale obejście konwersji na czas lokalny również przyspiesza - o 50% lub więcej w moich testach, patrz poniżej.)

Oto, co robiliśmy wcześniej:

DateTime.Parse(dateString, new CultureInfo("en-US"))

Wyprofilowaliśmy aplikację i stwierdziliśmy, że DateTime.Parse reprezentuje znaczny procent wykorzystania procesora. (Nawiasem mówiąc, CultureInfokonstruktor nie miał znaczącego wpływu na użycie procesora).

Skonfigurowałem więc aplikację konsolową do analizowania ciągu daty / godziny 10000 razy na różne sposoby. Konkluzja:
Parse()10 sekund
ParseExact()(konwersja na lokalną) 20-45 ms
ParseExact()(bez konwersji na lokalną) 10-15 ms
... i tak, wyniki dla Parse()są w sekundach , podczas gdy pozostałe są w milisekundach .

David
źródło
14

Chciałbym tylko dodać ogólną nutę ostrożności.

Jeśli wszystko, co robisz, to pobiera bieżącą godzinę z wewnętrznego zegara komputera, aby umieścić datę / czas na wyświetlaczu lub w raporcie, wszystko jest w porządku. Ale jeśli zapisujesz informacje o dacie / godzinie do późniejszego wykorzystania lub obliczasz datę / czas, strzeż się!

Załóżmy, że ustaliłeś, że statek wycieczkowy przybył do Honolulu w dniu 20 grudnia 2007 r. O 15:00 UTC. I chcesz wiedzieć, co to był czas lokalny.
1. W grę wchodzi prawdopodobnie co najmniej trzech „mieszkańców”. Lokalny może oznaczać Honolulu lub może oznaczać lokalizację komputera lub lokalizację klienta.
2. Jeśli użyjesz wbudowanych funkcji do wykonania konwersji, prawdopodobnie będzie źle. Wynika to z faktu, że czas letni jest (prawdopodobnie) obecnie na twoim komputerze, ale NIE obowiązywał w grudniu. Ale Windows tego nie wie ... wszystko, co ma, to jedna flaga, aby ustalić, czy obowiązuje czas letni. A jeśli to obowiązuje, to z radością doda godzinę nawet do daty w grudniu.
3)Czas letni jest wdrażany inaczej (lub wcale) w różnych działach politycznych. Nie myśl, że tylko dlatego, że twój kraj zmienia się w określonym dniu, inne kraje też.

Mark T
źródło
6
W rzeczywistości nr 2 nie jest całkowicie poprawny. W rzeczywistości istnieją zasady dotyczące czasu letniego w każdej strefie czasowej, którą komputer będzie wiedział, jeśli informacje zostały zainstalowane (i zaktualizowane). W wielu strefach reguły te są ustalone. Inni wdrażają „dynamiczny czas letni”. Brazylia jest dla mnie tym wkurzona. Podsumowując, twój komputer może sprawdzić, czy twój czas lokalny byłby DST w grudniu, przy założeniu, że od tego czasu nie zostaną wprowadzone żadne zmiany prawne.
Roger Willcocks
Nawet jeśli nie mieszkasz w Brazylii, DST jest „dynamiczna”, ponieważ politycy mogą ją zmienić w dowolnym momencie (tak jak kilka lat temu w USA). Ponieważ większość oprogramowania jest pisana z myślą o przyszłym użyciu, ważne jest, aby zdawać sobie sprawę, że NIE ma praktycznego, przewidywalnego ani nawet teoretycznego sposobu dowiedzenia się, jakie będą obowiązywały reguły DST. Możesz się zbliżyć, ale oszczędzaj sobie frustracji, rezygnując z perfekcji.
DaveWalley
6
@TimeZoneInfo.ConvertTimeFromUtc(timeUtc, TimeZoneInfo.Local)
Książę Prasad
źródło
5

Nie zapomnij, jeśli masz już obiekt DateTime i nie masz pewności, czy jest to obiekt UTC czy lokalny, wystarczy użyć metod bezpośrednio na obiekcie:

DateTime convertedDate = DateTime.Parse(date);
DateTime localDate = convertedDate.ToLocalTime();

Jak dostosowujemy się do dodatkowej godziny?

O ile nie określono inaczej .net użyje lokalnych ustawień komputera. Przeczytałbym: http://msdn.microsoft.com/en-us/library/system.globalization.daylighttime.aspx

Wyglądem może wyglądać kod:

DaylightTime daylight = TimeZone.CurrentTimeZone.GetDaylightChanges( year );

I jak wspomniano powyżej, sprawdź dwukrotnie, jakie ustawienie strefy czasowej jest ustawiony na serwerze. W sieci znajdują się artykuły dotyczące bezpiecznego wpływu na zmiany w IIS.

Brendan Kowitz
źródło
System poradzi sobie z tą złożonością za Ciebie, pod warunkiem, że poinformujesz system, jakiego rodzaju jest Twoja data (lokalna / utc / nieokreślona).
Drew Noakes
2

W odpowiedzi na sugestię Dany:

Przykładowy kod wygląda teraz tak:

string date = "Web service date"..ToString("R", ci);
DateTime convertedDate = DateTime.Parse(date);            
DateTime dt = TimeZone.CurrentTimeZone.ToLocalTime(convertedDate);

Pierwotna data to 20/08/08; tego rodzaju był UTC.

Zarówno „convertDate”, jak i „dt” są takie same:

21/08/08 10:00:26; ten rodzaj był lokalny

rbrayb
źródło
Proszę zobaczyć moją odpowiedź na wyjaśnienie tego.
Drew Noakes
1

Miałem problem z tym, że znajdował się on w zestawie danych przepychanym przez sieć (usługa internetowa do klienta), który to zmieniłby się automatycznie, ponieważ pole DateType DataColumn było ustawione na lokalne. Upewnij się, że sprawdzasz, jaki jest DateType, jeśli przesuwasz DataSets w poprzek.

Jeśli nie chcesz, aby to się zmieniało, ustaw na Nieokreślone

Miles
źródło
1

Natknąłem się na to pytanie, ponieważ miałem problem z datami UTC, które otrzymałeś przez interfejs API Twittera (pole create_at w stanie); Muszę przekonwertować je na DateTime. Żadna z odpowiedzi / próbek kodu w odpowiedziach na tej stronie nie była wystarczająca, aby powstrzymać mnie przed otrzymaniem błędu „Ciąg nie został rozpoznany jako prawidłowy parametr DateTime” (ale jest to najbliższy sposób znalezienia poprawnej odpowiedzi na SO)

Opublikowanie tego linku tutaj na wypadek, gdyby to pomogło komuś innemu - odpowiedź, której potrzebowałem, została znaleziona na tym blogu: http://www.wduffy.co.uk/blog/parsing-dates-when-aspnets-datetimeparse-doesnt-work/ - w zasadzie użyj DateTime.ParseExact z ciągiem formatu zamiast DateTime.Parse

DannykPowell
źródło