Створення DateTime у визначеній часовій зоні в c #


162

Я намагаюся створити блок-тест, щоб перевірити випадок, коли часовий пояс змінюється на машині, оскільки він був неправильно встановлений і потім виправлений.

У тесті мені потрібно мати можливість створювати об’єкти DateTime в не місцевому часовому поясі, щоб люди, які виконують тест, могли робити це успішно незалежно від місця їх знаходження.

З того, що я бачу з конструктора DateTime, я можу встановити, що TimeZone буде локальним часовим поясом, часовим поясом UTC або не вказаним.

Як створити DateTime з певним часовим поясом, таким як PST?


Питання, пов’язані з цим - stackoverflow.com/questions/2532729/…
Oded

Відповіді:


216

Відповідь Джона говорить про TimeZone , але я б запропонував використати TimeZoneInfo .

Особисто мені подобається зберігати речі в UTC, де це можливо (принаймні для минулого; зберігання UTC для майбутнього має потенційні проблеми ), тому я б запропонував таку структуру:

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

Ви можете змінити назви "TimeZone" на "TimeZoneInfo", щоб зробити ясніше - я віддаю перевагу самим короткішим іменам.


5
Боюся, я не знаю жодної еквівалентної конструкції SQL Server. Я б запропонував вказати назву часового поясу як один стовпець, а значення UTC - в іншому. Витягніть їх окремо, і тоді ви можете створювати екземпляри досить легко.
Джон Скіт

2
Не впевнений у очікуваному використанні конструктора, який приймає DateTime та TimeZoneInfo, але враховуючи, що ви називаєте метод dateTime.ToUniversalTime (), я підозрюю, що ви здогадуєтесь про те, що "можливо" буде за місцевим часом. У такому випадку я думаю, що ви дійсно повинні використовувати переданий TimeZoneInfo для перетворення його в UTC, оскільки вони говорять вам, що він повинен знаходитись у тому часовому поясі.
Ідентифікатор

2
@ChrisMoschini: На той момент ти просто вигадуєш свою власну схему ідентифікації, хоча схему, яку ніхто інший у світі не використовує. Я буду дотримуватися стандартної зони інформації, дякую. (Важко зрозуміти, наскільки "Європа / Лондон" є безглуздим, наприклад.)
Джон Скіт

2
@ChrisMoschini: інший приклад: CST. Це UTC-5 чи UTC-6? Як щодо IST - це Ізраїль, Індія чи Ірландія у вашій базі даних? (І навіть якщо ви знаєте компенсацію прямо зараз, різні країни, які дотримуються однієї і тієї самої абревіатури, можуть змінюватися в різний час. Отже, все ще існує неоднозначність щодо того, який фактичний часовий пояс це означає. Часовий пояс! = Зміщення.) Повертаючись до своєї справи: ви заявляєте що використання абревіатур найкраще вирішило вашу проблему. Як би використання гірських стандартних ідентифікаторів часового поясу стало гіршим?
Джон Скіт

6
@ChrisMoschini: Ну, я продовжуватиму рекомендувати використовувати стандартні однозначні ідентифікатори zoneinfo, а не двозначні абревіатури. Це не питання, чию бібліотеку віддають перевагу - авторство бібліотеки насправді не є проблемою. Якщо хтось хоче використати іншу бібліотеку з хорошим вибором ідентифікатора, це добре. Вибір ідентифікатора для тимчасової зони є важливим , хоча, і я думаю , що це дуже важливо , щоб читачі знають , що абревіатури мають неоднозначні, так як я показав на прикладі IST.
Джон Скіт

54

Структура DateTimeOffset була створена саме для цього типу використання.

Дивіться: http://msdn.microsoft.com/en-us/library/system.datetimeoffset.aspx

Ось приклад створення об’єкта DateTimeOffset з певним часовим поясом:

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


1
Дякую, це хороший спосіб досягти цього. Після отримання об’єкта DateTimeOffset в потрібний часовий пояс, ви можете використовувати властивість .UtcDateTime, щоб отримати час UTC для створеного вами. Якщо ви зберігаєте свої дати в UTC, то перетворення їх на місцевий час для кожного користувача - це не велика справа :)
Redth,

2
Я не думаю, що це справляється з літнім часом, оскільки деякі TimeZones шанують його, а інші - ні. Також DST починається / закінчується "в день", частини цього дня будуть вимкнені.
crokusek

14
Урок DST - це правило певного часового поясу. DateTimeOffset не не не пов’язаний з жодним часовим поясом. Не плутайте значення зміщення UTC, наприклад -5, з часовим поясом. Це не часовий пояс, це зміщення. Один і той же зміщення часто поділяється багатьма часовими поясами, тому це неоднозначний спосіб посилання на часовий пояс. Оскільки DateTimeOffset пов'язаний зі зміщенням, а не часовим поясом, він не може застосовувати правила DST. Таким чином, 3 ранку буде 3 ранку кожного дня року, без винятку, у структурі DateTimeOffset (наприклад, у її властивості Hours та TimeOfDay).
Трайнко

Де можна заплутатися, якщо ви подивитеся на властивість LocalDateTime у DateTimeOffset. Це властивість НЕ DateTimeOffset, це екземпляр DateTime, вид якого - DateTimeKind.Local. Цей екземпляр пов'язаний з часовим поясом ... незалежно від часового поясу локальної системи. Ця властивість відображатиме економію денного світла.
Трайко

4
Отже, справжня проблема з DateTimeOffset полягає в тому, що він не містить достатньої кількості інформації. Він включає зміщення, а не часовий пояс. Зсув неоднозначний з кількома часовими поясами.
Трайко

41

Інші відповіді тут корисні, але вони не висвітлюють, як конкретно отримати доступ до Тихого океану - ось що:

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

Як не дивно, хоча "тихоокеанський стандартний час" зазвичай означає щось інше, ніж "тихоокеанський літній час", в цьому випадку він взагалі відноситься до тихоокеанського часу. Насправді, якщо ви використовуєте його FindSystemTimeZoneByIdдля отримання, одним із доступних властивостей є загальний сигнал, який повідомляє вам, чи перебуває цей часовий пояс у режимі денного світла чи ні.

Ви можете побачити більш узагальнені приклади цього в бібліотеці, яку я закінчив, збираючи разом, щоб розібратися з DateTimes, який мені потрібен в різних TimeZones, залежно від того, звідки запитує користувач тощо:

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

Це не працюватиме за межами Windows (наприклад, Mono в Linux), оскільки список разів надходить із реєстру Windows: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\

Під цим ви знайдете ключі (піктограми папок у редакторі реєстру); назви цих ключів - це те, до чого ви переходите FindSystemTimeZoneById. В Linux ви повинні використовувати окремий стандартний набір Linux для визначення часових поясів, який я не вивчив належним чином.


1
Додатково є ConvertTimeBySystemTimeZoneId () ex: TimeZoneInfo.ConvertTimeBySystemTimeZoneId (DateTime.UtcNow, "Центральний стандартний час")
Brent

У вікні ідентифікаторів TimeZone Id також можна побачити цю відповідь: stackoverflow.com/a/24460750/4573839
юнь Ян Цзянь

7

Я трохи змінив відповідь Джона Скета для Інтернету методом розширення. Він також працює на блакиті, як шарм.

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

Для цього доведеться створити спеціальний об’єкт. Ваш спеціальний об’єкт буде містити два значення:

  • значення DateTime
  • TimeZone об'єкт

Не впевнений, чи існує вже такий тип даних, який надається CLR, але принаймні компонент TimeZone вже доступний.


2

Мені подобається відповідь Джона Скіта, але я хотів би додати одне. Я не впевнений, чи очікував, що Джон завжди буде переданий в локальний часовий пояс. Але я хочу використовувати його для випадків, коли це щось інше, ніж місцеве.

Я читаю значення з бази даних, і я знаю, в якому часовому поясі знаходиться ця база даних. Отже, в ctor я перейду в часовий пояс бази даних. Але тоді я хотів би значення в місцевий час. Jon's LocalTime не повертає початкову дату, перетворену в місцеву дату часового поясу. Він повертає дату, перетворену в початковий часовий пояс (що б ви не передали в ctor).

Я думаю, що ці назви властивостей зрозуміли це ...

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

Використання класу TimeZones полегшує створення конкретної дати для часового поясу.

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

1
Вибачте, але це не доступно для Asp .NET Core 2.2 тут, VS2017 пропонує мені встановити пакет Outlook Nuget.
Мачадо

example => TimeZoneInfo.ConvertTime (DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById ("Тихоокеанський стандартний час"))
AZ_
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.