С# дневной свет дубликат час конвертировать в UTC
Я использую TimeZoneInfo для преобразования между клиентским стендом "Восточное время" и UTC. Моя проблема связана с "повторяющимся" часом, который происходит во время осеннего перехода на летнее время.
Во время перехода от UTC к востоку:
2010-11-07 06:00 UTC → дает 2010-11-07T 01: 00: 00-03: 30
2010-11-07 07:00 UTC → дает 2010-11-07T 01: 00: 00-03: 30
Как я могу узнать, что такое первый час, а второй? DateTime.IsDaylightSavingTime() возвращает false для обоих часов, но не должен ли он возвращать true в течение первого часа?
Аналогично, как я могу хранить 2010-11-07 01:00:00 -03: 30? Как мое приложение может конвертировать в UTC, так как это может быть 2010-11-07 06: 00 или 2010-11-07 07: 00
Для тех, кто нуждается в коде, я езжу на велосипеде через datatable с столбцом datetime UTC, пытаясь конвертировать в Eastern с колонкой "DupHr" для второго повторяющегося часа, но я всегда заканчиваю оба 01:00 часов, DupHr '= 1.
TimeZoneInfo est = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime EasternTime;
DateTime DuplicateHour = new DateTime(2010, 11, 7, 1, 0, 0); // hard coded for this example
TimeZoneInfo.AdjustmentRule[] rules = est.GetAdjustmentRules();
foreach (DataRow row in dt.Rows)
{
row["DupHr"] = 0; // by default not duplicate hour
EasternTime = TimeZoneInfo.ConvertTimeFromUtc((DateTime)row[UTCColumnName], est);
if (!EasternTime.IsDaylightSavingTime())
{
if (EasternTime.Equals(DuplicateHour ))
{
row["DupHr"] = 1; // This is the second duplicate hour !
}
} else
EasternTime.Add(rules[1].DaylightDelta); // Add DST offset from rule #1
row[newESTColumnName] = EasternTime;
}
СПАСИБО!
Решение
Важно знать больше, чем "неоднозначное" для повторяющихся часов. Часы должны быть уникальными (первый и второй). Рассмотрите приложение счетчика денежных средств, которое должно работать 24x7 и суммировать каждую часовую коллекцию. Деньги, собранные в каждый час, должны быть идентифицируемы. Первый час с 1:00 до 1:59 отличается от второго 1:00 до 1:59 часа. Процедура ниже isSecondHour вернет значение true, только если прошедшее время находится в second часе осеннего перехода на летнее время. Пользовательский интерфейс может отображать этот флаг соответствующим образом.
// Get the DST rule for the year and zone (rules may change from year to year as in 2004)
public static TimeZoneInfo.AdjustmentRule GetDSTrule(int Year, TimeZoneInfo zone)
{
TimeZoneInfo.AdjustmentRule[] rules = zone.GetAdjustmentRules();
foreach (TimeZoneInfo.AdjustmentRule rul in rules)
{
if (rul.DateStart < new DateTime(Year, 1, 1) && rul.DateEnd > new DateTime(Year, 1, 1))
{
return rul;
}
}
return null;
}
// Determine if 'localtime' is in the second duplicate DST hour.
public static Boolean isSecondHour(TimeZoneInfo localzone, DateTime localtime, DateTime UTCtime)
{
if (localzone.IsAmbiguousTime(localtime))
{
TimeZoneInfo.AdjustmentRule rul = GetDSTrule(localtime.Year, localzone);
return UTCtime.Add(localzone.GetUtcOffset(localtime)) == localtime;
}
else
return false;
}
static void Main(string[] args)
{
var est = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var times = new DateTime[] {
new DateTime (2010, 11, 7, 3, 0, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 4, 0, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 5, 0, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 5, 30, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 6, 0, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 6, 30, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 7, 0, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 8, 0, 0, DateTimeKind.Unspecified)
};
DateTime EasternTime;
Console.WriteLine("UTC Time | Est Time | IsDaylightSaving | IsAmbiguousTime | isSecondHour ");
foreach (var utc in times)
{
// Get Eastern Time from UTC using standard convert routine.
EasternTime = TimeZoneInfo.ConvertTimeFromUtc(utc, est);
Console.WriteLine("{0:HH:mm} | {1:HH:mm} | {2,11} | {3,5} | {4,5}", utc,EasternTime, est.IsDaylightSavingTime(EasternTime), est.IsAmbiguousTime(EasternTime),isSecondHour(est,EasternTime, utc));
}
Результаты
UTC Time | Est Time | IsDaylightSaving | IsAmbiguousTime | isSecondHour
03:00 | 23:00 | True | False | False
04:00 | 00:00 | True | False | False
05:00 | 01:00 | False | True | False
05:30 | 01:30 | False | True | False
06:00 | 01:00 | False | True | True
06:30 | 01:30 | False | True | True
07:00 | 02:00 | False | False | False
08:00 | 03:00 | False | False | False
Ответы
Ответ 1
Резюме
Вы не можете знать, потому что, не сохраняя смещение, вы потеряли важную часть информации, часовой пояс, из которого первоначально было время, которое, как вы указали, может быть либо восточным стандартным временем, либо восточным дневным светом Время.
Обнаружение неопределенного времени
TimeZoneInfo предоставляет метод IsAmbiguousTime, чтобы проверить, может ли это быть.
Проблема с вашим обнаружением для этого неоднозначного времени заключается в том, что вы пытаетесь использовать IsDaylightSavings
, который возвращает false для неоднозначных времен, как показано в этом примере:
var est = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var times = new DateTime[] {
new DateTime (2010, 11, 7, 4, 0, 0, DateTimeKind.Utc),
new DateTime (2010, 11, 7, 5, 0, 0, DateTimeKind.Utc),
new DateTime (2010, 11, 7, 5, 30, 0, DateTimeKind.Utc),
new DateTime (2010, 11, 7, 6, 0, 0, DateTimeKind.Utc),
};
Console.WriteLine(" Time | IsDaylightSaving | IsAmbiguousTime");
foreach (var t in times) {
var time = TimeZoneInfo.ConvertTimeFromUtc(t, est);
Console.WriteLine (" {0:HH:mm} | {1,11} | {2,5}", time, est.IsDaylightSavingTime(time), est.IsAmbiguousTime(time));
}
Результат:
Time | IsDaylightSaving | IsAmbiguousTime
00:00 | True | False
01:00 | False | True
01:30 | False | True
01:00 | False | True
Итак, вы хотите использовать est.IsAmbiguousTime(EasternTime)
. Тогда нет необходимости в DuplicateHour
, так как это будет охватывать полный диапазон времени, который неоднозначен в этот день.
DateTimeOffset не страдает этой проблемой из-за того, что он явно сохраняет смещение.
Преобразование EST в UTC и сохранение в базе данных
Для вашего первоначального преобразования из EST в UTC существующих данных в базе данных вы захотите сохранить смещение для будущего использования. Для недвусмысленных времен это можно получить из часового пояса. Однако, как вы определили, для неоднозначных времен эта информация будет недоступна. Для этих времен вам придется предположить, какое смещение использовать и флага времени в БД, как подозреваемый, поэтому пользовательский интерфейс может соответствующим образом реагировать при отображении этих времен.
В зависимости от того, сколько данных затронуто, может не стоить усилий по изменению пользовательского интерфейса и просто игнорировать проблему, особенно если это действительно не так важно для пользователя, если время вышло на час (с тех пор на экране пользователя в этом часовом поясе он будет отображаться как 1 час). БД все равно будет записывать, что время было подозрительным, если вы когда-нибудь передумали.
Преобразование из UTC в EST и обнаружение неоднозначных времен
Во-первых, используйте DateTimeOffset, так как это может указать разницу между 1am EST и 1am EDT. В этот момент TimeZoneInfo.IsAmbiguousTime(DateTimeOffset)
можно использовать, чтобы выделить дублирующиеся времена на экране, а TimeZoneInfo.IsDaylightSavings(DateTimeOffset)
также правильно вернет true или false.
var est = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var times = new DateTimeOffset[] {
new DateTimeOffset (2010, 11, 7, 4, 00, 0, TimeSpan.Zero),
new DateTimeOffset (2010, 11, 7, 5, 00, 0, TimeSpan.Zero),
new DateTimeOffset (2010, 11, 7, 5, 30, 0, TimeSpan.Zero),
new DateTimeOffset (2010, 11, 7, 6, 00, 0, TimeSpan.Zero),
};
Console.WriteLine(" Time | IsDaylightSaving | IsAmbiguousTime");
foreach (var t in times) {
var time = TimeZoneInfo.ConvertTime (t, est);
Console.WriteLine (" {0:HH:mm} | {1,11} | {2,5}", time, est.IsDaylightSavingTime(time), est.IsAmbiguousTime(time));
}
Результат:
Time | IsDaylightSaving | IsAmbiguousTime
00:00 | True | False
01:00 | True | True
01:30 | True | True
01:00 | False | True
Будущие соображения
Проблемы с пользовательским интерфейсом
При отображении пользователю не имеет значения, является ли местное время неоднозначным или нет (повторяющийся час). Вы можете просто преобразовать время UTC в свой часовой пояс и форматировать в виде строки. Вы можете проверить IsAmbiguousTime, чтобы отобразить подсказку для пользователя, почему они могут видеть "1am" дважды. Сортировка информации по дате должна быть выполнена с использованием UTC. Переход от UTC к местному времени никогда не должен быть двусмысленным, так как каждый момент времени существует только один раз в UTC, нет повторяющихся часов.
Итак, единственная проблема сейчас - это время, когда пользователь вводит время, и вам нужно понять, какое время они имели в виду, так как пользователь вряд ли вступает в зачет или даже заботится о таких деталях. К сожалению, нет простого способа справиться с этим, и, не пытаясь научить ваших пользователей смещениям, они будут ошибаться и ошибаться. Например, они могут войти в 4 утра, думая 4 часа за полночь, забыв, что в ту ночь есть 1 час дополнительно/меньше. В качестве альтернативы они могут входить в 3 часа в день, когда часы идут вперед в 3 часа ночи, что в этот день - это время, которого просто не существует.
К счастью, время, в течение которого часы меняются, направлено на минимизацию проблемы ввода пользователя, поскольку большинство людей спят. Таким образом, система может принять наилучшее предположение и иногда принимать время на час. Если это действительно имеет значение, вы можете проверить, есть ли в этот день летнее время и показать другой интерфейс с предупреждением/подсказкой.
Хранение и передача
Если вы сохраняете время на сервере MSSQL, рекомендуется datetimeoffset, поскольку это может обрабатывать сохранение как времени, так и смещения. При использовании этого типа сервер MSSQL может обрабатывать время сравнения с различными смещениями правильно.
Для баз данных, которые не поддерживают такой тип, вы можете сохранить время в UTC в базе данных и сохранить смещение за это время в отдельном столбце. Это позволит вам точно знать местное время, в которое оно было записано.
При обмене с внешними системами, в идеале, переносите время как локальное в формате yyyy-MM-dd HH:mm:sszzzz
(например, 2010-11-07 01:00:00-03:30
), чтобы сохранить время и смещение. В противном случае UTC, как правило, лучший выбор, но в идеале должен быть суффикс с "Z" или "+00: 00", чтобы сделать это очевидным.
В памяти класс DateTimeOffset является лучшим выбором, поскольку он может представлять любое произвольное смещение по сравнению с DateTime, которое может представлять только UTC или локальное время системы.
Обратите внимание, что точность дневного света TimeZoneInfo зависит от версии ОС, пакетов обновлений и обновленных окон.
Кроме того, важно, как применяется дневная экономия. Если они применяются ОС, используя "Автоматически настраивать часы для экономии дневного света", тогда смещение будет правильно настроено. Если администратор отключил это и вручную отрегулировал время, добавив/вычитая час, ОС не будет знать об этом и будет работать с неправильным смещением. См. TimeZoneInfo.Local для других примечаний относительно этой настройки ОС.
Ответ 2
Итак, после всего этого обсуждения у меня теперь есть две процедуры, одна из которых указывает, является ли время UTC моим местным часовым поясом "Duplicate" hour и программой для преобразования Eastern в UTC. При получении данных клиентское приложение может использовать процедуру isSecondHour, чтобы отобразить время соответствующим образом. Аналогично, при сохранении времени на сервере клиент должен предоставить флаг second_DST_hour вместе с местными временами, чтобы их можно было преобразовать в UTC.
// Determine if 'localtime' is in the second duplicate DST hour.
public static Boolean isSecondHour(TimeZoneInfo localzone, DateTime localtime, DateTime UTCtime)
{
if (localzone.IsAmbiguousTime(localtime))
{
// UTC time + UTC offset = second hour time (not first hour)
return UTCtime.Add(localzone.GetUtcOffset(localtime)) == localtime;
}
else
return false;
}
// Convert Local time to UTC, with 'SecondDST' indicating if hour is the second hour of autumn DST change.
public static DateTime Convert_to_UTC(TimeZoneInfo localzone, DateTime localtime, Boolean SecondDST)
{
DateTime newUTC = TimeZoneInfo.ConvertTimeToUtc(localtime, localzone);
if (localzone.IsAmbiguousTime(localtime) && !SecondDST)
{
TimeZoneInfo.AdjustmentRule rul = GetDSTrule(localtime.Year, localzone);
return newUTC.Add(-rul.DaylightDelta);
}
else
return newUTC;
}
// Get the DST rule for the year and zone (rules may change from year to year as in 2004)
public static TimeZoneInfo.AdjustmentRule GetDSTrule(int Year, TimeZoneInfo zone)
{
TimeZoneInfo.AdjustmentRule[] rules = zone.GetAdjustmentRules();
foreach (TimeZoneInfo.AdjustmentRule rul in rules)
{
if (rul.DateStart < new DateTime(Year, 1, 1) && rul.DateEnd > new DateTime(Year, 1, 1))
{
return rul;
}
}
return null;
}
И затем использовать их:
var est = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var times = new DateTime[] {
new DateTime (2010, 11, 7, 3, 0, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 4, 0, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 5, 0, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 5, 30, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 6, 0, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 6, 30, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 7, 0, 0, DateTimeKind.Unspecified),
new DateTime (2010, 11, 7, 8, 0, 0, DateTimeKind.Unspecified)
};
// ------------------ UTC to Eastern
DateTime EasternTime;
Console.WriteLine("UTC Time | Est Time | IsDaylightSaving | IsAmbiguousTime | isSecondHour ");
foreach (var utc in times)
{
// Get Eastern Time from UTC using standard convert routine.
EasternTime = TimeZoneInfo.ConvertTimeFromUtc(utc, est);
Console.WriteLine("{0:HH:mm} | {1:HH:mm} | {2,11} | {3,5} | {4,5}", utc,EasternTime, est.IsDaylightSavingTime(EasternTime), est.IsAmbiguousTime(EasternTime),isSecondHour(est,EasternTime, utc));
}
// ------------------ Eastern to UTC
DateTime testTime;
Console.WriteLine("UTC Time | Est Time | IsDaylightSaving | IsAmbiguousTime | isSecondHour ");
EasternTime = new DateTime(2010, 11, 7, 1, 30, 0, DateTimeKind.Unspecified);
// First Hour of DST
testTime = Convert_to_UTC (est, EasternTime,false);
Console.WriteLine("{0:HH:mm} | {1:HH:mm} | {2,11} | {3,5} | {4,5}", testTime, EasternTime, est.IsDaylightSavingTime(EasternTime), est.IsAmbiguousTime(EasternTime), isSecondHour(est, EasternTime, testTime));
// Second Hour of DST
testTime = Convert_to_UTC(est, EasternTime, true);
Console.WriteLine("{0:HH:mm} | {1:HH:mm} | {2,11} | {3,5} | {4,5}", testTime, EasternTime, est.IsDaylightSavingTime(EasternTime), est.IsAmbiguousTime(EasternTime), isSecondHour(est, EasternTime, testTime));