Avatar ·

Как получить дату в формате ISO 8601 из объекта DateTime в C#?

Всем привет! Я новичок в C# и столкнулся с неожиданной проблемой. Мне нужно сериализовать дату и время в строку строго по стандарту ISO 8601 (например, "2023-10-27T14:30:00Z" или "2023-10-27T17:30:00+03:00"). Перепробовал кучу вариантов, но результат всё время какой-то неправильный. Вот мой код:
DateTime now = DateTime.Now;
string dateString = now.ToString(); 
Console.WriteLine(dateString); // Выводит "27.10.2023 14:30:00" - не то!
Пробовал использовать спецификатор "s":
string isoString = now.ToString("s"); 
Console.WriteLine(isoString); // "2023-10-27T14:30:00" - уже лучше, но нет часового пояса
А ещё я заметил, что если использую `DateTime.UtcNow`, то получаю время без учёта локального смещения. Мне нужно, чтобы дата всегда была с правильным часовым поясом, а для UTC — с буквой "Z" на конце. Пробовал добавлять "K" в формат, но получается каша. Подскажите, как правильно это сделать? Желательно с примерами для разных сценариев: локальное время, UTC, произвольный часовой пояс. <<>>

Правильное форматирование даты по ISO 8601 в C#

Проблема, с которой вы столкнулись, очень распространена. Дело в том, что стандарт ISO 8601 имеет несколько вариаций, и C# предоставляет гибкие инструменты для каждой из них. Главное — правильно выбрать тип DateTime и спецификатор формата.

Ключевой момент: для корректного отображения часового пояса нужно использовать спецификатор "O" (буква "O", а не ноль) или комбинацию "yyyy-MM-ddTHH:mm:ssK". Разберём оба подхода.

Метод 1: Спецификатор "O" (Round-trip format)

Это самый надёжный способ, рекомендованный Microsoft. Он автоматически учитывает тип DateTime:

  • Для DateTime.UtcNow — добавляет "Z" на конце.
  • Для DateTime.Now — добавляет смещение часового пояса (например, "+03:00").
  • Для DateTimeKind.Unspecified — не добавляет ничего.

Пример:

using System;

class Program
{
    static void Main()
    {
        // UTC время
        DateTime utcNow = DateTime.UtcNow;
        Console.WriteLine(utcNow.ToString("O")); 
        // Вывод: 2023-10-27T11:30:00.0000000Z

        // Локальное время (например, Москва, UTC+3)
        DateTime localNow = DateTime.Now;
        Console.WriteLine(localNow.ToString("O")); 
        // Вывод: 2023-10-27T14:30:00.0000000+03:00

        // DateTime с явным указанием смещения (через DateTimeOffset)
        DateTimeOffset dto = new DateTimeOffset(2023, 10, 27, 14, 30, 0, TimeSpan.FromHours(5));
        Console.WriteLine(dto.ToString("O")); 
        // Вывод: 2023-10-27T14:30:00.0000000+05:00
    }
}

Обратите внимание: "O" всегда выводит миллисекунды (7 знаков после запятой). Если они не нужны, используйте следующий метод.

Метод 2: Ручной формат с "K"

Спецификатор "K" отвечает только за часовой пояс, а остальную часть строки вы формируете сами. Это даёт больше контроля:

string format = "yyyy-MM-ddTHH:mm:ssK";

Console.WriteLine(DateTime.UtcNow.ToString(format)); 
// Вывод: 2023-10-27T11:30:00Z

Console.WriteLine(DateTime.Now.ToString(format)); 
// Вывод: 2023-10-27T14:30:00+03:00

Если вам нужно убрать миллисекунды или изменить разделитель — это ваш вариант.

Метод 3: Сериализация в JSON

Если вы работаете с JSON (например, через System.Text.Json или Newtonsoft.Json), то даты автоматически сериализуются в ISO 8601. Но можно настроить:

using System.Text.Json;

var options = new JsonSerializerOptions
{
    // Гарантирует вывод с часовым поясом
    DateTimeFormat = "O" 
};
string json = JsonSerializer.Serialize(DateTime.UtcNow, options);
Console.WriteLine(json); // "2023-10-27T11:30:00Z"

Важные замечания

  • Всегда используйте DateTimeOffset для работы с произвольными часовыми поясами. DateTime с Kind.Unspecified может привести к ошибкам.
  • Для строгого соответствия ISO 8601 избегайте локального времени (DateTime.Now) — используйте UTC или явное смещение.
  • Если нужно парсить ISO 8601 обратно в DateTime, используйте DateTime.ParseExact с тем же форматом.
<<>>

Альтернативный подход: через DateTimeOffset и ToString

Если вам нужно больше гибкости в отображении (например, убрать миллисекунды или изменить формат времени), рекомендую явно преобразовывать DateTime в DateTimeOffset. Это особенно полезно, когда вы работаете с базами данных или API, где требуется строгий формат.

DateTime now = DateTime.Now;
DateTimeOffset dto = new DateTimeOffset(now);
string isoString = dto.ToString("yyyy-MM-ddTHH:mm:sszzz");
Console.WriteLine(isoString); // 2023-10-27T14:30:00+03:00

// Для UTC:
DateTimeOffset utcDto = DateTimeOffset.UtcNow;
string utcIso = utcDto.ToString("yyyy-MM-ddTHH:mm:ss'Z'");
Console.WriteLine(utcIso); // 2023-10-27T11:30:00Z

Обратите внимание: в последнем примере я использовал 'Z' (в одинарных кавычках), чтобы добавить символ "Z" вручную. Это удобно, если вы точно знаете, что время UTC.

Для .NET 6 и новее

Начиная с .NET 6, появился метод DateTime.ToString("u"), который выводит дату в универсальном формате, но без "Z" на конце:

Console.WriteLine(DateTime.UtcNow.ToString("u")); 
// 2023-10-27 11:30:00Z (обратите внимание: пробел вместо T)

Этот формат не совсем ISO 8601, но близок к нему. Используйте его только если API требует именно такой строки.