Вступление: Почему Unix Timestamp — это не просто число
Каждый разработчик, работающий с API, базами данных или логами, рано или поздно сталкивается с Unix timestamp. Это целое число (или число с плавающей точкой), которое обозначает количество секунд, прошедших с 1 января 1970 года 00:00:00 UTC — так называемой эпохи Unix (Unix epoch).
Но когда вы видите в ответе сервера число 1719878400, ваш мозг не может сразу сказать: «Ага, это 2 июля 2024 года». Нам нужно преобразовать его в привычный DateTime. И наоборот — сохранить дату в виде метки для передачи.
На Stack Overflow вопрос «How can I convert a Unix timestamp to DateTime and vice versa?» набрал сотни тысяч просмотров. Это значит, что даже опытные C#-разработчики иногда путаются в нюансах. В этой статье мы не просто дадим готовые рецепты, но и заглянем под капот: разберём, как устроены DateTime, DateTimeOffset, тики и временные зоны.
Что такое Unix Timestamp на самом деле?
Прежде чем писать код, давайте договоримся о терминах. Unix timestamp — это количество секунд (иногда миллисекунд) с полуночи 1 января 1970 года по UTC. Важно: это абсолютный момент времени, не зависящий от часового пояса. Именно поэтому его так удобно использовать в распределённых системах.
В C# мы работаем с двумя основными типами:
DateTime— классический тип, который может быть как в UTC, так и в локальном времени (свойствоKind).DateTimeOffset— более современный тип, который явно хранит смещение от UTC. Рекомендуется для новых проектов.
Под капотом DateTime хранит количество тиков (100-наносекундных интервалов) с 1 января 0001 года. А Unix timestamp — это просто количество секунд с 1970 года. Чтобы перейти от одного к другому, нужно вычислить разницу между этими двумя эпохами.
Конвертация Unix Timestamp в DateTime (и обратно) — базовые методы
Начиная с .NET Framework 4.6 и .NET Core, в C# появились встроенные методы, которые делают жизнь проще. Давайте посмотрим на них.
1. Из Unix timestamp (секунды) в DateTime
Самый простой способ — использовать DateTimeOffset.FromUnixTimeSeconds(). Он возвращает DateTimeOffset, который затем можно преобразовать в DateTime.
long unixTimestamp = 1719878400; // 2 июля 2024, 00:00:00 UTC
// Получаем DateTimeOffset
DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(unixTimestamp);
// Преобразуем в DateTime (UTC)
DateTime utcDateTime = dateTimeOffset.UtcDateTime;
Console.WriteLine(utcDateTime); // 02.07.2024 0:00:00
Если вам нужно локальное время (например, для отображения пользователю), используйте dateTimeOffset.LocalDateTime. Но будьте осторожны: локальное время зависит от настроек сервера или компьютера.
2. Из DateTime в Unix timestamp
Обратное преобразование — через метод ToUnixTimeSeconds() у DateTimeOffset.
DateTime utcDateTime = new DateTime(2024, 7, 2, 0, 0, 0, DateTimeKind.Utc);
// Создаём DateTimeOffset с явным указанием UTC
DateTimeOffset dateTimeOffset = new DateTimeOffset(utcDateTime);
long unixTimestamp = dateTimeOffset.ToUnixTimeSeconds();
Console.WriteLine(unixTimestamp); // 1719878400
Критически важно: всегда указывайте DateTimeKind.Utc при создании DateTime, который вы собираетесь конвертировать. Если этого не сделать, DateTimeOffset будет считать, что время локальное, и вы получите неверный timestamp — со сдвигом на ваш часовой пояс.
3. Работа с миллисекундами
Иногда timestamp передаётся в миллисекундах (например, из JavaScript). Для этого есть FromUnixTimeMilliseconds() и ToUnixTimeMilliseconds().
long unixMs = 1719878400000; // 2 июля 2024, 00:00:00 UTC
DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds(unixMs);
DateTime utcDateTime = dateTimeOffset.UtcDateTime;
// Обратно
long backToMs = new DateTimeOffset(utcDateTime).ToUnixTimeMilliseconds();
Console.WriteLine(backToMs); // 1719878400000
Как это работает под капотом? Разбираем тики и эпоху
Теперь давайте заглянем глубже. Внутри DateTimeOffset.FromUnixTimeSeconds() делает примерно следующее:
- Вычисляет количество тиков, соответствующее Unix epoch (1 января 1970 года, UTC).
- Умножает переданное количество секунд на
TimeSpan.TicksPerSecond(10 000 000 тиков в секунде). - Прибавляет это к базовому значению эпохи.
Вот как это можно написать вручную для понимания:
public static DateTime UnixTimeStampToDateTime(long unixTimeStamp)
{
// Unix epoch в тиках
long epochTicks = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
// Прибавляем тики из секунд
long totalTicks = epochTicks + unixTimeStamp * TimeSpan.TicksPerSecond;
return new DateTime(totalTicks, DateTimeKind.Utc);
}
public static long DateTimeToUnixTimeStamp(DateTime dateTime)
{
// Приводим к UTC, чтобы избежать сдвига
DateTime utcDateTime = dateTime.ToUniversalTime();
long epochTicks = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
return (utcDateTime.Ticks - epochTicks) / TimeSpan.TicksPerSecond;
}
Этот код наглядно показывает, что никакой магии нет — просто арифметика с тиками. Но именно понимание этой механики спасает от ошибок, когда вы работаете с разными часовыми поясами или нестандартными эпохами.
Типичные ошибки и как их избежать
На Stack Overflow полно вопросов от разработчиков, которые получают неверное время. Вот три самые частые проблемы:
- Забыли указать DateTimeKind.Utc. Если вы создаёте
DateTimeчерезnew DateTime(2024, 7, 2, 0, 0, 0), его Kind = Unspecified. При конвертации в timestamp вы получите значение, смещённое на ваш часовой пояс. Всегда используйтеDateTimeKind.UtcилиDateTime.SpecifyKind(). - Путаница между секундами и миллисекундами. Если вы ожидаете секунды, а получаете миллисекунды (или наоборот), время уедет на 1000 порядков. Проверяйте формат данных от API.
- Игнорирование DateTimeOffset. Многие разработчики до сих пор используют