Compatibilidad con DateTime y DateTimeOffset en System.Text.Json

La biblioteca System.Text.Json analiza y escribe DateTime y valores DateTimeOffset según el perfil extendido ISO 8601-1:2019. Los convertidores proporcionan compatibilidad personalizada para serializar y deserializar con JsonSerializer. También puede usar Utf8JsonReader y Utf8JsonWriter para implementar compatibilidad personalizada.

Compatibilidad con el formato ISO 8601-1:2019

Los tipos JsonSerializer, Utf8JsonReader, Utf8JsonWriter y JsonElement analizan y escriben representaciones de texto DateTime y DateTimeOffset según el perfil extendido del formato ISO 8601-1:2019. Por ejemplo, 2019-07-26T16:59:57-05:00.

Los datos DateTime y DateTimeOffset se pueden serializar con 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 y DateTimeOffset también se pueden deserializar con 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

Con las opciones predeterminadas, las representaciones de entrada DateTime y DateTimeOffset texto deben cumplir el perfil extendido ISO 8601-1:2019. Si se intenta deserializar las representaciones que no se ajustan al perfil, se producirá JsonSerializer una 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.

El JsonDocument proporciona acceso estructurado al contenido de una carga JSON, incluidas DateTime las representaciones y DateTimeOffset. En el ejemplo siguiente se muestra cómo calcular la temperatura media de los lunes a partir de una colección 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

Si intenta calcular la temperatura media dada una carga con representaciones no compatibles DateTime , hará que JsonDocument lance una 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()

Los datos y Utf8JsonWriter escrituras DateTime de nivel DateTimeOffset 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álisis DateTime y DateTimeOffset datos:

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

Si se intenta leer formatos no compatibles con Utf8JsonReader, se producirá una FormatExceptionexcepción :

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()

Serialización de las propiedades DateOnly y TimeOnly

Con .NET 7 y versiones posteriores, System.Text.Json admite la serialización y deserialización de los tipos DateOnly y TimeOnly. Tenga en cuenta el siguiente objeto:

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

En el ejemplo siguiente se serializa un objeto Appointment, se muestra el código JSON resultante y, luego, se deserializa en una nueva instancia del tipo Appointment. Por último, las instancias originales y recién deserializadas se comparan para ver si son iguales y los resultados se escriben en la consola:

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

En el código anterior:

  • Se crea una instancia de un objeto Appointment y se asigna a la variable appointment.
  • La instancia appointment se serializa en JSON mediante JsonSerializer.Serialize.
  • El código JSON resultante se escribe en la consola.
  • El código JSON se deserializa de nuevo en una nueva instancia del tipo Appointment mediante JsonSerializer.Deserialize.
  • Las instancias originales y recién deserializadas se comparan para ver si son iguales.
  • El resultado de la comparación se escribe en la consola.

Compatibilidad personalizada con DateTime y DateTimeOffset

Al usar JsonSerializer

Si desea que el serializador realice análisis o formato personalizados, puede implementar convertidores personalizados. Estos son algunos ejemplos:

DateTime(Offset).Parse y DateTime(Offset).ToString

Si no puede determinar los formatos de las representaciones de entrada DateTime o DateTimeOffset texto, puede usar el método en la DateTime(Offset).Parse lógica de lectura del convertidor. Este método le permite usar la amplia compatibilidad de .NET para analizar varios formatos de texto DateTime y DateTimeOffset, incluidas cadenas no ISO 8601 y formatos ISO 8601 que no cumplen con el perfil extendido ISO 8601-1:2019. Este enfoque es menos eficaz que el uso de la implementación nativa del serializador.

Para serializar, puede usar el método en la DateTime(Offset).ToString lógica de escritura del convertidor. Este método permite escribir DateTime valores y DateTimeOffset usar cualquiera de los formatos de fecha y hora estándar, así como los formatos de fecha y hora personalizados. Este enfoque es también menos eficaz que el uso de la implementación nativa del 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

Al implementar JsonConverter<T>, y T es DateTime, el typeToConvert parámetro siempre será typeof(DateTime). El parámetro es útil para controlar casos polimórficos y cuando se usan genéricos para obtener typeof(T) de una manera eficaz.

Utf8Parser y Utf8Formatter

Puede usar métodos de análisis y formato basados en UTF-8 rápidos en la lógica del convertidor si las representaciones de entrada DateTime o DateTimeOffset texto son compatibles con una de las cadenas de formato de fecha y hora estándar «R», «l», «O» o «G», o si desea escribir según uno de estos formatos. Este enfoque es mucho más rápido que usar DateTime(Offset).Parse y DateTime(Offset).ToString.

En el ejemplo siguiente se muestra un convertidor personalizado que serializa y deserializa valores DateTime según el formato estándar «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

El formato estándar «R» siempre tendrá 29 caracteres.

El formato «l» («L») en minúsculas no está documentado con las demás cadenas de formato de fecha y hora estándar, ya que solo es compatible con los tipos Utf8Parser y Utf8Formatter. El formato es RFC 1123 en minúsculas (una versión minúscula del formato «R»). Por ejemplo, "thu, 25 jul 2019 06:36:07 gmt".

Usar DateTime(Offset).Parse como reserva

Si normalmente espera que la entrada DateTime o DateTimeOffset los datos se ajusten al perfil extendido ISO 8601-1:2019, puede usar la lógica de análisis nativa del serializador. También puede implementar un mecanismo de reserva. En el ejemplo siguiente se muestra que, después de no analizar una DateTime representación de texto mediante TryGetDateTime(DateTime), el convertidor analiza correctamente los datos mediante 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"

Usar el formato de fecha de época de Unix

Los convertidores siguientes controlan el formato de época de Unix con o sin un desplazamiento de zona horaria (valores como /Date(1590863400000-0700)/ o /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);
    }
}

Al usar Utf8JsonWriter

Si deseas escribir una representación de texto DateTime o DateTimeOffset personalizada con Utf8JsonWriter, puedes dar formato a su representación personalizada a String, ReadOnlySpan<Byte>, ReadOnlySpan<Char> o JsonEncodedText y pasarla al método o correspondienteUtf8JsonWriter.WriteStringValue o Utf8JsonWriter.WriteString.

En el ejemplo siguiente se muestra cómo se puede crear un formato DateTime personalizado con ToString(String, IFormatProvider) y, a continuación, se escribe con el método 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
// }

Al usar Utf8JsonReader

Si desea leer una representación de texto o DateTime personalizada DateTimeOffset con Utf8JsonReader, puede obtener el valor del token JSON actual como un String mediante el método GetString() y, a continuación, analizar el valor mediante lógica personalizada.

En el ejemplo siguiente se muestra cómo se puede recuperar una representación de texto DateTimeOffset personalizada mediante el método GetString() y, a continuación, analizar mediante 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

El perfil extendido ISO 8601-1:2019 en System.Text.Json

Componentes de fecha y hora

El perfil extendido ISO 8601-1:2019 implementado en System.Text.Json define los siguientes componentes para las representaciones de fecha y hora. Estos componentes se usan para definir varios niveles de granularidad admitidos al analizar y dar formato a representaciones DateTime y DateTimeOffset.

Componente Formato Descripción
Year "yyyy" 0001-9999
Month (Mes) "MM" 01-12
Día "dd" 01-28, 01-29, 01-30, 01-31 según mes/año.
Hora "HH" 00-23
Minuto "mm" 00-59
Segundo "ss" 00-59
Segunda fracción "FFFFFFF" Mínimo de un dígito, máximo de 16 dígitos.
Compensación de tiempo "K" «Z» o «(«+»/«-»)HH»:»mm».
Tiempo parcial "HH':'mm':'ss[FFFFFFF]" Hora sin información de diferencia horaria con UTC.
Fecha completa "yyyy'-'MM'-'dd" Fecha del calendario.
Hora completa Tiempo parcial «K» UTC del día o hora local del día con el desplazamiento de hora entre la hora local y la hora UTC.
Datetime «Full date» T «Full time» Fecha y hora del día del calendario, por ejemplo, 2019-07-26T16:59:57-05:00.

Compatibilidad con el análisis

Se definen los siguientes niveles de granularidad para el análisis:

  1. «Fecha completa»

    1. "yyyy'-'MM'-'dd"
  2. "'Full date''T''Hour'':''Minute'"

    1. "yyyy'-'MM'-'dd'T'HH':'mm"
  3. «Fecha completa»T«Hora parcial»

    1. "aaaa'-'MM'-'dd'T'HH':'mm':'ss" (El especificador de formato ordenable ("s"))
    2. "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'FFFFFFF"
  4. "'Full date''T''Time hour'':'Minute''Time offset'"

    1. "yyyy'-'MM'-'dd'T'HH':'mmZ"
    2. "yyyy'-'MM'-'dd'T'HH':'mm('+'/'-')HH':'mm"
  5. «Fecha Hora»

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

    Este nivel de granularidad es compatible con RFC 3339, un perfil ampliamente adoptado de ISO 8601 que se usa para intercambiar información de fecha y hora. Sin embargo, hay algunas restricciones en la implementación de System.Text.Json.

    • RFC 3339 no especifica un número máximo de dígitos fraccionarios de segundo, pero especifica que al menos un dígito debe seguir el período, si existe una sección de fracciones de segundo. La implementación de System.Text.Json permite hasta 16 dígitos (para admitir la interoperabilidad con otros lenguajes de programación y marcos), pero analiza solo los siete primeros. Se producirá una JsonException excepción si hay más de 16 dígitos de segundo fraccionario al leer DateTime e DateTimeOffset instancias.
    • RFC 3339 permite que los caracteres «T» y «Z» sean «t» o «z», respectivamente, pero permite a las aplicaciones limitar la compatibilidad con solo las variantes en mayúsculas. La implementación de System.Text.Json requiere que sean «T» y «Z». Se producirá una JsonException excepción si las cargas de entrada contienen «t» o «z» al leer DateTime e DateTimeOffset instancias.
    • RFC 3339 especifica que las secciones de fecha y hora están separadas por «T», pero permite a las aplicaciones separarlas por un espacio («») en su lugar. System.Text.Json requiere que las secciones de fecha y hora estén separadas con «T». Se producirá una JsonException si las cargas de entrada contienen un espacio (« ») al leer las instancias DateTime y DateTimeOffset.

Si hay fracciones decimales durante segundos, debe haber al menos un dígito. 2019-07-26T00:00:00. no está permitido. Aunque se permiten hasta 16 dígitos fraccionarios, solo se analizan los siete primeros. Cualquier cosa más allá de eso se considera cero. Por ejemplo, 2019-07-26T00:00:00.1234567890 se analizará como si fuera 2019-07-26T00:00:00.1234567. Este enfoque mantiene la compatibilidad con la implementación DateTime, que se limita a esta resolución.

No se admiten segundos intercalares.

Compatibilidad con el formato

Se definen los siguientes niveles de granularidad para el análisis:

  1. «Fecha completa»T«Hora parcial»

    1. "aaaa'-'MM'-'dd'T'HH':'mm':'ss" (El especificador de formato ordenable ("s"))

      Se usa para dar formato a un sin DateTime fracciones de segundo y sin información de desplazamiento.

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

      Se usa para dar formato a un objeto DateTime con fracciones de segundos, pero sin información de desplazamiento.

  2. «Fecha Hora»

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

      Se usa para dar formato a un sin DateTime fracciones de segundos, pero con un desplazamiento UTC.

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

      Se usa para dar formato a un objeto DateTime con fracciones de segundo y con un desplazamiento UTC.

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

      Se usa para dar formato a una DateTime o DateTimeOffset sin fracciones de segundos, pero con un desplazamiento local.

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

      Se usa para dar formato a un DateTime o DateTimeOffset con fracciones de segundo y con un desplazamiento local.

    Este nivel de granularidad es compatible con RFC 3339.

Si la representación del formato de ida y vuelta de una DateTime instancia de o DateTimeOffset tiene ceros finales en sus fracciones de segundo, JsonSerializer y Utf8JsonWriter dará formato a una representación de la instancia sin ceros finales. Por ejemplo, una DateTime instancia de cuya representación de formato de ida y vuelta es 2019-04-24T14:50:17.1010000Z, tendrá el formato 2019-04-24T14:50:17.101Z por JsonSerializer y Utf8JsonWriter.

Si la representación del formato de ida y vuelta de una DateTime instancia de o DateTimeOffset tiene todos los ceros en sus fracciones de segundo, JsonSerializer y Utf8JsonWriter dará formato a una representación de la instancia sin fracciones de segundos. Por ejemplo, una DateTime instancia de cuya representación de formato de ida y vuelta es 2019-04-24T14:50:17.0000000+02:00, tendrá el formato 2019-04-24T14:50:17+02:00JsonSerializer y Utf8JsonWriter.

El truncamiento de ceros en dígitos fraccionarios de segundo permite escribir la salida más pequeña necesaria para conservar la información de un recorrido de ida y vuelta.

Se escribe un máximo de siete dígitos de fracciones de segundo. Este máximo se alinea con la DateTime implementación, que se limita a esta resolución.