Я пытаюсь создать модульный тест, чтобы проверить случай, когда часовой пояс меняется на машине, потому что он был неправильно установлен, а затем исправлен.
В тесте мне нужно иметь возможность создавать объекты DateTime в часовом поясе, отличном от локального, чтобы люди, выполняющие тест, могли успешно выполнять это независимо от того, где они находятся.
Из того, что я вижу в конструкторе DateTime, я могу установить для TimeZone либо местный часовой пояс, либо часовой пояс UTC, либо не указанный.
Как мне создать DateTime с определенным часовым поясом, например PST?





Для этого вам нужно будет создать собственный объект. Ваш настраиваемый объект будет содержать два значения:
Не уверен, существует ли уже предоставленный CLR тип данных, который имеет это, но, по крайней мере, компонент 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», чтобы было понятнее - я сам предпочитаю более короткие имена.
будет ли такая структура отображаться на интерфейс IQueryable на LinqToSql? coz каждый раз, когда я пытался использовать другой объект вместо DateTime или DateTime? на моем сопоставлении LinqToSql он терпит неудачу, когда я запрашиваю его .. Вы, наверное, знаете исключение: "..не удается преобразовать в SQL .."
Боюсь, я не знаю ни одной эквивалентной конструкции SQL Server. Я бы предложил указать имя часового пояса в одном столбце, а значение UTC в другом столбце. Получите их по отдельности, и тогда вы сможете довольно легко создавать экземпляры.
Не уверен в ожидаемом использовании конструктора, который принимает DateTime и TimeZoneInfo, но, учитывая, что вы вызываете метод dateTime.ToUniversalTime (), я подозреваю, что вы предполагаете, что «возможно» он будет в местном времени. В этом случае, я думаю, вам действительно следует использовать переданный TimeZoneInfo, чтобы преобразовать его в UTC, поскольку они говорят вам, что он должен быть в этом часовом поясе.
+1 - только что заметил ошибку в свойстве LocalTime - переменная должна быть «utcDateTime», а не «utcTime».
@JonSkeet спасибо за ответ. представлена хорошая идея. небольшое примечание: вы устанавливаете частные поля как только чтение и даете им значение из .ctor, но типом является struct, а struct всегда имеют значение по умолчанию .ctor. С Уважением.
@Javad_Amiry: Да, это означает, что "значение по умолчанию" структуры, к сожалению, невозможно использовать. Вы потенциально можете сделать нулевой часовой пояс эквивалентом (скажем) UTC.
Я создал библиотеку, которая использует аналогичный объект и помогает отображать штаты / страны в часовые пояса, конвертировать между ними и т. д. github.com/b9chris/TimeZoneInfoLib.Net - demo: timezoneinfolib.brass9.com
@ChrisMoschini: Я бы на самом деле старался избегать использования сокращений, если это возможно. Они неоднозначны. Я также утверждаю, что имя «UtcTimeZone» на самом деле не то, что представляет ваш класс. Я ожидал, что это будет некий подкласс абстрактного типа часового пояса с некоторым описанием, представляющий зону UTC.
Старый комментарий, но в конечном итоге я сохранил сокращения в своей библиотеке. Я считаю их очень полезными как в коде, так и в простом хранилище в базе данных (просто сохраните UTC и короткое имя часового пояса - DateTime и String, чтобы решить проблему @cottsak). Я считаю их очень удобочитаемыми, а не запутанными - но именно поэтому для многих задач существует более одной библиотеки - неудивительно, что авторы предпочитают свои собственные библиотеки.
@ChrisMoschini: Как вы справляетесь с тем фактом, что сокращения не уникальны? На самом деле они не являются часовым поясом идентифицировать. Если ваш код используется только в США, это может быть нормально, но если вы когда-нибудь захотите расшириться в другом месте, я думаю, что будет разумно иметь уникальные идентификаторы.
@JonSkeet Вы просто используете уникальные имена для каждого часового пояса - не сложно. Например, и в США, и в Австралии используется восточное стандартное время, поэтому вы используете EST для США и AUEST для Австралии. Вы по-прежнему можете отображать для пользователя стандартные, неуникальные сокращения, если хотите, чтобы в этом не было никакого вреда, и, как я уже сказал, это делает данные и код намного проще для чтения, чем какой-то сумасшедший числовой идентификатор или другой бессмысленный, но уникальный идентификатор. .
@ChrisMoschini: В этот момент вы просто изобретаете свою собственную схему идентификации - схему, которую никто в мире не использует. Я буду придерживаться стандартной информации о зоне, спасибо. (Например, трудно понять, насколько бессмысленно «Европа / Лондон».)
@JonSkeet Да, как я уже сказал, неудивительно, что авторы предпочитают собственные библиотеки. Мой подход позволяет легко хранить в базе данных и легко проверять быстро - я предпочитаю это, без сюрпризов.
@ChrisMoschini: Безусловно, есть разница между «предпочитать собственную библиотеку» и «предпочитать общеотраслевой стандарт». Учитывая выбор между двумя библиотеками, в которых я участвовал не имел, я бы предпочел ту, которая использует стандартизированные идентификаторы. Это не случай NIH-синдрома.
@JonSkeet На самом деле существует стандарт для этих сокращений: timeanddate.com/library/abbreviations/timezones (и мои извинения, Aus EST есть AEST, а не AUEST). Так что по вашим меркам разницы нет. Отличие заключается в том, что написанная мною библиотека лучше всего решает проблему, которую я искал, и, что неудивительно, я предпочитаю ее. Вы предпочитаете свое. Не думаю, что есть что еще сказать. У нас обоих могут быть свои библиотеки, кому бы они ни помогали, они помогают, мы не зарабатываем на них денег, поэтому нет особого смысла конкурировать. Я надеюсь, что люди сочтут их оба полезными.
@ChrisMoschini: Да, есть сокращения, которые неоднозначны, поэтому не должны использоваться в качестве идентификаторов. (Например, CET.) Чтобы избежать двусмысленности, вы должны были отойти от стандарта. Существует значительная разница между тем, кто использует вашу библиотеку, и тем, кто использует Noda Time: любой, кто использует Noda Time, может взаимодействовать с любой другой системой, использующей TZDB. Их очень много. Сколько других систем используют ваш однозначный набор сокращений? (Есть и другие преимущества с точки зрения определения часового пояса, а не "половины" часового пояса (например, BST).)
Возможно, вы смотрите слишком быстро - CET означает то же самое - это всегда UTC + 1. Никакой двусмысленности.
@ChrisMoschini: Тогда другой пример: CST. Это UTC-5 или UTC-6? Как насчет IST - это Израиль, Индия или Ирландия в вашей базе данных? (И даже если вы знаете смещение прямо сейчас, разные страны, использующие одну и ту же аббревиатуру, могут меняться в разное время. Так что остается неясность, какой именно часовой пояс это означает. Часовой пояс! = Смещение.) Возвращаясь к вашему случаю: вы утверждаете, что что использование сокращений лучше всего решило вашу проблему. Чем было бы хуже использовать стандартные отраслевые идентификаторы часовых поясов?
Я не пытаюсь кодировать регион, просто шаблон DST. И для проблемы, которую я пытаюсь решить, мы используем небольшое количество однозначных, приблизительных часовых поясов (если вы посмотрите на код, я заметил, что я даже не беспокоюсь о сложностях штата Индиана). Проще проверить и легче хранить. Это обсуждение продолжается слишком долго, поэтому я перестану отвечать здесь. Если вам не нравится моя библиотека, это нормально, но я не собираюсь заставлять ее работать так же, как ваша. Это решает проблему, которую я намеревался решить. Если решает чужое ... круто.
@ChrisMoschini: Что ж, я продолжу рекомендовать использовать стандартные однозначные идентификаторы zoneinfo вместо неоднозначных сокращений. Это не вопрос, чья библиотека предпочтительнее - авторство библиотеки действительно не является проблемой. Если кто-то желает использовать другую библиотеку с выбором идентификатора хорошо, это нормально. Однако выбор идентификатора для часового пояса является важным, и я думаю, что очень важно, чтобы читатели знали, что сокращения являются неоднозначны, как я показал на примере IST.
@JonSkeet Я установил часовой пояс как стандартное время Индии, и когда я пробую LocalTime.ToString (), вместо 13.12.2019 16:00:00 я получаю 13.12.2019 16:00:00 . Мне нужно установить это преобразование на основе часового пояса.
@Gopi: Вместо того, чтобы добавлять комментарий к сообщению, написанному более 10 лет назад, задайте новый вопрос с более подробной информацией, но имейте в виду, что формат и часовой пояс - это совершенно разные вещи.
@JonSkeet Спасибо. Согласовано, Формат и Часовой пояс - это разные вещи, но я намерен отформатировать дату на основе часового пояса. Я создал отдельный вопрос. stackoverflow.com/questions/59279009/…
Структура 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));
Спасибо, это хороший способ добиться этого. После того, как вы получите объект DateTimeOffset в правильном часовом поясе, вы можете использовать свойство .UtcDateTime, чтобы получить время в формате UTC для созданного вами. Если вы храните свои даты в UTC, то конвертировать их в местное время для каждого пользователя не составит труда :)
Я не думаю, что это правильно работает с переходом на летнее время, поскольку некоторые часовые пояса соблюдают его, а другие нет. Кроме того, «в тот день», когда летнее время начинается / заканчивается, части этого дня будут отключены.
Урок. Летнее время - это правило определенного часового пояса. DateTimeOffset не не связан не с каким часовым поясом. Не путайте значение смещения UTC, например -5, с часовым поясом. Это не часовой пояс, это смещение. Одно и то же смещение часто используется во многих часовых поясах, поэтому это неоднозначный способ обозначения часового пояса. Поскольку DateTimeOffset связан со смещением, а не с часовым поясом, он не может применять правила DST. Таким образом, 3:00 будет 3:00 в каждый день в году, без исключения в структуре DateTimeOffset (например, в ее свойствах Hours и TimeOfDay).
Вы можете запутаться, если посмотрите на свойство LocalDateTime DateTimeOffset. Это свойство НЕ является DateTimeOffset, это экземпляр DateTime с типом DateTimeKind.Local. Этот экземпляр связан с часовым поясом ... независимо от часового пояса локальной системы. Это свойство БУДЕТ отражать переход на летнее время.
Итак, настоящая проблема DateTimeOffset в том, что он не содержит достаточно информации. Он включает смещение, а не часовой пояс. Смещение неоднозначно для нескольких часовых поясов.
Если бы у него был определенный часовой пояс, связанный с ним, у него не только было бы смещение, но также были бы все правила, связанные с этим часовым поясом, включая время начала и окончания летнего времени (если он даже поддерживает его), величина смещения (обычно всего 1 час), независимо от того, начинается ли оно в фиксированную дату или гибкую дату, например, 1-е воскресенье марта, и даже является ли конкретный момент времени недействительным, неоднозначным или находится в пределах диапазона летнего времени. . Вам нужен TimeZoneInfo для всей информации. И теперь вы знаете, почему приведенная выше структура Джона Скита потрясающая.
Другие ответы здесь полезны, но они не охватывают конкретно, как получить доступ к Тихоокеанскому региону - вот вам:
public static DateTime GmtToPacific(DateTime dateTime)
{
return TimeZoneInfo.ConvertTimeFromUtc(dateTime,
TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"));
}
Как ни странно, хотя «тихоокеанское стандартное время» обычно означает нечто иное, чем «тихоокеанское летнее время», в данном случае оно относится к тихоокеанскому времени в целом. Фактически, если вы используете FindSystemTimeZoneById для его получения, одно из доступных свойств - это логическое значение, указывающее, находится ли этот часовой пояс в настоящее время в летнем или нет.
Вы можете увидеть более общие примеры этого в библиотеке, которую я собрал вместе, чтобы иметь дело с DateTimes, которые мне нужны в разных часовых поясах, в зависимости от того, откуда пользователь запрашивает, и т. Д .:
https://github.com/b9chris/TimeZoneInfoLib.Net
Это не будет работать вне Windows (например, Mono в Linux), поскольку список времен взят из реестра Windows:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
Под ним вы найдете ключи (значки папок в редакторе реестра); имена этих ключей - это то, что вы передаете FindSystemTimeZoneById. В Linux вам нужно использовать отдельный стандартный набор определений часовых поясов Linux, который я недостаточно изучил.
Кроме того, существует ConvertTimeBySystemTimeZoneId (), например: TimeZoneInfo.ConvertTimeBySystemTimeZoneId (DateTime.UtcNow, «Центральное стандартное время»)
В окнах Список идентификаторов часовых поясов тоже можно увидеть этот ответ: stackoverflow.com/a/24460750/4573839
Мне нравится ответ Джона Скита, но я хотел бы добавить одну вещь. Я не уверен, ожидал ли Джон, что ctor всегда будет передаваться в местном часовом поясе. Но я хочу использовать его в тех случаях, когда это что-то другое, чем местное.
Я читаю значения из базы данных и знаю, в каком часовом поясе находится эта база данных. Итак, в ctor я передам часовой пояс базы данных. Но тогда я бы хотел значение по местному времени. 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);
}
Я немного изменил Джон Скит ответ для Интернета с помощью метода расширения. Он также действует на лазурь, как амулет.
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);
}
}
Использование класса Часовые пояса упрощает создание даты для конкретного часового пояса.
TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById(TimeZones.Paris.Id));
Извините, но он недоступен в Asp .NET Core 2.2 здесь, VS2017 предлагает мне установить пакет Outlook Nuget.
пример => TimeZoneInfo.ConvertTime (DateTime.Now, TimeZoneInfo.FindSystemTimeZoneById ("Стандартное тихоокеанское время"))
Связанный вопрос - stackoverflow.com/questions/2532729/…