Добавление DateTimeOffset TimeSpan возвращает неверное смещение UTC для его TimeZoneInfo - PullRequest
0 голосов
/ 06 января 2010

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

Теперь я, похоже, застрял, так как не уверен, как получить откорректированные даты из объектов DateTimeOffset в правильное летнее время (DST), когда граница перехода пересекается, используя любое разнообразие .Добавить для перемещения дни, часы, и т.д.

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

Мне удалось найти фрагмент (не сохранивший источник, извините!), Который пытается отменить поиск информации о часовом поясе по смещению, но поскольку существует несколько потенциальных результатов, каждый из которых, вероятно, имеет разные правила перехода на летнее время, которые не будут Работа. (Возможны некоторые варианты оптимизации, но я думаю, что базовая предпосылка ошибочна)

public TimeZoneInfo GetTimeZoneInfo(DateTimeOffset Value)
{
    // Search available sytem time zones for a matching one
    foreach (var tzi in TimeZoneInfo.GetSystemTimeZones())
    {
        // Compare value offset with time zone offset
        if (tzi.GetUtcOffset(Value).Equals(Value.Offset))
        {
            return tzi;
        }
    }
}

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

public DateTimeOffset GetNextDay_Wrong(DateTimeOffset FromDateTimeOffset)
{
    // Cannot create a new DateTimeOffset using simply the supplied value's UtcOffset
    // because in PST, for example, it could be -7 or -8 depending on DST
    return new DateTimeOffset(FromDateTimeOffset.Date.AddDays(1), FromDateTimeOffset.Offset);
}

[TestMethod]
public void GetNextDay_WrongTest()
{
    var tz = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");

    var workingDate = new DateTime(2009, 11, 2, 0, 0, 0);
    var failingDate = new DateTime(2009, 11, 1, 0, 0, 0);

    var workingDate_tz = new DateTimeOffset(workingDate, tz.GetUtcOffset(workingDate));
    var failingDate_tz = new DateTimeOffset(failingDate, tz.GetUtcOffset(failingDate));

    var actual_workingDate_tz = GetNextDay_Wrong(workingDate_tz);
    var actual_failingDate_tz = GetNextDay_Wrong(failingDate_tz);

    var expected_workingDate = new DateTime(2009, 11, 3, 0, 0, 0);
    var expected_failingDate = new DateTime(2009, 11, 2, 0, 0, 0);

    var expected_workingDate_tz = new DateTimeOffset(expected_workingDate, tz.GetUtcOffset(expected_workingDate));
    var expected_failingDate_tz = new DateTimeOffset(expected_failingDate, tz.GetUtcOffset(expected_failingDate));

    Assert.AreEqual(expected_workingDate_tz, actual_workingDate_tz, "Should have found the following day's midnight");
    Assert.AreEqual(expected_failingDate_tz, actual_failingDate_tz, "Failing date does not have the correct offset for it's DST");
}

public DateTimeOffset GetNextDay_LooksRight(DateTimeOffset FromDateTimeOffset)
{
    // Because we cannot create a new DateTimeOffset we simply adjust the one provided!
    var temp = FromDateTimeOffset;
    // Move back to midnight of the current day
    temp = temp.Subtract(new TimeSpan(temp.Hour, temp.Minute, temp.Second));
    // Now move to the next day
    temp = temp.AddDays(1);
    // Let the DateTimeOffset class do it's magic
    temp = temp.ToLocalTime();
    // Check if the time zone has changed
    if (FromDateTimeOffset.Offset != temp.Offset)
    {
        // Calculate the change amount (could be 30 mins or even stranger)
        var delta = FromDateTimeOffset.Offset - temp.Offset;
        // Adjust the temp value by the delta
        temp = temp.Add(delta);
    }
    return temp.ToLocalTime();
}

[TestMethod]
public void GetNextDay_LooksRightTest()
{
    // Everything is looking good and the test passes now, so we're home free yeah?

    // { To work this needs to match your system's configured Local Time Zone, I'm in PST }
    var tz = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");

    var workingDate = new DateTime(2009, 11, 2, 0, 0, 0);
    var failingDate = new DateTime(2009, 11, 1, 0, 0, 0);

    var workingDate_tz = new DateTimeOffset(workingDate, tz.GetUtcOffset(workingDate));
    var failingDate_tz = new DateTimeOffset(failingDate, tz.GetUtcOffset(failingDate));

    var actual_workingDate_tz = GetNextDay_LooksRight(workingDate_tz);
    var actual_failingDate_tz = GetNextDay_LooksRight(failingDate_tz);

    var expected_workingDate = new DateTime(2009, 11, 3, 0, 0, 0);
    var expected_failingDate = new DateTime(2009, 11, 2, 0, 0, 0);

    var expected_workingDate_tz = new DateTimeOffset(expected_workingDate, tz.GetUtcOffset(expected_workingDate));
    var expected_failingDate_tz = new DateTimeOffset(expected_failingDate, tz.GetUtcOffset(expected_failingDate));

    Assert.AreEqual(expected_workingDate_tz, actual_workingDate_tz, "Should have found the following day's midnight");
    Assert.AreEqual(expected_failingDate_tz, actual_failingDate_tz, "Failing date does not have the correct offset for it's DST");
}

[TestMethod]
public void GetNextDay_LooksRight_FAILTest()
{
    // Here is where the frustrating part is... aparantly the "magic" that DateTimeOffset provides only works for your systems Local Time Zone...

    // { To properly fail this cannot match your system's configured Local Time Zone, I'm in PST so I use EST }
    var tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

    var workingDate = new DateTime(2009, 11, 2, 0, 0, 0);
    var failingDate = new DateTime(2009, 11, 1, 0, 0, 0);

    var workingDate_tz = new DateTimeOffset(workingDate, tz.GetUtcOffset(workingDate));
    var failingDate_tz = new DateTimeOffset(failingDate, tz.GetUtcOffset(failingDate));

    var actual_workingDate_tz = GetNextDay_LooksRight(workingDate_tz);
    var actual_failingDate_tz = GetNextDay_LooksRight(failingDate_tz);

    var expected_workingDate = new DateTime(2009, 11, 3, 0, 0, 0);
    var expected_failingDate = new DateTime(2009, 11, 2, 0, 0, 0);

    var expected_workingDate_tz = new DateTimeOffset(expected_workingDate, tz.GetUtcOffset(expected_workingDate));
    var expected_failingDate_tz = new DateTimeOffset(expected_failingDate, tz.GetUtcOffset(expected_failingDate));

    Assert.AreEqual(expected_workingDate_tz, actual_workingDate_tz, "Should have found the following day's midnight");
    Assert.AreEqual(expected_failingDate_tz, actual_failingDate_tz, "Failing date does not have the correct offset for it's DST");
}

1 Ответ

0 голосов
/ 12 января 2010

Основная проблема в этом случае заключается в том, что недостаточно информации для выполнения соответствующих преобразований часовых поясов. Простое смещение не является достаточно конкретным, чтобы вывести часовой пояс, потому что для данного смещения в определенный момент может быть несколько потенциальных часовых поясов. Поскольку объект DateTimeOffset не собирает и не хранит информацию о часовом поясе, с которым он был создан, он должен нести ответственность за поддержание этой связи чем-то внешним по отношению к этому классу.

То, что сбило меня с толку, было то, что вызов ToLocalTime (), который я позже понял, фактически вводил в расчет подразумеваемое TimeZoneInfo, то есть настроенного локального времени для машины, и я считаю, что внутренне DateTimeOffset преобразование в UTC путем простого удаления настроенного смещения и последующего создания нового класса DateTimeOffset с использованием конструктора, принимающего DateTime [в UTC] и TimeZoneInfo [из локальной системы], чтобы получить правильную полученную дату с учетом dst.

Учитывая это ограничение, я больше не вижу никакого значения в классе DateTimeOffset по сравнению с одинаково точной и более ценной комбинацией DateTime и TimeZoneInfo.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...