Другое

Полное руководство: Преобразование Unix Timestamp в DateTime в C#

Узнайте, как преобразовывать Unix timestamp в объекты DateTime в C# с лучшими практиками для точности и обработки часовых поясов. Полное руководство с примерами кода.

Как преобразовать Unix-метку времени в DateTime и наоборот в C#?

Я работаю над реализацией преобразования меток времени, но сталкиваюсь с проблемами точности в миллисекундах и наносекундах. Вот моя текущая реализация:

csharp
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

csharp
// Использование 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-метку времени

csharp
// Использование 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)
csharp
// Проверка, находится ли метка времени в секундах или миллисекундах
public static DateTime ConvertUnixTimestamp(long timestamp)
{
    // Если метка времени превышает типичные значения для 2038 года, предполагаем миллисекунды
    if (timestamp > 253402300799) // 1 января 10000 года UTC в секундах
    {
        return DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime;
    }
    else
    {
        return DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime;
    }
}

Высокая точность (микросекунды и наносекунды)

Для приложений, требующих более высокой точности, потребуется ручной расчет:

csharp
// Точность в микросекунды (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. Никогда не используйте локальное время для расчетов эпохи.

csharp
// ❌ Неверно - использует локальное время для эпохи
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

csharp
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: Неправильная инициализация эпохи

Ваш текущий код:

csharp
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime();

Проблема: Это создает эпоху с DateTimeKind.Unspecified, затем преобразует в локальное время, что может вызвать несоответствия.

Решение: Всегда используйте UTC для эпохи:

csharp
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

Проблема 2: Потеря точности с Double

Ваш текущий код:

csharp
return span.TotalSeconds; // Возвращает double

Проблема: double может потерять точность для больших меток времени. Используйте long для целых секунд или миллисекунд.

Решение:

csharp
// Для секунд (целые числа)
return (long)span.TotalSeconds;

// Для миллисекунд (целые числа)
return (long)span.TotalMilliseconds;

Проблема 3: Непоследовательная обработка часовых поясов

Проблема: Преобразование в локальное время как для эпохи, так и для расчета может усугублять ошибки.

Решение: Используйте UTC во внутренних операциях, преобразовывая в локальное время только при отображении пользователям.

Проблема 4: Неопределение единиц измерения метки времени

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

Решение: Реализуйте автоматическое определение или сделайте это явным в вашем API:

csharp
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("Неподдерживаемая единица времени")
    };
}

Полный пример реализации

Вот улучшенная версия вашего свойства, которая решает все проблемы:

csharp
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();
}

Примеры использования

csharp
// Преобразование текущего времени в 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# требует внимания к трем ключевым областям:

  1. Используйте встроенные методы .NET, такие как DateTimeOffset.FromUnixTimeSeconds() и DateTimeOffset.FromUnixTimeMilliseconds(), для надежных, протестированных преобразований
  2. Поддерживайте согласованность UTC, всегда используя DateTimeKind.Utc для расчетов эпохи и внутренних операций
  3. Выбирайте подходящую точность, используя long для целых секунд/миллисекунд вместо double для избежания потери точности

Улучшенная реализация решает основные проблемы в вашем исходном коде: неправильную инициализацию эпохи, потерю точности с double и непоследовательную обработку часовых поясов. Следуя этим лучшим практикам, вы создадите преобразования меток времени, которые будут точными и надежными в разных часовых поясах и при изменениях перехода на летнее время.

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

Источники

  1. Как преобразовать Unix-метку времени в DateTime и наоборот? - Stack Overflow
  2. Преобразование между Unix-метками времени и DateTime в C# – Aske DOTNET
  3. Преобразование DateTime в UNIX-метки времени в C# | Блог Брайана Педерсена о Sitecore и .NET
  4. Как получить Unix-метку времени в C# - Stack Overflow
  5. Упрощение обработки часовых поясов с помощью Unix-времени: практический опыт в C# - Medium
  6. Руководство по преобразованию DateTime и Unix-метки времени в C# - iifx.dev
  7. Добавление микросекунд и наносекунд в TimeStamp, DateTime, DateTimeOffset и TimeOnly - GitHub
  8. Преобразование в/из Unix-метки времени в C# - DZone
Авторы
Проверено модерацией
Модерация