Prise en charge de DateTime et DateTimeOffset dans System.Text.Json

La bibliothèque System.Text.Json analyse et écrit les valeurs DateTime et DateTimeOffset conformément au profil étendu du format ISO 8601-1:2019. Les convertisseurs fournissent une prise en charge personnalisée pour la sérialisation et la désérialisation avec JsonSerializer. Vous pouvez également utiliser Utf8JsonReader et Utf8JsonWriter pour implémenter une prise en charge personnalisée.

Prise en charge du format ISO 8601-1:2019

Les types JsonSerializer, Utf8JsonReader, Utf8JsonWriter et JsonElement analysent et écrivent les représentations textuelles DateTime et DateTimeOffset conformément au profil étendu du format ISO 8601-1:2019. Par exemple : 2019-07-26T16:59:57-05:00.

Les données DateTime et DateTimeOffset peuvent être sérialisées avec 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"}

Les données DateTime et DateTimeOffset peuvent également être désérialisées avec 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

Avec les options par défaut, les représentations de texte DateTime et DateTimeOffset en entrée doivent être conformes au profil étendu du format ISO 8601-1:2019. Toute tentative de désérialisation de représentations qui ne sont pas conformes au profil entraîne la levée d’une exception JsonException par 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"":""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 fournit un accès structuré au contenu d’une charge utile JSON, dont les représentations DateTime et DateTimeOffset. L’exemple suivant montre comment calculer la température moyenne les lundis à partir d’une collection de températures :

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

La tentative de calcul de la température moyenne pour une charge utile contenant des représentations DateTime non conformes entraîne la levée d’une exception FormatException par JsonDocument :

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

Le Utf8JsonWriter de niveau inférieur écrit les données DateTime et 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 analyse les données DateTime et 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

La tentative de lecture de formats non conformes avec Utf8JsonReader entraîne la levée d’une exception 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()

Sérialiser les propriétés DateOnly et TimeOnly

À compter de .NET 7, System.Text.Json prend en charge la sérialisation et la désérialisation des types DateOnly et TimeOnly. Examinez l’objet suivant :

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

L’exemple suivant sérialise un objet Appointment, affiche le JSON obtenu, puis le désérialise dans une nouvelle instance du type Appointment. Enfin, l’instance d’origine et la nouvelle instance désérialisée sont comparées pour vérifier l’égalité, et les résultats sont écrits dans la 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}
    """);

Dans le code précédent :

  • Un objet Appointment est instancié et affecté à la variable appointment.
  • L’instance appointment est sérialisée en JSON avec JsonSerializer.Serialize.
  • Le JSON obtenu est écrit dans la console.
  • Le JSON est désérialisé dans une nouvelle instance du type Appointment avec JsonSerializer.Deserialize.
  • L’instance d’origine et la nouvelle instance désérialisée sont comparées pour vérifier l’égalité.
  • Le résultat de la comparaison est écrit dans la console.

Prise en charge personnalisée pour DateTime et DateTimeOffset

Utilisation de JsonSerializer

Si vous souhaitez que le sérialiseur effectue une analyse ou une mise en forme personnalisées, vous pouvez implémenter des convertisseurs personnalisés. Voici quelques exemples :

DateTime(Offset).Parse et DateTime(Offset).ToString

Si vous ne pouvez pas déterminer les formats des représentations textuelles DateTime ou DateTimeOffset en entrée, utilisez la méthode DateTime(Offset).Parse dans la logique de lecture de vos convertisseurs. Cette méthode vous permet d’utiliser la prise en charge étendue de .NET pour l’analyse de divers formats texte DateTime et DateTimeOffset, y compris les chaînes non ISO 8601 et les formats ISO 8601 qui ne sont pas conformes au profil étendu ISO 8601-1:2019. Cette approche est moins performante que l’implémentation native du sérialiseur.

Pour la sérialisation, vous pouvez utiliser la méthodeDateTime(Offset).ToString dans la logique d’écriture de vos convertisseurs. Cette méthode vous permet d’écrire les valeurs DateTime et DateTimeOffset dans l’un des formats de date et d’heure standard ou dans des formats de date et d’heure personnalisés. Cette approche est également moins performante que l’implémentation native du sérialiseur.

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"

Notes

Quand JsonConverter<T> est implémenté, avec T défini sur DateTime, le paramètre typeToConvert est toujours typeof(DateTime). Le paramètre est utile pour gérer les cas polymorphes et lors de l’utilisation de génériques pour obtenir typeof(T) de manière performante.

Utf8Parser et Utf8Formatter

Vous pouvez utiliser des méthodes d’analyse et de mise en forme rapides basées sur UTF-8 dans la logique de vos convertisseurs si les représentations textuelles DateTime ou DateTimeOffset en entrée sont conformes à l’une des chaînes de format de date et d’heure standard "R", "l", "O" ou "G", ou si vous souhaitez écrire dans l’un de ces formats. Cette approche est beaucoup plus rapide que d’utiliser DateTime(Offset).Parse et DateTime(Offset).ToString.

L’exemple suivant montre un convertisseur personnalisé qui sérialise et désérialise les valeurs DateTime selon le format standard "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"

Notes

Le format standard "R" contient toujours 29 caractères.

Le format "l" ("L" minuscule) n’est pas documenté avec les autres chaînes de format de date et d’heure standard, car il est pris en charge uniquement par les types Utf8Parser et Utf8Formatter. Le format est le format RFC 1123 en minuscules (version en minuscules du format "R" ). Par exemple, "thu, 25 jul 2019 06:36:07 gmt".

Utiliser DateTime(Offset). Analyse en tant que secours

Si vous vous attendez généralement à ce que vos données DateTime ou DateTimeOffset en entrée soient conformes au profil ISO 8601-1:2019 étendu, vous pouvez utiliser la logique d’analyse native du sérialiseur. Vous pouvez également implémenter un mécanisme de secours. L’exemple suivant montre qu’après avoir échoué à analyser une représentation textuelle DateTime avec TryGetDateTime(DateTime), le convertisseur réussit à analyser correctement les données en utilisant 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"

Utiliser le format de date epoch Unix

Les convertisseurs suivants gèrent le format Epoch d’Unix avec ou sans décalage de fuseau horaire (valeurs /Date(1590863400000-0700)/ ou /Date(1590863400000)/, par exemple) :

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

Utilisation de Utf8JsonWriter

Si vous souhaitez écrire une représentation textuelle DateTime ou DateTimeOffset personnalisée avec Utf8JsonWriter, vous pouvez mettre en forme votre représentation personnalisée en String, ReadOnlySpan<Byte>, ReadOnlySpan<Char> ou JsonEncodedText, puis la passer à la méthode Utf8JsonWriter.WriteStringValue ou Utf8JsonWriter.WriteString correspondante.

L’exemple suivant montre comment créer un format DateTime personnalisé avec ToString(String, IFormatProvider), puis comment écrire avec la méthode 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
// }

Utilisation de Utf8JsonReader

Si vous souhaitez lire une représentation textuelle DateTime ou DateTimeOffset personnalisée avec Utf8JsonReader, vous pouvez obtenir la valeur du jeton JSON actuel au format String en utilisant la méthode GetString(), puis analyser la valeur avec une logique personnalisée.

L’exemple suivant montre comment une représentation textuelle DateTimeOffset personnalisée peut être récupérée avec la méthode GetString(), puis analysée avec 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

Profil ISO 8601-1:2019 étendu dans System.Text.Json

Composants date/heure

Le profil ISO 8601-1:2019 étendu qui est implémenté dans System.Text.Json définit les composants suivants pour les représentations de date et d’heure. Ces composants sont utilisés pour définir les différents niveaux de granularité pris en charge lors de l’analyse et de la mise en forme des représentations DateTime et DateTimeOffset.

Composant Format Description
Year "yyyy" 0001-9999
Month "MM" 01-12
Jour "dd" 01-28, 01-29, 01-30, 01-31 basé sur mois/année.
Heure "HH" 00-23
Minute "mm" 00-59
Second "ss" 00-59
Second fraction (Fraction de seconde) "FFFFFFF" Un chiffre minimum, 16 chiffres maximum.
Time offset (Décalage d’heure) "K" Soit "Z", soit "('+'/'-')HH':'mm".
Partial time (Heure partielle) "HH':'mm':'ss[FFFFFFF]" Heure sans informations de décalage UTC.
Full date (Date complète) "yyyy'-'MM'-'dd" Date du calendrier.
Full time (Heure complète) "'Partial time'K" Heure UTC du jour ou heure locale du jour avec le décalage entre l’heure locale et l’heure UTC.
Date et heure "'Full date''T''Full time'" Date et heure du jour dans le calendrier, par exemple, 2019-07-26T16:59:57-05:00.

Prise en charge pour l’analyse

Les niveaux de granularité suivants sont définis pour l’analyse :

  1. 'Full date'

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

    1. "yyyy'-'MM'-'dd'T'HH':'mm"
  3. "'Full date''T''Partial time'"

    1. "yyyy'-'MM'-'dd'T'HH':'mm':'ss" (Spécificateur de format Sortable ("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. 'Date time'

    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"

    Ce niveau de granularité est conforme à la norme RFC 3339, un profil ISO 8601 largement adopté et utilisé pour l’échange des informations de date et d’heure. Il existe toutefois quelques restrictions relatives à l’implémentation dans System.Text.Json.

    • RFC 3339 ne spécifie pas de nombre maximal de chiffres de fraction de seconde, mais spécifie qu’au moins un chiffre doit suivre le point, si une section de fraction de seconde est présente. L’implémentation dans System.Text.Json autorise jusqu’à 16 chiffres (pour prendre en charge l’interopérabilité avec d’autres frameworks et langages de programmation), mais elle analyse uniquement les sept premiers. Une exception JsonException est levée s’il y a plus de 16 chiffres de fraction de seconde trouvés lors de la lecture des instances DateTime et DateTimeOffset.
    • RFC 3339 autorise l’emploi des caractères "t" ou "z" au lieu de "T" ou "Z" respectivement, mais autorise les applications à limiter la prise en charge aux variantes en majuscules uniquement. L’implémentation dans System.Text.Json exige l’emploi des majuscules "T" et "Z". Une exception JsonException est levée si les charges utiles en entrée contiennent "t" ou "z" lors de la lecture des instances DateTime et DateTimeOffset.
    • RFC 3339 spécifie que les sections date et heure doivent être séparées par "T", mais autorise les applications à les séparer par un espace (" "). System.Text.Json exige que les sections date et heure soient séparées par "T". Une exception JsonException est levée si les charges utiles en entrée contiennent un espace (" ") lors de la lecture des instances DateTime et DateTimeOffset.

En présence de fractions décimales pour les secondes, il doit y avoir au moins un chiffre. 2019-07-26T00:00:00. n’est pas autorisé. Bien que jusqu’à 16 chiffres fractionnaires soient autorisés, seuls les sept premiers sont analysés. Tout chiffre supplémentaire sera considéré comme un zéro. Par exemple, 2019-07-26T00:00:00.1234567890 sera analysé comme s’il s’agissait de la valeur 2019-07-26T00:00:00.1234567. Cette approche préserve la compatibilité avec l’implémentation dans DateTime, qui est limitée à cette résolution.

Les secondes intercalaires ne sont pas prises en charge.

Prise en charge pour la mise en forme

Les niveaux de granularité suivants sont définis pour la mise en forme :

  1. "'Full date''T''Partial time'"

    1. "yyyy'-'MM'-'dd'T'HH':'mm':'ss" (Spécificateur de format Sortable ("s"))

      Utilisé pour mettre en forme une valeur DateTime sans fractions de seconde et sans informations de décalage.

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

      Utilisé pour mettre en forme une valeur DateTime avec des fractions de seconde, mais sans informations de décalage.

  2. 'Date time'

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

      Utilisé pour mettre en forme une valeur DateTime sans fractions de seconde, mais avec un décalage UTC.

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

      Utilisé pour mettre en forme une valeur DateTime avec des fractions de seconde et avec un décalage UTC.

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

      Utilisé pour mettre en forme une valeur DateTime ou DateTimeOffset sans fractions de seconde, mais avec un décalage local.

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

      Utilisé pour mettre en forme une valeur DateTime ou DateTimeOffset avec des fractions de seconde et avec un décalage local.

    Ce niveau de granularité est conforme à la norme RFC 3339.

Si la représentation au format aller-retour d’une instance DateTime ou DateTimeOffset a des zéros de fin dans ses fractions de seconde, JsonSerializer et Utf8JsonWriter mettent en forme une représentation de l’instance sans zéros de fin. Par exemple, une instance DateTime dont la représentation au format aller-retour est 2019-04-24T14:50:17.1010000Z, sera mise en forme par JsonSerializer et Utf8JsonWriter de cette façon : 2019-04-24T14:50:17.101Z.

Si la représentation au format aller-retour d’une instance DateTime ou DateTimeOffset a tous les zéros de fin dans ses fractions de seconde, JsonSerializer et Utf8JsonWriter mettent en forme une représentation de l’instance sans fractions de seconde. Par exemple, une instance DateTime dont la représentation au format aller-retour est 2019-04-24T14:50:17.0000000+02:00, sera mise en forme par JsonSerializer et Utf8JsonWriter de cette façon : 2019-04-24T14:50:17+02:00.

La troncation des zéros en chiffres de fraction de seconde permet d’écrire la plus petite sortie nécessaire pour conserver les informations d’aller-retour.

Un maximum de sept chiffres de fraction de seconde sont écrits. Ce maximum est aligné sur l’implémentation dans DateTime, qui est limitée à cette résolution.