Разбор DateTime с известным, но не заданным часовым поясом - PullRequest
6 голосов
/ 30 августа 2011

Я застрял с проблемой разбора даты и времени:

Я пытаюсь разобрать строку даты и времени, извлеченную из немецкого веб-сайта.Он задается в формате «день.монт.год 24 часа: минуты», например:

01.01.2011 17:00

И всегда в немецком часовом поясе.Но тут возникает проблема:

  • '01 .01.2011 17:00 'должен быть проанализирован в структуре DateTime с' 01. 01.2011 16:00 'в UTC (здесь, часовой пояс CET, без переход на летнее время)
  • , в то время как '01 .06.2011 17:00 'следует анализировать в структуре DateTime с '01 .01.2011 15:00' в UTC (здесь часовой пояс CEST с летнее время)

Понятия не имею, как этого добиться.Если я установлю свои локальные часы на немецкий часовой пояс и проанализирую с DateTime.ParseExact и флагом DateTimeStyles.AssumeLocal и DateTimeStyles.AdjustToUniversal, то они будут проанализированы правильно.Однако я хочу, чтобы любой клиент анализировал его независимо от своих часов и часового пояса.Кроме того, я не хочу сам делать смещение часового пояса, потому что оно зависит от даты (лето: -2 / зима: -1).

Если у меня есть дата и время в формате UTC, его будет легко конвертировать.в любой местный часовой пояс.

Ответы [ 2 ]

4 голосов
/ 30 августа 2011

Звучит так, будто вы знаете, с каким часовым поясом вы должны его анализировать.Предполагая .NET 3.5 (и, следовательно, TimeZoneInfo), вы должны логически:

  • Анализировать его как "местное" время (не зависит от часового пояса)
  • Преобразовать это местное время вВремя UTC

К сожалению DateTime делает это немного хитрым .РЕДАКТИРОВАТЬ: Я думал, вы хотели бы преобразовать, проанализировать его, используя DateTimeStyles.AssumeUniversal - но это в конечном итоге возвращает локальный DateTime, досадно.В основном вы хотите получить a DateTime с правильным временем, чтобы вы могли использовать:

parsed = DateTime.SpecifyKind(parsed, DateTimeKind.Unspecified);

Затем вы можете получить значение UTC с помощью:

DateTime utc = TimeZoneInfo.ConvertTimeToUtc(parsed, germanTimeZone);

Обратите внимание, что вам действительно нужно сначала указать «неопределенное» время, чтобы вы могли преобразовать его в UTC в произвольном часовом поясе.Также следует помнить о том, что местное время неоднозначно (встречается дважды) или невозможно (вообще не происходит) из-за изменений летнего времени.

И да, это будет намного проще в Время Нода когда оно закончится:)

1 голос
/ 31 августа 2011

Убедившись, что задача не может быть архивирована с помощью инфраструктуры WP7 / Silverlight, я написал небольшой помощник, который выполняет эту работу:

public static class DateTimeHelper
{
    /// <summary>
    /// Tries to parse the given datetime string that is not annotated with a timezone 
    /// information but known to be in the CET/CEST zone and returns a DateTime struct
    /// in UTC (so it can be converted to the devices local time). If it could not be 
    /// parsed, result contains the current date/time in UTC.
    /// </summary>
    public static bool TryParseCetCest(string s, string format, IFormatProvider provider, DateTimeStyles style, out DateTime result)
    {
        // Parse datetime, knowing it is in CET/CEST timezone. Parse as universal as we fix it afterwards
        if (!DateTime.TryParseExact(s, format, provider, style, out result))
        {
            result = DateTime.UtcNow;
            return false;
        }
        result = DateTime.SpecifyKind(result, DateTimeKind.Utc);

        // The boundaries of the daylight saving time period in CET and CEST (_not_ in UTC!)
        // Both DateTime structs are of kind 'Utc', to be able to compare them with the parsing result
        DateTime DstStart = LastSundayOf(result.Year, 3).AddHours(2);
        DateTime DstEnd = LastSundayOf(result.Year, 10).AddHours(3);

        // Are we inside the daylight saving time period?
        if (DstStart.CompareTo(result) <= 0 && result.CompareTo(DstEnd) < 0)
            result = result.AddHours(-2); // CEST = UTC+2h
        else
            result = result.AddHours(-1); // CET = UTC+1h

        return true;
    }

    /// <summary>
    /// Returns the last sunday of the given month and year in UTC
    /// </summary>
    private static DateTime LastSundayOf(int year, int month)
    {
        DateTime firstOfNextMonth = new DateTime(year, month + 1, 1, 0, 0, 0, DateTimeKind.Utc);
        return firstOfNextMonth.AddDays(firstOfNextMonth.DayOfWeek == DayOfWeek.Sunday ? -7 :
                                                    (-1 * (int)firstOfNextMonth.DayOfWeek));
    }
}

Хитрость заключалась в том, чтобы разобрать ее без флаг DateTimeStyles.AssumeUniversal (при этом TryParseExact предполагается, что дата указана в формате UTC, и возвращается дата, преобразованная / скорректированная в локальную), с повторным указанием ее в формате UTC и последующей ручной настройкой на фактический эквивалент UTC.

Это следует правилам DST, которые можно найти здесь .Я проверил это со всеми 4 граничными случаями непосредственно перед / после начала / конца летнего времени.Это еще раз показало важность тестирования: мне пришлось изменить оператор < в DstStart.CompareTo(result) < 0 на <=, чтобы он давал правильный результат.

У меня было ощущение, что я заново изобретаю колесо здесь(что я ненавижу делать), но не хотел использовать выделенную библиотеку для этой простой работы.Я взглянул на Noda Time, это отличный проект, но я думаю, что в этом нет необходимости.

Надеюсь, я смогу сэкономить немного времени с помощью этого маленького помощника.Он преднамеренно не является общим для всех часовых поясов (если вам нужно, используйте вместо этого библиотеку типа Noda Time), но для тех случаев, когда у вас просто один фиксированный часовой пояс, как в моем случае.

...