Безопасная обработка летнего времени (или любого другого теоретического непостоянного смещения) при расчете длительности между датами времени - PullRequest
1 голос
/ 21 января 2020

Я знаю, что это не первый раз, когда эта topi c поднималась даже за последние 24 часа, но я удивлен, что не нашел ни одного ясного / наилучшего решения этой проблемы. Проблема также, кажется, противоречит тому, что я думал, было простым решением для сохранения всех дат в UT C. Я попытаюсь сформулировать проблему здесь:

Учитывая два объекта DateTime, найдите длительность между ними, учитывая переход на летнее время.

Рассмотрим следующий сценарий ios:

  1. UtcDate - LocalDate, где LocalDate на 1 миллисекунду раньше, чем переключение DST.

  2. LocalDateA - LocalDateB, где LocalDateB на 1 миллисекунду раньше, чем переключение DST.

UtcDate - LocalDate.ToUt c () предоставляет продолжительность, которая не учитывает переключение DST. LocalDateA.ToUt c () - LocalDateB.ToUt c () является правильным, но LocalDateA - LocalDateB также игнорирует DST.

Теперь, очевидно, есть решения этой проблемы. Решение, которое я использую сейчас, - это метод расширения:

public static TimeSpan Subtract(this DateTime minuend, TimeZoneInfo minuendTimeZone, 
    DateTime subtrahend, TimeZoneInfo subtrahendTimeZone)
{
    return TimeZoneInfo.ConvertTimeToUtc(DateTime.SpecifyKind(minuend, 
        DateTimeKind.Unspecified), minuendTimeZone)
        .Subtract(TimeZoneInfo.ConvertTimeToUtc(DateTime.SpecifyKind(subtrahend, 
            DateTimeKind.Unspecified), subtrahendTimeZone));
}

Я думаю, он работает. У меня есть некоторые проблемы с этим, хотя:

  1. Если даты будут преобразованы в UT C перед сохранением, то этот метод не поможет. Информация о часовом поясе (и любая обработка DST) теряется. Я был условно всегда сохранять даты в UT C, разве вопрос о летнем времени недостаточно эффективен, чтобы принять это плохое решение?

  2. Маловероятно, что кто-то будет в курсе этого метода, или даже думать об этой проблеме, при расчете разницы между датами. Есть ли более безопасное решение?

  3. Если мы все будем работать вместе, может быть, техническая индустрия сможет убедить конгресс отменить sh летнее время.

1 Ответ

1 голос
/ 22 января 2020

Как вы указали, это обсуждалось ранее. Здесь и здесь - два хороших сообщения для обзора.

Кроме того, документация на DateTime.Subtract имеет следующее:

Метод Subtract(DateTime) не учитывает значение свойства Kind двух значений DateTime при выполнении вычитания. Перед вычитанием DateTime объектов убедитесь, что объекты представляют время в одном часовом поясе. В противном случае результат будет включать разницу между часовыми поясами.

Примечание

Метод DateTimeOffset.Subtract(DateTimeOffset) учитывает разницу между часовыми поясами, когда выполняя вычитание.

За пределами просто «представлять время в одном часовом поясе», имейте в виду, что даже если объекты находятся в одном часовом поясе, вычитание значений DateTime все равно не будет рассмотрим DST или другие переходы между двумя объектами.

Ключевым моментом является то, что для определения прошедшего времени вы должны вычитать абсолютных точек во времени . Они лучше всего представлены DateTimeOffset in. NET.

Если у вас уже есть значения DateTimeOffset, вы можете просто вычесть их. Тем не менее, вы все равно можете работать со значениями DateTime до тех пор, пока вы сначала правильно их преобразуете в DateTimeOffset.

В качестве альтернативы вы можете преобразовать все в UT C - но вам придется go - DateTimeOffset или аналогичный код, чтобы сделать это правильно в любом случае.

В вашем случае вы можете изменить свой код на следующее:

public static TimeSpan Subtract(this DateTime minuend, TimeZoneInfo minuendTimeZone, 
    DateTime subtrahend, TimeZoneInfo subtrahendTimeZone)
{
    return minuend.ToDateTimeOffset(minuendTimeZone) -
        subtrahend.ToDateTimeOffset(subtrahendTimeZone);
}

Вам также понадобится ToDateTimeOffset метод расширения (, который я также использовал в других ответах ).

public static DateTimeOffset ToDateTimeOffset(this DateTime dt, TimeZoneInfo tz)
{
    if (dt.Kind != DateTimeKind.Unspecified)
    {
        // Handle UTC or Local kinds (regular and hidden 4th kind)
        DateTimeOffset dto = new DateTimeOffset(dt.ToUniversalTime(), TimeSpan.Zero);
        return TimeZoneInfo.ConvertTime(dto, tz);
    }

    if (tz.IsAmbiguousTime(dt))
    {
        // Prefer the daylight offset, because it comes first sequentially (1:30 ET becomes 1:30 EDT)
        TimeSpan[] offsets = tz.GetAmbiguousTimeOffsets(dt);
        TimeSpan offset = offsets[0] > offsets[1] ? offsets[0] : offsets[1];
        return new DateTimeOffset(dt, offset);
    }

    if (tz.IsInvalidTime(dt))
    {
        // Advance by the gap, and return with the daylight offset  (2:30 ET becomes 3:30 EDT)
        TimeSpan[] offsets = { tz.GetUtcOffset(dt.AddDays(-1)), tz.GetUtcOffset(dt.AddDays(1)) };
        TimeSpan gap = offsets[1] - offsets[0];
        return new DateTimeOffset(dt.Add(gap), offsets[1]);
    }

    // Simple case
    return new DateTimeOffset(dt, tz.GetUtcOffset(dt));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...