Создание DateTime в определенном часовом поясе в c #

162

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

В тесте мне нужно иметь возможность создавать объекты DateTime в не локальном часовом поясе, чтобы люди, выполняющие тест, могли делать это успешно независимо от того, где они находятся.

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

Как мне создать DateTime с определенным часовым поясом, таким как PST?

Джек Хьюз
источник
Связанный вопрос - stackoverflow.com/questions/2532729/…
Одед

Ответы:

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, поскольку они сообщают вам, что он должен находиться в этом часовом поясе.
IDosposable
2
@ChrisMoschini: В этот момент вы просто изобретаете свою собственную схему идентификации - схему, которую никто другой в мире не использует. Я буду придерживаться стандартного zoneinfo, спасибо. (Трудно понять, например, что «Европа / Лондон» бессмысленны.)
Джон Скит
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));

CleverPatrick
источник
1
Спасибо, это хороший способ сделать это. После того как вы получите объект DateTimeOffset в правильном часовом поясе, вы можете использовать свойство .UtcDateTime, чтобы получить время UTC для того, который вы создали. Если вы храните свои даты в UTC, то их преобразование в местное время для каждого пользователя не составляет
особого труда
2
Я не думаю, что это правильно обрабатывает переход на летнее время, поскольку некоторые часовые пояса соблюдают его, а другие нет. Также "в тот день" DST начинается / заканчивается, часть этого дня будет выключена.
Crokusek
14
Урок. Летнее время - это правило определенного часового пояса. DateTimeOffset не не не не связан ни с одним часовым поясом. Не путайте значение смещения UTC, например -5, с часовым поясом. Это не часовой пояс, это смещение. Одно и то же смещение часто используется многими часовыми поясами, поэтому это неоднозначный способ обращения к часовому поясу. Поскольку DateTimeOffset связан со смещением, а не с часовым поясом, он не может применять правила DST. Таким образом, 3:00 будет 3:00 каждый день года без исключения в структуре 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для извлечения, одним из доступных свойств будет bool, сообщающий вам, находится ли данный часовой пояс в летнее время или нет.

Вы можете увидеть более обобщенные примеры этого в библиотеке, которую я бросил вместе, чтобы иметь дело с DateTimes, которые мне нужны в разных часовых поясах, в зависимости от того, откуда пользователь спрашивает, и т.д .:

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 (), например: TimeZoneInfo.ConvertTimeBySystemTimeZoneId (DateTime.UtcNow, «Центральное стандартное время»)
Брент,
В Windows TimeZone Id List также можно увидеть этот ответ: 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 будет всегда передаваться в местном часовом поясе. Но я хочу использовать это для случаев, когда это что-то отличное от местного.

Я читаю значения из базы данных, и я знаю, в каком часовом поясе находится эта база данных. Поэтому в 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.
Мачадо
пример => TimeZoneInfo.ConvertTime (DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById («Тихоокеанское стандартное время»))
AZ_