В настоящее время я пишу инструмент, который анализирует файлы, которые имеют нестандартный нестандартный формат файла. Файлы содержат данные для нескольких точек измерения с временными метками (одно измерение каждые 30 секунд). Эти временные метки находятся в часовом поясе CET / CEST, что означает, что, когда, например, время изменяется с летнего на зимнее время, в файле будет несколько измерений с одной и той же временной меткой, поскольку каждая метка времени между 2:00 и 3:00 в Утро будет существовать дважды в файле (одно до и одно после измененного времени).

Я уже могу проанализировать все нужные мне временные метки (год, месяц, день, часы и минуты) из файла, но мне также нужно преобразовать их в UTC. Есть ли какая-либо функциональность в .NET Framework или в сторонней библиотеке для этого преобразования?

Я уже пытался использовать TimeZoneInfo.ConvertTimeToUtc, но мне уже нужно иметь объект DateTime для того, что я пока не знаю, как его создать. Я не могу просто создать новый объект DateTime, используя один из конструкторов DateTime, потому что, допустим, я предоставляю одну из временных меток, которая существует дважды в файле из-за перехода с летнего на зимнее время. Как он узнает, что эта метка времени была раньше? или после того, как время изменилось?

Я думал, что это, вероятно, довольно распространенная проблема при работе с метками времени, но я пока не смог найти хорошего решения для своей проблемы. Нужно ли вручную отслеживать, когда происходит изменение времени в файле, чтобы правильно преобразовать время в UTC? Если так, как я могу это сделать?

ОБНОВЛЕНИЕ: Вот очень упрощенный пример проблемы, которую я пытаюсь решить: это могут быть некоторые данные, которые мой инструмент проанализировал по временным меткам в файле:

Day: 27 - Month: 10 - Year: 2019 - Hour: 1 - Minute: 45 - Second: 0 
Day: 27 - Month: 10 - Year: 2019 - Hour: 2 - Minute: 0 - Second: 0 
Day: 27 - Month: 10 - Year: 2019 - Hour: 2 - Minute: 15 - Second: 0 
Day: 27 - Month: 10 - Year: 2019 - Hour: 2 - Minute: 30 - Second: 0 
Day: 27 - Month: 10 - Year: 2019 - Hour: 2 - Minute: 45 - Second: 0 
Day: 27 - Month: 10 - Year: 2019 - Hour: 2 - Minute: 0 - Second: 0 
Day: 27 - Month: 10 - Year: 2019 - Hour: 2 - Minute: 15 - Second: 0 
Day: 27 - Month: 10 - Year: 2019 - Hour: 2 - Minute: 30 - Second: 0 
Day: 27 - Month: 10 - Year: 2019 - Hour: 2 - Minute: 45 - Second: 0 
Day: 27 - Month: 10 - Year: 2019 - Hour: 3 - Minute: 0 - Second: 0 
Day: 27 - Month: 10 - Year: 2019 - Hour: 3 - Minute: 15 - Second: 0 
Day: 27 - Month: 10 - Year: 2019 - Hour: 3 - Minute: 30 - Second: 0

(в действительности данных в час намного больше, но это не имеет значения для примера)

В этом примере часы устанавливаются на один час назад с 3:00 до 2:00 утра, потому что здесь заканчивается летнее время, и поэтому мы переключаемся с CEST на CET. Противоположная вещь происходит ~ 6 месяцев спустя (и, очевидно, также 6 месяцев назад), когда начинается период перехода на летнее время и часы перемещаются на один час вперед.

В файлах нет указаний на то, что мой инструмент анализирует, находится ли временная метка в CET или CEST, поэтому моя логика синтаксического анализа должна справиться с этим. У меня в основном есть цикл, который перебирает строки в файле, анализирует метки времени и затем вызывает эту функцию, чтобы получить объект DateTime для каждой метки времени:

private static DateTime toDateTimeUTC(int year, int month, int day, int hour, int minute, int second)
{
    // ToDo

    return new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc); // This is WRONG!
}

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

1
Chris 4 Май 2020 в 16:28

2 ответа

Лучший ответ

Если вы знаете, что у вас всегда будет один день на файл, и вы всегда можете обнаружить изменение, потому что вы записываете данные каждые 30 секунд, вы можете определить, когда локальная временная метка движется назад, и определить, какое неоднозначное смещение использовать таким образом. , Что-то вроде этого:

TimeZoneInfo zone = ...; // I assume you've got this already
bool afterFallBack = false;
DateTime previousUnspecifiedTimestamp = DateTime.MinValue;
foreach (var line in log)
{
    var timestampText = ...; // Take the timestamp from the line
    // Parse the timestamp without performing any time zone conversions
    // (The "unspecified" part of the name refers to the DateTimeKind.Unspecified.)
    // TODO: Check the format
    var unspecifiedTimestamp = DateTime.ParseExact(timestampText,
        "yyyy-MM-dd'T'HH:mm:ss", timestampText, CultureInfo.InvariantCulture);

    // Detect "fall back" so we know which 
    if (unspecifiedTimestamp < previousUnspecifiedTimestamp)
    {
        afterFallBack = true;
    }
    previousUnspecifiedTimestamp = unspecifiedTimestamp;

    DateTime utcTimestamp = ConvertToZone(zone, unspecifiedTimestamp, afterFallBack);
    // Process the log entry
}

// Method extracted for testability and tidiness
private static DateTime ConvertToZone(
    TimeZoneInfo zone, DateTime dateTime, bool useLaterAmbiguousOffset)
{
    if (!zone.IsAmbiguousTime(dateTime))
    {
        return TimeZoneInfo.ConvertToUtc(dateTime, zone);
    }
    // The offsets returned by this appear to be in order of "smallest offset"
    // to "largest offset" - which means that the offset that's observed later is 
    // the one that occurs at the start of the array. This isn't actually
    // documented... you could order the offsets for added certainty,
    // but I'd be surprised if this changed.
    var offsets = zone.GetAmbiguousTimeOffsets(dateTime);
    var chosenOffset = useLaterAmbiguousOffset ? offsets[0] : offset[1];
    return DateTime.SpecifyKind(dateTime - chosenOffset, DateTimeKind.Utc);
}

Кроме того, этот код был бы проще для IMO с моим проектом Noda Time, но я не обязательно рекомендую вам переключиться только ради этого. Если вы выполняете какое-то значительное количество работы с датой / временем, это стоит посмотреть.

1
Jon Skeet 5 Май 2020 в 15:03

Если у вас есть все в DateTimeOffset с + 1 / + 2 в зависимости от лета / зимы, тогда вы просто сохраните ToUniversalTime, если хотите сохранить его с помощью Offset +0.

var input = new DateTimeOffset(2000,1,1,23,59,00, TimeSpan.FromHours(1));
var utc = cet.ToUniversalTime();
0
Thomas Koelle 5 Май 2020 в 09:47