Поделиться через


Поддержка DateTime и DateTimeOffset в System.Text.Json

Библиотека System.Text.Json анализирует и записывает DateTime и DateTimeOffset значения в соответствии с расширенным профилем ISO 8601-1:2019. Преобразователи обеспечивают настраиваемую поддержку сериализации и десериализации с JsonSerializerпомощью . Вы также можете использовать Utf8JsonReader и Utf8JsonWriter реализовать пользовательскую поддержку.

Поддержка формата ISO 8601-1:2019

JsonElement Utf8JsonWriterUtf8JsonReaderВ JsonSerializerсоответствии с расширенным профилем формата ISO 8601-1-2019 и типы синтаксического анализа и записи DateTime и DateTimeOffset текста в соответствии с расширенным профилем. Например, 2019-07-26T16:59:57-05:00.

DateTime и DateTimeOffset данные можно сериализовать с помощью JsonSerializer:

using System.Text.Json;

public class Example
{
    private class Product
    {
        public string? Name { get; set; }
        public DateTime ExpiryDate { get; set; }
    }

    public static void Main(string[] args)
    {
        Product p = new Product();
        p.Name = "Banana";
        p.ExpiryDate = new DateTime(2019, 7, 26);

        string json = JsonSerializer.Serialize(p);
        Console.WriteLine(json);
    }
}

// The example displays the following output:
// {"Name":"Banana","ExpiryDate":"2019-07-26T00:00:00"}

DateTime и DateTimeOffset также можно десериализировать с помощью JsonSerializer:

using System.Text.Json;

public class Example
{
    private class Product
    {
        public string? Name { get; set; }
        public DateTime ExpiryDate { get; set; }
    }

    public static void Main(string[] args)
    {
        string json = @"{""Name"":""Banana"",""ExpiryDate"":""2019-07-26T00:00:00""}";
        Product p = JsonSerializer.Deserialize<Product>(json)!;
        Console.WriteLine(p.Name);
        Console.WriteLine(p.ExpiryDate);
    }
}

// The example displays output similar to the following:
// Banana
// 7/26/2019 12:00:00 AM

При использовании параметров по умолчанию входные DateTime и DateTimeOffset текстовые представления должны соответствовать расширенному профилю ISO 8601-1:2019. Попытка десериализации представлений, которые не соответствуют профилю, приведет JsonSerializer к возникновению ошибки JsonException:

using System.Text.Json;

public class Example
{
    private class Product
    {
        public string? Name { get; set; }
        public DateTime ExpiryDate { get; set; }
    }

    public static void Main(string[] args)
    {
        string json = @"{""Name"":""Banana"",""ExpiryDate"":""26/07/2019""}";
        try
        {
            Product _ = JsonSerializer.Deserialize<Product>(json)!;
        }
        catch (JsonException e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

// The example displays the following output:
// The JSON value could not be converted to System.DateTime. Path: $.ExpiryDate | LineNumber: 0 | BytePositionInLine: 42.

Предоставляет JsonDocument структурированный доступ к содержимому полезных данных JSON, включая DateTime и DateTimeOffset представления. В следующем примере показано, как вычислить среднюю температуру в понедельник из коллекции температур:

using System.Text.Json;

public class Example
{
    private static double ComputeAverageTemperatures(string json)
    {
        JsonDocumentOptions options = new JsonDocumentOptions
        {
            AllowTrailingCommas = true
        };

        using (JsonDocument document = JsonDocument.Parse(json, options))
        {
            int sumOfAllTemperatures = 0;
            int count = 0;

            foreach (JsonElement element in document.RootElement.EnumerateArray())
            {
                DateTimeOffset date = element.GetProperty("date").GetDateTimeOffset();

                if (date.DayOfWeek == DayOfWeek.Monday)
                {
                    int temp = element.GetProperty("temp").GetInt32();
                    sumOfAllTemperatures += temp;
                    count++;
                }
            }

            double averageTemp = (double)sumOfAllTemperatures / count;
            return averageTemp;
        }
    }

    public static void Main(string[] args)
    {
        string json =
                @"[" +
                    @"{" +
                        @"""date"": ""2013-01-07T00:00:00Z""," +
                        @"""temp"": 23," +
                    @"}," +
                    @"{" +
                        @"""date"": ""2013-01-08T00:00:00Z""," +
                        @"""temp"": 28," +
                    @"}," +
                    @"{" +
                        @"""date"": ""2013-01-14T00:00:00Z""," +
                        @"""temp"": 8," +
                    @"}," +
                @"]";

        Console.WriteLine(ComputeAverageTemperatures(json));
    }
}

// The example displays the following output:
// 15.5

Попытка вычислить среднюю температуру с учетом полезных данных с несоответствующими DateTime представлениями приведет JsonDocument к возникновению следующих причин FormatException:

using System.Text.Json;

public class Example
{
    private static double ComputeAverageTemperatures(string json)
    {
        JsonDocumentOptions options = new JsonDocumentOptions
        {
            AllowTrailingCommas = true
        };

        using (JsonDocument document = JsonDocument.Parse(json, options))
        {
            int sumOfAllTemperatures = 0;
            int count = 0;

            foreach (JsonElement element in document.RootElement.EnumerateArray())
            {
                DateTimeOffset date = element.GetProperty("date").GetDateTimeOffset();

                if (date.DayOfWeek == DayOfWeek.Monday)
                {
                    int temp = element.GetProperty("temp").GetInt32();
                    sumOfAllTemperatures += temp;
                    count++;
                }
            }

            double averageTemp = (double)sumOfAllTemperatures / count;
            return averageTemp;
        }
    }

    public static void Main(string[] args)
    {
        // Computing the average temperatures will fail because the DateTimeOffset
        // values in the payload do not conform to the extended ISO 8601-1:2019 profile.
        string json =
                @"[" +
                    @"{" +
                        @"""date"": ""2013/01/07 00:00:00Z""," +
                        @"""temp"": 23," +
                    @"}," +
                    @"{" +
                        @"""date"": ""2013/01/08 00:00:00Z""," +
                        @"""temp"": 28," +
                    @"}," +
                    @"{" +
                        @"""date"": ""2013/01/14 00:00:00Z""," +
                        @"""temp"": 8," +
                    @"}," +
                @"]";

        Console.WriteLine(ComputeAverageTemperatures(json));
    }
}

// The example displays the following output:
// Unhandled exception.System.FormatException: One of the identified items was in an invalid format.
//    at System.Text.Json.JsonElement.GetDateTimeOffset()

Нижний уровень Utf8JsonWriter записывает DateTime и DateTimeOffset данные:

using System.Text;
using System.Text.Json;

public class Example
{
    public static void Main(string[] args)
    {
        JsonWriterOptions options = new JsonWriterOptions
        {
            Indented = true
        };

        using (MemoryStream stream = new MemoryStream())
        {
            using (Utf8JsonWriter writer = new Utf8JsonWriter(stream, options))
            {
                writer.WriteStartObject();
                writer.WriteString("date", DateTimeOffset.UtcNow);
                writer.WriteNumber("temp", 42);
                writer.WriteEndObject();
            }

            string json = Encoding.UTF8.GetString(stream.ToArray());
            Console.WriteLine(json);
        }
    }
}

// The example output similar to the following:
// {
//     "date": "2019-07-26T00:00:00+00:00",
//     "temp": 42
// }

Utf8JsonReader синтаксический DateTime анализ и DateTimeOffset данные:

using System.Text;
using System.Text.Json;

public class Example
{
    public static void Main(string[] args)
    {
        byte[] utf8Data = Encoding.UTF8.GetBytes(@"""2019-07-26T00:00:00""");

        Utf8JsonReader json = new Utf8JsonReader(utf8Data);
        while (json.Read())
        {
            if (json.TokenType == JsonTokenType.String)
            {
                Console.WriteLine(json.TryGetDateTime(out DateTime datetime));
                Console.WriteLine(datetime);
                Console.WriteLine(json.GetDateTime());
            }
        }
    }
}

// The example displays output similar to the following:
// True
// 7/26/2019 12:00:00 AM
// 7/26/2019 12:00:00 AM

Попытка считывать несоответствующие форматы Utf8JsonReader приведет к возникновению:FormatException

using System.Text;
using System.Text.Json;

public class Example
{
    public static void Main(string[] args)
    {
        byte[] utf8Data = Encoding.UTF8.GetBytes(@"""2019/07/26 00:00:00""");

        Utf8JsonReader json = new Utf8JsonReader(utf8Data);
        while (json.Read())
        {
            if (json.TokenType == JsonTokenType.String)
            {
                Console.WriteLine(json.TryGetDateTime(out DateTime datetime));
                Console.WriteLine(datetime);

                DateTime _ = json.GetDateTime();
            }
        }
    }
}

// The example displays the following output:
// False
// 1/1/0001 12:00:00 AM
// Unhandled exception. System.FormatException: The JSON value is not in a supported DateTime format.
//     at System.Text.Json.Utf8JsonReader.GetDateTime()

Сериализация свойств DateOnly и TimeOnly

С помощью .NET 7+поддерживает System.Text.Json сериализацию и десериализацию DateOnly и TimeOnly типы. Рассмотрим следующий объект:

sealed file record Appointment(
    Guid Id,
    string Description,
    DateOnly Date,
    TimeOnly StartTime,
    TimeOnly EndTime);

В следующем примере сериализуется Appointment объект, отображается результирующий json, а затем десериализирует его обратно в новый экземпляр Appointment типа. Наконец, исходные и недавно десериализированные экземпляры сравниваются для равенства, а результаты записываются в консоль:

Appointment originalAppointment = new(
    Id: Guid.NewGuid(),
    Description: "Take dog to veterinarian.",
    Date: new DateOnly(2002, 1, 13),
    StartTime: new TimeOnly(5,15),
    EndTime: new TimeOnly(5, 45));
string serialized = JsonSerializer.Serialize(originalAppointment);

Console.WriteLine($"Resulting JSON: {serialized}");

Appointment deserializedAppointment =
    JsonSerializer.Deserialize<Appointment>(serialized)!;

bool valuesAreTheSame = originalAppointment == deserializedAppointment;
Console.WriteLine($"""
    Original record has the same values as the deserialized record: {valuesAreTheSame}
    """);

В предыдущем коде:

  • Объект Appointment создается и назначается переменной appointment .
  • Экземпляр appointment сериализуется в JSON с помощью JsonSerializer.Serialize.
  • Результирующий json записывается в консоль.
  • JSON десериализирован обратно в новый экземпляр Appointment типа с помощью JsonSerializer.Deserialize.
  • Исходные и недавно десериализированные экземпляры сравниваются для равенства.
  • Результат сравнения записывается в консоль.

Настраиваемая поддержка и DateTimeDateTimeOffset

При использовании JsonSerializer

Если вы хотите, чтобы сериализатор выполнял пользовательский анализ или форматирование, можно реализовать пользовательские преобразователи. Вот несколько таких случаев.

DateTime(Offset). Синтаксический анализ и DateTime(Offset). ToString

Если вы не можете определить форматы входных DateTime или DateTimeOffset текстовых представлений, можно использовать DateTime(Offset).Parse метод в логике чтения преобразователя. Этот метод позволяет использовать . Расширенная поддержка анализа различных DateTime и DateTimeOffset текстовых форматов NET, включая строки, отличные от ISO 8601, и форматы ISO 8601, которые не соответствуют расширенному профилю ISO 8601-1:2019. Этот подход меньше производительности, чем использование собственной реализации сериализатора.

Для сериализации можно использовать метод в логике DateTime(Offset).ToString записи преобразователя. Этот метод позволяет записывать DateTime и DateTimeOffset значения с помощью любого из стандартных форматов даты и времени, а также пользовательских форматов даты и времени. Этот подход также меньше производительности, чем использование собственной реализации сериализатора.

using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

namespace DateTimeConverterExamples;

public class DateTimeConverterUsingDateTimeParse : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        Debug.Assert(typeToConvert == typeof(DateTime));
        return DateTime.Parse(reader.GetString() ?? string.Empty);
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString());
    }
}

class Program
{
    private static void ParseDateTimeWithDefaultOptions()
    {
        DateTime _ = JsonSerializer.Deserialize<DateTime>(@"""04-10-2008 6:30 AM""");
    }

    private static void FormatDateTimeWithDefaultOptions()
    {
        Console.WriteLine(JsonSerializer.Serialize(DateTime.Parse("04-10-2008 6:30 AM -4")));
    }

    private static void ProcessDateTimeWithCustomConverter()
    {
        JsonSerializerOptions options = new JsonSerializerOptions();
        options.Converters.Add(new DateTimeConverterUsingDateTimeParse());

        string testDateTimeStr = "04-10-2008 6:30 AM";
        string testDateTimeJson = @"""" + testDateTimeStr + @"""";

        DateTime resultDateTime = JsonSerializer.Deserialize<DateTime>(testDateTimeJson, options);
        Console.WriteLine(resultDateTime);

        string resultDateTimeJson = JsonSerializer.Serialize(DateTime.Parse(testDateTimeStr), options);
        Console.WriteLine(Regex.Unescape(resultDateTimeJson));
    }

    static void Main(string[] args)
    {
        // Parsing non-compliant format as DateTime fails by default.
        try
        {
            ParseDateTimeWithDefaultOptions();
        }
        catch (JsonException e)
        {
            Console.WriteLine(e.Message);
        }

        // Formatting with default options prints according to extended ISO 8601 profile.
        FormatDateTimeWithDefaultOptions();

        // Using converters gives you control over the serializers parsing and formatting.
        ProcessDateTimeWithCustomConverter();
    }
}

// The example displays output similar to the following:
// The JSON value could not be converted to System.DateTime. Path: $ | LineNumber: 0 | BytePositionInLine: 20.
// "2008-04-10T06:30:00-04:00"
// 4/10/2008 6:30:00 AM
// "4/10/2008 6:30:00 AM"

Примечание.

При реализации JsonConverter<T>и T имеет DateTimetypeToConvert значение параметраtypeof(DateTime). Параметр полезен для обработки полиморфных случаев и при использовании универсальных шаблонов для эффективного выполнения typeof(T) .

Utf8Parser и Utf8Formatter.

В логике преобразователя можно использовать быстрые методы синтаксического анализа и форматирования на основе UTF-8, если входные DateTime или текстовые представления соответствуют одной из строк стандартного формата даты и DateTimeOffset времени типа "R", "L", "O" или "G" или вы хотите написать в соответствии с одним из этих форматов. Этот подход гораздо быстрее, чем использование DateTime(Offset).Parse и DateTime(Offset).ToString.

В следующем примере показан настраиваемый преобразователь, который сериализует и десериализирует DateTime значения в соответствии со стандартным форматом R:

using System.Buffers;
using System.Buffers.Text;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace DateTimeConverterExamples;

// This converter reads and writes DateTime values according to the "R" standard format specifier:
// https://learn.microsoft.com/dotnet/standard/base-types/standard-date-and-time-format-strings#the-rfc1123-r-r-format-specifier.
public class DateTimeConverterForCustomStandardFormatR : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        Debug.Assert(typeToConvert == typeof(DateTime));

        if (Utf8Parser.TryParse(reader.ValueSpan, out DateTime value, out _, 'R'))
        {
            return value;
        }

        throw new FormatException();
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        // The "R" standard format will always be 29 bytes.
        Span<byte> utf8Date = new byte[29];

        bool result = Utf8Formatter.TryFormat(value, utf8Date, out _, new StandardFormat('R'));
        Debug.Assert(result);

        writer.WriteStringValue(utf8Date);
    }
}

class Program
{
    private static void ParseDateTimeWithDefaultOptions()
    {
        DateTime _ = JsonSerializer.Deserialize<DateTime>(@"""Thu, 25 Jul 2019 13:36:07 GMT""");
    }

    private static void ProcessDateTimeWithCustomConverter()
    {
        JsonSerializerOptions options = new JsonSerializerOptions();
        options.Converters.Add(new DateTimeConverterForCustomStandardFormatR());

        string testDateTimeStr = "Thu, 25 Jul 2019 13:36:07 GMT";
        string testDateTimeJson = @"""" + testDateTimeStr + @"""";

        DateTime resultDateTime = JsonSerializer.Deserialize<DateTime>(testDateTimeJson, options);
        Console.WriteLine(resultDateTime);

        Console.WriteLine(JsonSerializer.Serialize(DateTime.Parse(testDateTimeStr), options));
    }

    static void Main(string[] args)
    {
        // Parsing non-compliant format as DateTime fails by default.
        try
        {
            ParseDateTimeWithDefaultOptions();
        }
        catch (JsonException e)
        {
            Console.WriteLine(e.Message);
        }

        // Using converters gives you control over the serializers parsing and formatting.
        ProcessDateTimeWithCustomConverter();
    }
}

// The example displays output similar to the following:
// The JSON value could not be converted to System.DateTime.Path: $ | LineNumber: 0 | BytePositionInLine: 31.
// 7/25/2019 1:36:07 PM
// "Thu, 25 Jul 2019 09:36:07 GMT"

Примечание.

Стандартный формат "R" всегда будет содержать 29 символов.

Формат "l" (нижний регистр "L") не задокументирован с другими стандартными строками формата даты и времени, так как он поддерживается только типами и Utf8Formatter датамиUtf8Parser. Формат — строчная версия RFC 1123 (строчная версия формата R). Например, "thu, 25 jul 2019 06:36:07 gmt".

Используйте DateTime(Offset). Анализ как резервный вариант

Если вы обычно ожидаете, что входные данные или DateTimeOffset данные DateTime соответствуют расширенному профилю ISO 8601-1:2019, можно использовать собственную логику синтаксического анализа сериализатора. Вы также можете реализовать резервный механизм. В следующем примере показано, что после сбоя синтаксического анализа текстового DateTime представления с помощью TryGetDateTime(DateTime)преобразователя успешно анализирует данные с помощью Parse(String):

using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

namespace DateTimeConverterExamples;

public class DateTimeConverterUsingDateTimeParseAsFallback : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        Debug.Assert(typeToConvert == typeof(DateTime));

        if (!reader.TryGetDateTime(out DateTime value))
        {
            value = DateTime.Parse(reader.GetString()!);
        }

        return value;
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString("dd/MM/yyyy"));
    }
}

class Program
{
    private static void ParseDateTimeWithDefaultOptions()
    {
        DateTime _ = JsonSerializer.Deserialize<DateTime>(@"""2019-07-16 16:45:27.4937872+00:00""");
    }

    private static void ProcessDateTimeWithCustomConverter()
    {
        JsonSerializerOptions options = new JsonSerializerOptions();
        options.Converters.Add(new DateTimeConverterUsingDateTimeParseAsFallback());

        string testDateTimeStr = "2019-07-16 16:45:27.4937872+00:00";
        string testDateTimeJson = @"""" + testDateTimeStr + @"""";

        DateTime resultDateTime = JsonSerializer.Deserialize<DateTime>(testDateTimeJson, options);
        Console.WriteLine(resultDateTime);

        string resultDateTimeJson = JsonSerializer.Serialize(DateTime.Parse(testDateTimeStr), options);
        Console.WriteLine(Regex.Unescape(resultDateTimeJson));
    }

    static void Main(string[] args)
    {
        // Parsing non-compliant format as DateTime fails by default.
        try
        {
            ParseDateTimeWithDefaultOptions();
        }
        catch (JsonException e)
        {
            Console.WriteLine(e.Message);
        }

        // Using converters gives you control over the serializers parsing and formatting.
        ProcessDateTimeWithCustomConverter();
    }
}

// The example displays output similar to the following:
// The JSON value could not be converted to System.DateTime.Path: $ | LineNumber: 0 | BytePositionInLine: 35.
// 7/16/2019 4:45:27 PM
// "16/07/2019"

Использование формата дат эпохи Unix

Следующие преобразователи обрабатывают формат эпохи Unix со смещением часового пояса или без нее (например/Date(1590863400000-0700)/, ):/Date(1590863400000)/

sealed class UnixEpochDateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
    static readonly DateTimeOffset s_epoch = new(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
    static readonly Regex s_regex = new("^/Date\\(([+-]*\\d+)([+-])(\\d{2})(\\d{2})\\)/$", RegexOptions.CultureInvariant);

    public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        string formatted = reader.GetString()!;
        Match match = s_regex.Match(formatted);

        if (
                !match.Success
                || !long.TryParse(match.Groups[1].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out long unixTime)
                || !int.TryParse(match.Groups[3].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out int hours)
                || !int.TryParse(match.Groups[4].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out int minutes))
        {
            throw new JsonException();
        }

        int sign = match.Groups[2].Value[0] == '+' ? 1 : -1;
        TimeSpan utcOffset = new(hours * sign, minutes * sign, 0);

        return s_epoch.AddMilliseconds(unixTime).ToOffset(utcOffset);
    }

    public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
    {
        long unixTime = Convert.ToInt64((value - s_epoch).TotalMilliseconds);
        TimeSpan utcOffset = value.Offset;

        string formatted = string.Create(CultureInfo.InvariantCulture, $"/Date({unixTime}{(utcOffset >= TimeSpan.Zero ? "+" : "-")}{utcOffset:hhmm})/");

        writer.WriteStringValue(formatted);
    }
}
sealed class UnixEpochDateTimeConverter : JsonConverter<DateTime>
{
    static readonly DateTime s_epoch = new(1970, 1, 1, 0, 0, 0);
    static readonly Regex s_regex = new("^/Date\\(([+-]*\\d+)\\)/$", RegexOptions.CultureInvariant);

    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        string formatted = reader.GetString()!;
        Match match = s_regex.Match(formatted);

        if (
                !match.Success
                || !long.TryParse(match.Groups[1].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out long unixTime))
        {
            throw new JsonException();
        }

        return s_epoch.AddMilliseconds(unixTime);
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        long unixTime = Convert.ToInt64((value - s_epoch).TotalMilliseconds);

        string formatted = string.Create(CultureInfo.InvariantCulture, $"/Date({unixTime})/");
        writer.WriteStringValue(formatted);
    }
}

При использовании Utf8JsonWriter

Если вы хотите написать пользовательское DateTime или DateTimeOffset текстовое представление, можно отформатировать пользовательское представление Utf8JsonWriterв , ReadOnlySpan<Byte>StringReadOnlySpan<Char>или JsonEncodedTextзатем передать его соответствующему Utf8JsonWriter.WriteStringValue или Utf8JsonWriter.WriteString методу.

В следующем примере показано, как можно создать настраиваемый DateTime формат с ToString(String, IFormatProvider) помощью метода, а затем записать его WriteStringValue(String) .

using System.Globalization;
using System.Text;
using System.Text.Json;

public class Example
{
    public static void Main(string[] args)
    {
        var options = new JsonWriterOptions
        {
            Indented = true
        };

        using (var stream = new MemoryStream())
        {
            using (var writer = new Utf8JsonWriter(stream, options))
            {
                string dateStr = DateTime.UtcNow.ToString("F", CultureInfo.InvariantCulture);

                writer.WriteStartObject();
                writer.WriteString("date", dateStr);
                writer.WriteNumber("temp", 42);
                writer.WriteEndObject();
            }

            string json = Encoding.UTF8.GetString(stream.ToArray());
            Console.WriteLine(json);
        }
    }
}

// The example displays output similar to the following:
// {
//     "date": "Tuesday, 27 August 2019 19:21:44",
//     "temp": 42
// }

При использовании Utf8JsonReader

Если вы хотите прочитать пользовательское DateTime или DateTimeOffset текстовое представление Utf8JsonReader, можно получить значение текущего токена JSON в качестве String GetString() метода, а затем проанализировать значение с помощью пользовательской логики.

В следующем примере показано, как можно получить пользовательское DateTimeOffset текстовое представление с помощью метода, а затем проанализировать с помощью GetString() ParseExact(String, String, IFormatProvider):

using System.Globalization;
using System.Text;
using System.Text.Json;

public class Example
{
    public static void Main(string[] args)
    {
        byte[] utf8Data = Encoding.UTF8.GetBytes(@"""Friday, 26 July 2019 00:00:00""");

        var json = new Utf8JsonReader(utf8Data);
        while (json.Read())
        {
            if (json.TokenType == JsonTokenType.String)
            {
                string value = json.GetString();
                DateTimeOffset dto = DateTimeOffset.ParseExact(value, "F", CultureInfo.InvariantCulture);
                Console.WriteLine(dto);
            }
        }
    }
}

// The example displays output similar to the following:
// 7/26/2019 12:00:00 AM -04:00

Расширенный профиль ISO 8601-1:2019 в System.Text.Json

Компоненты даты и времени

Расширенный профиль ISO 8601-1:2019, реализованный в System.Text.Json определении следующих компонентов для представлений даты и времени. Эти компоненты используются для определения различных поддерживаемых уровней детализации при анализе и форматировании DateTime и DateTimeOffset представлениях.

Компонент Формат Description
Year "yyyy" 0001-9999
месяц "MM" 01-12
день "dd" 01-28, 01-29, 01-30, 01-31 на основе месяца или года.
Часы "HH" 00-23
Minute "mm" 00-59
Second "сс" 00-59
Вторая дробь "FFFFFFF" Не менее одной цифры, не более 16 цифр.
Смещение времени "K" Либо Z, либо "('+'/'-)HH':'mm".
Частичное время "HH':'mm'ss[FFFFFFFFF]" Время без сведений смещения в формате UTC.
Полная дата "гггг'-'ММ'-дд" Дата календаря.
Полный рабочий день "'Частичное время'K" UTC дня или локального времени с смещением времени между местным временем и UTC.
Дата и время "'Полная дата'T'Полный рабочий день'" Дата и время дня календаря, например 2019-07-26T16:59:57-05:00.

Поддержка синтаксического анализа

Для синтаксического анализа определены следующие уровни детализации:

  1. "Полная дата"

    1. "гггг'-'ММ'-дд"
  2. "'Full date'T'Hour'':'Minute'"

    1. "гггг'-'ММ'-дд'T'HH':'mm"
  3. "'Полная дата'не'Частичное время'"

    1. "гггг'-'ММ'-dd'T'HH':'mm'ss" (Описатель формата сортировки ("s")
    2. "гггг'-'ММ'-dd'T'HH':'mm'ss'. FFFFFFF"
  4. "'Full date'T'Time hour':'Minute'Minute''Time offset'" (Смещение времени)

    1. "гггг'-'ММ'-dd'T'HH':'mmZ"
    2. "гггг'-'ММ'-dd'T'HH':'mm('+'/')HH':'mm"
  5. "Дата и время"

    1. "гггг'-'ММ'-dd'T'HH':'mm'ssZ"
    2. "гггг'-'ММ'-dd'T'HH':'mm'ss'. FFFFFFFZ"
    3. "гггг'-'ММ'-dd'T'HH':'mm'ss('+'/'-)HH':'mm"
    4. "гггг'-'ММ'-dd'T'HH':'mm'ss'. FFFFFFF('+'/'-)HH':'mm"

    Этот уровень детализации соответствует стандарту RFC 3339, широко принятому профилю ISO 8601, используемому для чередующихся сведений о дате и времени. Однако в реализации есть несколько ограничений System.Text.Json .

    • RFC 3339 не указывает максимальное количество дробных секундных цифр, но указывает, что по крайней мере одна цифра должна следовать за периодом, если присутствует раздел дробной секунды. System.Text.Json Реализация позволяет выполнять до 16 цифр (поддерживать взаимодействие с другими языками программирования и платформами), но анализирует только первые семь. При JsonException чтении и экземплярах создается исключение, если при чтении DateTime и DateTimeOffset экземплярах имеется более 16 дробных секунд.
    • RFC 3339 позволяет символам T и Z быть "t" или "z" соответственно, но позволяет приложениям ограничить поддержку только вариантов верхнего регистра. Реализация в System.Text.Json них должна быть "T" и "Z". При JsonException чтении DateTime и DateTimeOffset экземплярах будет возникать исключение, если полезные данные ввода содержат "t" или "z".
    • RFC 3339 указывает, что разделы даты и времени разделяются "T", но позволяют приложениям разделять их пробелами ("") вместо этого. System.Text.Json Требуется разделение разделов даты и времени с помощью "T". Если JsonException входные полезные данные содержат пробел ("") при чтении DateTime и DateTimeOffset экземплярах, возникает исключение.

Если в секундах имеется десятичная дробь, должно быть по крайней мере одна цифра. 2019-07-26T00:00:00. не разрешено. Хотя допускается до 16 дробных цифр, только первые семь анализируются. Все, что выходит за рамки этого, считается нулевым. Например, 2019-07-26T00:00:00.1234567890 будет синтаксический анализ, как если бы он был 2019-07-26T00:00:00.1234567. Этот подход поддерживает совместимость с DateTime реализацией, которая ограничена этим разрешением.

Секунды прыжка не поддерживаются.

Поддержка форматирования

Для форматирования определены следующие уровни детализации:

  1. "'Полная дата'не'Частичное время'"

    1. "гггг'-'ММ'-dd'T'HH':'mm'ss" (Описатель формата сортировки ("s")

      Используется для форматирования DateTime без дробных секунд и без сведений смещения.

    2. "гггг'-'ММ'-dd'T'HH':'mm'ss'. FFFFFFF"

      Используется для форматирования DateTime с дробными секундами, но без сведений смещения.

  2. "Дата и время"

    1. "гггг'-'ММ'-dd'T'HH':'mm'ssZ"

      Используется для форматирования DateTime без дробных секунд, но с смещением UTC.

    2. "гггг'-'ММ'-dd'T'HH':'mm'ss'. FFFFFFFZ"

      Используется для форматирования DateTime дробных секунд и смещения в формате UTC.

    3. "гггг'-'ММ'-dd'T'HH':'mm'ss('+'/'-)HH':'mm"

      Используется для форматирования DateTime дробной секунды или DateTimeOffset без нее, но с локальным смещением.

    4. "гггг'-'ММ'-dd'T'HH':'mm'ss'. FFFFFFF('+'/'-)HH':'mm"

      Используется для форматирования DateTime DateTimeOffset дробных секунд и с локальным смещением.

    Этот уровень детализации соответствует RFC 3339.

Если представление формата кругового пути для экземпляра DateTime DateTimeOffset имеет конечные нули в дробных секундах, то JsonSerializer и Utf8JsonWriter отформатирует представление экземпляра без нуля. Например, экземпляр, DateTime представление 2019-04-24T14:50:17.1010000Zформата кругового пути которого равно, будет отформатировано как 2019-04-24T14:50:17.101Z и Utf8JsonWriterJsonSerializer .

Если представление формата кругового пути экземпляра DateTimeOffset DateTime имеет все нули в дробных секундах, то JsonSerializer и Utf8JsonWriter отформатирует представление экземпляра без дробных секунд. Например, экземпляр, DateTime представление 2019-04-24T14:50:17.0000000+02:00формата кругового пути которого равно, будет отформатировано как 2019-04-24T14:50:17+02:00 и Utf8JsonWriterJsonSerializer .

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

Записывается не более семи дробных секунд. Это максимальное DateTime значение соответствует реализации, которая ограничена этим разрешением.