Partilhar via


Suporte a DateTime e DateTimeOffset em System.Text.Json

A System.Text.Json biblioteca analisa, grava DateTime e DateTimeOffset quantifica de acordo com o perfil estendido ISO 8601-1:2019. Os conversores fornecem suporte personalizado para serialização e desserialização com JsonSerializero . Você também pode usar Utf8JsonReader e Utf8JsonWriter implementar suporte personalizado.

Suporte para o formato ISO 8601-1:2019

Os JsonSerializertipos , , Utf8JsonReaderUtf8JsonWriter, e JsonElement analisam e escrevem DateTime e DateTimeOffset as representações de texto de acordo com o perfil estendido do formato ISO 8601-1:2019. Por exemplo, 2019-07-26T16:59:57-05:00.

DateTime e DateTimeOffset os dados podem ser serializados com 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 e DateTimeOffset também pode ser desserializado com 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

Com as opções padrão, as representações de entrada DateTime e DateTimeOffset texto devem estar em conformidade com o perfil estendido ISO 8601-1:2019. A tentativa de desserializar representações que não estão de acordo com o perfil fará com JsonSerializer que um 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.

O JsonDocument fornece acesso estruturado ao conteúdo de uma carga JSON útil, incluindo DateTime e DateTimeOffset representações. O exemplo a seguir mostra como calcular a temperatura média às segundas-feiras a partir de uma coleção de temperaturas:

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

Tentar calcular a temperatura média dada uma carga útil com representações não conformes DateTime fará com que JsonDocument se lance um 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()

As gravações DateTime e DateTimeOffset dados de nível Utf8JsonWriter inferior:

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 Análises DateTime e DateTimeOffset dados:

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

Tentar ler formatos não compatíveis com fará com Utf8JsonReader que ele lance um 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()

Serializar as propriedades DateOnly e TimeOnly

Com o .NET 7+, System.Text.Json suporta serialização e desserialização DateOnly e TimeOnly tipos. Considere o seguinte objeto:

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

O exemplo a seguir serializa um Appointment objeto, exibe o JSON resultante e o desserializa novamente em uma nova instância do Appointment tipo. Finalmente, as instâncias originais e recém-desserializadas são comparadas para igualdade e os resultados são gravados no console:

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}
    """);

No código anterior:

  • Um Appointment objeto é instanciado appointment e atribuído à variável.
  • A appointment instância é serializada para JSON usando JsonSerializer.Serialize.
  • O JSON resultante é gravado no console.
  • O JSON é desserializado novamente em uma nova instância do Appointment tipo usando JsonSerializer.Deserialize.
  • As instâncias originais e recém-desserializadas são comparadas para igualdade.
  • O resultado da comparação é gravado no console.

Suporte personalizado para DateTime e DateTimeOffset

Ao utilizar JsonSerializer

Se desejar que o serializador execute análise ou formatação personalizadas, você pode implementar conversores personalizados. Eis alguns exemplos:

DateTime(Deslocamento). Parse e DateTime(Offset). ToString

Se não for possível determinar os formatos das suas representações de entrada DateTime ou DateTimeOffset texto, você pode usar o DateTime(Offset).Parse método na lógica de leitura do conversor. Este método permite que você use o . Suporte extensivo da NET para análise de vários DateTime formatos de DateTimeOffset texto, incluindo strings não ISO 8601 e formatos ISO 8601 que não estão em conformidade com o perfil estendido ISO 8601-1:2019. Essa abordagem é menos eficiente do que usar a implementação nativa do serializador.

Para serialização, você pode usar o DateTime(Offset).ToString método em sua lógica de gravação do conversor. Esse método permite que você escreva DateTime e DateTimeOffset valores usando qualquer um dos formatos de data e hora padrão e os formatos de data e hora personalizados. Essa abordagem também é menos eficiente do que usar a implementação nativa do serializador.

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"

Nota

Ao implementar JsonConverter<T>, e T é DateTime, o typeToConvert parâmetro será sempre typeof(DateTime). O parâmetro é útil para lidar com casos polimórficos e ao usar genéricos para obter typeof(T) um desempenho.

Utf8Parser e Utf8Formatter

Você pode usar métodos rápidos de análise e formatação baseados em UTF-8 em sua lógica de conversor se suas representações de entrada DateTime ou DateTimeOffset texto forem compatíveis com uma das cadeias de caracteres de formato de data e hora padrão "R", "l", "O" ou "G", ou se você quiser escrever de acordo com um desses formatos. Esta abordagem é muito mais rápida do que usar DateTime(Offset).Parse e DateTime(Offset).ToString.

O exemplo a seguir mostra um conversor personalizado que serializa e desserializa DateTime valores de acordo com o formato padrão "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"

Nota

O formato padrão "R" terá sempre 29 caracteres.

O formato "l" (letra minúscula "L") não está documentado com as outras cadeias de caracteres de formato de data e hora padrão porque é suportado Utf8Parser apenas pelos tipos e Utf8Formatter . O formato é RFC 1123 minúsculo (uma versão minúscula do formato "R"). Por exemplo, "qui, 25 jul 2019 06:36:07 gmt".

Use DateTime(Offset). Analisar como um fallback

Se você geralmente espera que sua entrada DateTime ou DateTimeOffset dados estejam em conformidade com o perfil estendido ISO 8601-1:2019, você pode usar a lógica de análise nativa do serializador. Você também pode implementar um mecanismo de fallback. O exemplo a seguir mostra que, depois de falhar ao analisar uma DateTime representação de texto usando TryGetDateTime(DateTime), o conversor analisa com êxito os dados usando 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"

Use o formato de data de época Unix

Os conversores a seguir lidam com o formato Unix epoch com ou sem um deslocamento de fuso horário (valores como /Date(1590863400000-0700)/ ou /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);
    }
}

Ao utilizar Utf8JsonWriter

Se quiser escrever uma representação personalizada DateTime ou de texto com Utf8JsonWritero , você pode formatar sua representação personalizada para um String, ReadOnlySpan<Byte>, ReadOnlySpan<Char>, ou , e JsonEncodedTextpassá-la para o método ou correspondente Utf8JsonWriter.WriteStringValue Utf8JsonWriter.WriteString.DateTimeOffset

O exemplo a seguir mostra como um formato personalizado DateTime pode ser criado com ToString(String, IFormatProvider) e, em seguida, escrito com o WriteStringValue(String) método:

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
// }

Ao utilizar Utf8JsonReader

Se quiser ler uma representação personalizada DateTime ou DateTimeOffset de texto com Utf8JsonReadero , você pode obter o valor do token JSON atual como um String método using GetString() e, em seguida, analisar o valor usando a lógica personalizada.

O exemplo a seguir mostra como uma representação de texto personalizada DateTimeOffset pode ser recuperada usando o GetString() método e, em seguida, analisada usando 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

O perfil estendido da ISO 8601-1:2019 em System.Text.Json

Componentes de data e hora

O perfil estendido ISO 8601-1:2019 implementado no System.Text.Json define os seguintes componentes para representações de data e hora. Esses componentes são usados para definir vários níveis suportados de granularidade ao analisar, formatar DateTime e DateTimeOffset representar.

Componente Formato Description
Year "AAAA" 0001-9999
Month "MM" 01-12
Dia "DD" 01-28, 01-29, 01-30, 01-31 com base no mês/ano.
Hora "HH" 00-23
Minuto "Milímetro" 00-59
Second "SS" 00-59
Segunda fração "FFFFFFF" Mínimo de um dígito, máximo de 16 dígitos.
Compensação de tempo "K" "Z" ou "('+'/'-')HH':'mm".
Tempo parcial "HH':'mm':'ss[FFFFFFF]" Tempo sem informações de deslocamento UTC.
Data completa "aaaa'-'MM'-'dd" Data do calendário.
A tempo inteiro "'Tempo parcial'K" UTC do dia ou hora local do dia com a hora deslocada entre a hora local e a UTC.
Data/Hora "'Data completa''T''A tempo inteiro'" Data e hora do dia do calendário, por exemplo, 2019-07-26T16:59:57-05:00.

Suporte para análise

Os seguintes níveis de granularidade são definidos para análise:

  1. 'Data completa'

    1. "aaaa'-'MM'-'dd"
  2. "'Data completa''T''Hora'':''Minuto'"

    1. "aaaa'-'MM'-'dd'T'HH':'mm"
  3. "'Data completa''T''Tempo parcial'"

    1. "yyyy'-'MM'-'dd'T'HH':'mm':'ss" (O Especificador de Formato Sortable ("s")
    2. "aaaa'-'MM'-'dd'T'HH':'mm':'ss'.' FFFFFFF"
  4. "'Data completa''T''Hora hora'':''Minuto''Compensação de tempo'"

    1. "aaaa'-'MM'-'dd'T'HH':'mmZ"
    2. "aaaa'-'MM'-'dd'T'HH':'mm('+'/'-')HH':'mm"
  5. 'Data e hora'

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

    Este nível de granularidade é compatível com RFC 3339, um perfil amplamente adotado da ISO 8601 usado para trocar informações de data e hora. No entanto, existem algumas restrições na System.Text.Json implementação.

    • O RFC 3339 não especifica um número máximo de dígitos de segundo fracionário, mas especifica que pelo menos um dígito deve seguir o período, se uma seção de segundo fracionário estiver presente. A implementação em System.Text.Json permite até 16 dígitos (para suportar interoperabilidade com outras linguagens de programação e frameworks), mas analisa apenas os primeiros sete. A JsonException será lançado se houver mais de 16 segundos fracionários ao ler DateTime e DateTimeOffset instâncias.
    • O RFC 3339 permite que os caracteres "T" e "Z" sejam "t" ou "z", respectivamente, mas permite que os aplicativos limitem o suporte apenas às variantes maiúsculas. A implementação em System.Text.Json exige que eles sejam "T" e "Z". A JsonException será lançado se as cargas úteis de entrada contiverem "t" ou "z" durante a leitura DateTime e DateTimeOffset instâncias.
    • RFC 3339 especifica que as seções de data e hora são separadas por "T", mas permite que os aplicativos as separem por um espaço (" ") em vez disso. System.Text.Json requer que as seções de data e hora sejam separadas com "T". A JsonException será lançado se as cargas úteis de entrada contiverem um espaço (" ") durante a leitura DateTime e DateTimeOffset instâncias.

Se houver frações decimais por segundos, deve haver pelo menos um dígito. 2019-07-26T00:00:00. não é permitido. Embora até 16 dígitos fracionários sejam permitidos, apenas os sete primeiros são analisados. Qualquer coisa além disso é considerada um zero. Por exemplo, 2019-07-26T00:00:00.1234567890 será analisado como se fosse 2019-07-26T00:00:00.1234567. Esta abordagem mantém a compatibilidade com a DateTime implementação, que se limita a esta resolução.

Não há suporte para segundos bissextos.

Suporte para formatação

Os seguintes níveis de granularidade são definidos para formatação:

  1. "'Data completa''T''Tempo parcial'"

    1. "yyyy'-'MM'-'dd'T'HH':'mm':'ss" (O Especificador de Formato Sortable ("s")

      Usado para formatar um DateTime sem frações de segundos e sem informações de deslocamento.

    2. "aaaa'-'MM'-'dd'T'HH':'mm':'ss'.' FFFFFFF"

      Usado para formatar um DateTime com frações de segundos, mas sem informações de deslocamento.

  2. 'Data e hora'

    1. "aaaa'-'MM'-'dd'T'HH':'mm':'ssZ"

      Usado para formatar um DateTime sem segundos fracionários, mas com um deslocamento UTC.

    2. "aaaa'-'MM'-'dd'T'HH':'mm':'ss'.' FFFFFFFZ"

      Usado para formatar um DateTime com frações de segundos e com um deslocamento UTC.

    3. "aaaa'-'MM'-'dd'T'HH':'mm':'ss('+'/'-')HH':'mm"

      Usado para formatar um DateTime ou DateTimeOffset sem segundos fracionários, mas com um deslocamento local.

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

      Usado para formatar um DateTime ou DateTimeOffset com segundos fracionários e com um deslocamento local.

    Este nível de granularidade é compatível com RFC 3339.

Se a representação de formato de ida e volta de uma DateTime ou DateTimeOffset instância tiver zeros à direita em seus segundos fracionários, então JsonSerializer e Utf8JsonWriter formatará uma representação da instância sem zeros à direita. Por exemplo, uma DateTime instância cuja representação de formato de ida e volta é 2019-04-24T14:50:17.1010000Z, será formatada como 2019-04-24T14:50:17.101Z por JsonSerializer e Utf8JsonWriter.

Se a representação de formato de ida e volta de uma DateTime ou DateTimeOffset instância tiver todos os zeros em seus segundos fracionários, então JsonSerializer e Utf8JsonWriter formatará uma representação da instância sem segundos fracionários. Por exemplo, uma DateTime instância cuja representação de formato de ida e volta é 2019-04-24T14:50:17.0000000+02:00, será formatada como 2019-04-24T14:50:17+02:00 por JsonSerializer e Utf8JsonWriter.

Truncar zeros em dígitos de fração de segundo permite que a menor saída necessária para preservar informações em uma viagem de ida e volta seja gravada.

Um máximo de sete dígitos de segundo fracionário são escritos. Este máximo está alinhado com a DateTime implementação, que se limita a esta resolução.