Полное руководство: Преобразование Unix Timestamp в DateTime в C#
Узнайте, как преобразовывать Unix timestamp в объекты DateTime в C# с лучшими практиками для точности и обработки часовых поясов. Полное руководство с примерами кода.
Как преобразовать Unix-метку времени в DateTime и наоборот в C#?
Я работаю над реализацией преобразования меток времени, но сталкиваюсь с проблемами точности в миллисекундах и наносекундах. Вот моя текущая реализация:
public Double CreatedEpoch
{
get
{
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime();
TimeSpan span = (this.Created.ToLocalTime() - epoch);
return span.TotalSeconds;
}
set
{
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime();
this.Created = epoch.AddSeconds(value);
}
}
Каковы лучшие практики для работы с преобразованием Unix-меток времени в C#, особенно в отношении точности и учета часовых поясов?
Преобразование Unix-меток времени в объекты DateTime в C#
Преобразование Unix-меток времени в объекты DateTime в C# является простой задачей с использованием встроенных методов .NET, однако требует внимания к точности и обработке часовых поясов. Рекомендуемый подход использует DateTimeOffset.FromUnixTimeSeconds() и DateTimeOffset.FromUnixTimeMilliseconds() для преобразований на основе UTC, с тщательным учетом DateTimeKind для избежания ошибок, связанных с часовыми поясами.
Содержание
- Базовые методы преобразования
- Обработка точности меток времени
- Лучшие практики работы с часовыми поясами
- Распространенные проблемы и решения
- Полный пример реализации
Базовые методы преобразования
.NET предоставляет встроенные методы для преобразования Unix-меток времени, которые должны быть вашим первым выбором. Эти методы автоматически обрабатывают расчет эпохи и работают с временем UTC.
Из Unix-метки времени в DateTime
// Использование DateTimeOffset (рекомендуемый подход)
DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(1609459200); // 1 января 2021 года UTC
DateTime dateTime = dateTimeOffset.DateTime;
// Для точности в миллисекунды
DateTimeOffset dateTimeOffsetMs = DateTimeOffset.FromUnixTimeMilliseconds(1609459200000);
DateTime dateTimeMs = dateTimeOffsetMs.DateTime;
// Использование DateTime напрямую с ручным расчетом эпохи
DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime dateTimeManual = unixEpoch.AddSeconds(1609459200);
Обсуждение на Stack Overflow подтверждает, что эти встроенные методы являются предпочтительным подходом в современных приложениях .NET.
Из DateTime в Unix-метку времени
// Использование DateTimeOffset (рекомендуется)
DateTimeOffset dto = new DateTimeOffset(DateTime.UtcNow);
long unixTimeSeconds = dto.ToUnixTimeSeconds();
long unixTimeMilliseconds = dto.ToUnixTimeMilliseconds();
// Ручной расчет
DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime now = DateTime.UtcNow;
long unixTime = (long)(now - unixEpoch).TotalSeconds;
Блог Брайана Педерсена демонстрирует эти шаблоны преобразования в практических сценариях.
Обработка точности меток времени
Unix-метки времени могут выражаться в разных единицах измерения, и понимание требований к точности необходимо для точного преобразования.
Секунды против миллисекунд
- Секунды: Традиционная Unix-метка времени (10 цифр)
- Миллисекунды: Современная метка времени (13 цифр, распространена в JavaScript и многих API)
// Проверка, находится ли метка времени в секундах или миллисекундах
public static DateTime ConvertUnixTimestamp(long timestamp)
{
// Если метка времени превышает типичные значения для 2038 года, предполагаем миллисекунды
if (timestamp > 253402300799) // 1 января 10000 года UTC в секундах
{
return DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime;
}
else
{
return DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime;
}
}
Высокая точность (микросекунды и наносекунды)
Для приложений, требующих более высокой точности, потребуется ручной расчет:
// Точность в микросекунды (1 микросекунда = 0,001 миллисекунды)
public static DateTime FromUnixTimeMicroseconds(long microseconds)
{
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return epoch.AddTicks(microseconds * 10); // 1 тик = 100 наносекунд
}
// Точность в наносекунды
public static DateTime FromUnixTimeNanoseconds(long nanoseconds)
{
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return epoch.AddTicks(nanoseconds / 100);
}
Обсуждение проблемы на GitHub показывает, что .NET добавляет нативную поддержку микросекунд в конструкторах DateTime, что указывает на растущий спрос на обработку времени высокой точности.
Лучшие практики работы с часовыми поясами
Обработка часовых поясов является наиболее распространенным источником ошибок при преобразовании меток времени. Вот ключевые принципы:
Всегда используйте UTC для расчетов
Unix-метки времени определяются как количество секунд/миллисекунд с 1 января 1970 года UTC. Никогда не используйте локальное время для расчетов эпохи.
// ❌ Неверно - использует локальное время для эпохи
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0); // DateTimeKind.Unspecified
// ✅ Верно - явно использует UTC
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
Правильно обрабатывайте DateTimeKind
public static class UnixTimestampConverter
{
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public static DateTime FromUnixSeconds(long seconds, DateTimeKind kind = DateTimeKind.Utc)
{
DateTime utcDateTime = UnixEpoch.AddSeconds(seconds);
return kind == DateTimeKind.Utc ? utcDateTime : utcDateTime.ToLocalTime();
}
public static long ToUnixSeconds(DateTime dateTime)
{
DateTime utcDateTime = dateTime.Kind == DateTimeKind.Utc ?
dateTime : dateTime.ToUniversalTime();
return (long)(utcDateTime - UnixEpoch).TotalSeconds;
}
}
Избегайте проблем с переходом на летнее время
Статья в Medium по обработке часовых поясов подчеркивает, что “UTC не меняется с изменением сезонов, но местное или гражданское время может измениться, если часовой пояс использует переход на летнее время”. Именно поэтому UTC следует использовать для всех расчетов меток времени.
Распространенные проблемы и решения
Проблема 1: Неправильная инициализация эпохи
Ваш текущий код:
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime();
Проблема: Это создает эпоху с DateTimeKind.Unspecified, затем преобразует в локальное время, что может вызвать несоответствия.
Решение: Всегда используйте UTC для эпохи:
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
Проблема 2: Потеря точности с Double
Ваш текущий код:
return span.TotalSeconds; // Возвращает double
Проблема: double может потерять точность для больших меток времени. Используйте long для целых секунд или миллисекунд.
Решение:
// Для секунд (целые числа)
return (long)span.TotalSeconds;
// Для миллисекунд (целые числа)
return (long)span.TotalMilliseconds;
Проблема 3: Непоследовательная обработка часовых поясов
Проблема: Преобразование в локальное время как для эпохи, так и для расчета может усугублять ошибки.
Решение: Используйте UTC во внутренних операциях, преобразовывая в локальное время только при отображении пользователям.
Проблема 4: Неопределение единиц измерения метки времени
Проблема: Предположение, что все метки времени выражены в секундах, в то время как некоторые могут быть в миллисекундах.
Решение: Реализуйте автоматическое определение или сделайте это явным в вашем API:
public static DateTime ConvertTimestamp(long timestamp, TimeUnit unit = TimeUnit.Seconds)
{
return unit switch
{
TimeUnit.Seconds => DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime,
TimeUnit.Milliseconds => DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime,
_ => throw new ArgumentException("Неподдерживаемая единица времени")
};
}
Полный пример реализации
Вот улучшенная версия вашего свойства, которая решает все проблемы:
public class TimestampConverter
{
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// Улучшенная версия с использованием long для целочисленной точности
public long CreatedEpochSeconds
{
get
{
// Всегда работаем в UTC для расчетов
DateTime utcCreated = this.Kind == DateTimeKind.Utc ?
this.Created : this.Created.ToUniversalTime();
return (long)(utcCreated - UnixEpoch).TotalSeconds;
}
set
{
// Устанавливаем с типом UTC для поддержания согласованности
this.Created = UnixEpoch.AddSeconds(value);
this.Kind = DateTimeKind.Utc;
}
}
// Версия с точностью до миллисекунд
public long CreatedEpochMilliseconds
{
get
{
DateTime utcCreated = this.Kind == DateTimeKind.Utc ?
this.Created : this.Created.ToUniversalTime();
return (long)(utcCreated - UnixEpoch).TotalMilliseconds;
}
set
{
this.Created = UnixEpoch.AddMilliseconds(value);
this.Kind = DateTimeKind.Utc;
}
}
// Использование DateTimeOffset для автоматической обработки часовых поясов
public DateTimeOffset CreatedDateTimeOffset
{
get => new DateTimeOffset(this.Created);
set => this.Created = value.DateTime;
}
// Методы расширения для более чистого синтаксиса
public static DateTime ToDateTime(long unixSeconds)
=> DateTimeOffset.FromUnixTimeSeconds(unixSeconds).DateTime;
public static long ToUnixSeconds(DateTime dateTime)
=> new DateTimeOffset(dateTime).ToUnixTimeSeconds();
}
Примеры использования
// Преобразование текущего времени в Unix-метку времени
long nowSeconds = TimestampConverter.ToUnixSeconds(DateTime.UtcNow);
long nowMilliseconds = DateTimeOffset.Now.ToUnixTimeMilliseconds();
// Преобразование обратно в DateTime
DateTime dateTime = TimestampConverter.ToDateTime(nowSeconds);
// Использование улучшенного свойства
var converter = new TimestampConverter();
converter.CreatedEpochSeconds = 1609459200; // 1 января 2021 года UTC
Console.WriteLine(converter.Created); // Показывает правильную дату UTC
Руководство по преобразованиям в C# от iifx.dev предоставляет дополнительные примеры и лучшие практики для этих шаблонов преобразования.
Заключение
Преобразование между Unix-метками времени и объектами DateTime в C# требует внимания к трем ключевым областям:
- Используйте встроенные методы .NET, такие как
DateTimeOffset.FromUnixTimeSeconds()иDateTimeOffset.FromUnixTimeMilliseconds(), для надежных, протестированных преобразований - Поддерживайте согласованность UTC, всегда используя
DateTimeKind.Utcдля расчетов эпохи и внутренних операций - Выбирайте подходящую точность, используя
longдля целых секунд/миллисекунд вместоdoubleдля избежания потери точности
Улучшенная реализация решает основные проблемы в вашем исходном коде: неправильную инициализацию эпохи, потерю точности с double и непоследовательную обработку часовых поясов. Следуя этим лучшим практикам, вы создадите преобразования меток времени, которые будут точными и надежными в разных часовых поясах и при изменениях перехода на летнее время.
Для большинства приложений подход на основе DateTimeOffset обеспечивает наилучший баланс простоты и точности, в то время как ручные расчеты становятся необходимыми только для специализированных требований высокой точности.
Источники
- Как преобразовать Unix-метку времени в DateTime и наоборот? - Stack Overflow
- Преобразование между Unix-метками времени и DateTime в C# – Aske DOTNET
- Преобразование DateTime в UNIX-метки времени в C# | Блог Брайана Педерсена о Sitecore и .NET
- Как получить Unix-метку времени в C# - Stack Overflow
- Упрощение обработки часовых поясов с помощью Unix-времени: практический опыт в C# - Medium
- Руководство по преобразованию DateTime и Unix-метки времени в C# - iifx.dev
- Добавление микросекунд и наносекунд в TimeStamp, DateTime, DateTimeOffset и TimeOnly - GitHub
- Преобразование в/из Unix-метки времени в C# - DZone