c # переход на летнее время повторяется час преобразования в UTC - PullRequest
15 голосов
/ 02 июня 2011

Я использую 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

Для тех, кому нужен код, я циклически перебираю таблицу данных со столбцом даты и времени UTC, пытаясь преобразовать в восточный столбец '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;        
    }

СПАСИБО!

Решение

Важно знать больше, чем «двусмысленность» для повторяющихся часов. Часы должны быть уникальными (первое и второе). Рассмотрим приложение для подсчета денег на платных будках, которое должно работать круглосуточно и суммировать сбор за каждый час. Деньги, собранные за каждый час, должны быть идентифицируемыми. Первый час с 1:00 до 1:59 отличается от второго часа с 1:00 до 1:59. Подпрограмма ниже isSecondHour вернет истину, только если время прошло в секунде часе изменения летнего времени осени. Пользовательский интерфейс может отображать этот флаг соответствующим образом.

 // 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

Ответы [ 2 ]

11 голосов
/ 02 июня 2011

Резюме

Вы не можете знать, потому что, не сохраняя смещение, вы потеряли важную часть информации, часовой пояс, в котором изначально было время, которое, как вы указали, может быть либо по восточному стандартному времени, либо по восточному дневному свету. Время.

Обнаружение неоднозначного времени

TimeZoneInfo предоставляет метод IsAmbiguousTime , чтобы проверить, может ли это быть так.

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

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:00). БД все равно запишет, что время было подозрительным, если вы когда-нибудь потом передумали.

Преобразование из UTC в EST и обнаружение неоднозначного времени

Во-первых, используйте DateTimeOffset, так как это может показать разницу между 1:00 EST и 1:00 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, чтобы отобразить подсказку для пользователя, почему он может видеть «1:00» дважды. Сортировка информации по дате должна производиться с использованием 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 зависит от версии ОС, пакетов обновления и примененных обновлений Windows.

Кроме того, важно, как применяется летнее время. Если они применяются ОС с использованием «Автоматически настраивать часы для перехода на летнее время», тогда смещение будет корректно отрегулировано. Если администратор отключил это и вручную настраивает время, добавляя / вычитая час, ОС не узнает об этом и будет работать с неправильным смещением. См. TimeZoneInfo.Local для других примечаний относительно этой настройки ОС.

0 голосов
/ 03 июня 2011

Итак, после всего этого обсуждения у меня теперь есть две подпрограммы, одна из которых указывает, является ли время UTC «дублирующим» часом моего местного часового пояса, и подпрограмма для преобразования восточного значения в 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));
...