Obsługa elementów DateTime i DateTimeOffset w pliku System.Text.Json

Biblioteka System.Text.Json analizuje i zapisuje DateTime i DateTimeOffset wartości zgodnie z rozszerzonym profilem ISO 8601-1:2019. Konwertery zapewniają niestandardową obsługę serializacji i deserializacji za pomocą polecenia JsonSerializer. Można również użyć polecenia Utf8JsonReader i Utf8JsonWriter zaimplementować obsługę niestandardową.

Obsługa formatu ISO 8601-1:2019

Typy JsonSerializer, Utf8JsonReader, Utf8JsonWriteri JsonElement analizują i zapisują DateTime i DateTimeOffset tekst zgodnie z rozszerzonym profilem formatu ISO 8601-1:2019. Na przykład 2019-07-26T16:59:57-05:00.

DateTime i DateTimeOffset dane można serializować za pomocą polecenia 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 można DateTimeOffset również wykonać deserializacji za pomocą polecenia 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

W przypadku opcji domyślnych reprezentacje wejściowe DateTime i DateTimeOffset tekstowe muszą być zgodne z rozszerzonym profilem ISO 8601-1:2019. Próba deserializacji reprezentacji, które nie są zgodne z profilem, spowoduje JsonSerializer zgłoszenie błędu 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.

Obiekt JsonDocument zapewnia ustrukturyzowany dostęp do zawartości ładunku JSON, w tym DateTimeDateTimeOffset i reprezentacji. W poniższym przykładzie pokazano, jak obliczyć średnią temperaturę w poniedziałek z kolekcji temperatur:

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

Próba obliczenia średniej temperatury na podstawie ładunku z niezgodnymi DateTime reprezentacjami spowoduje JsonDocument zgłoszenie błędu 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()

Dane i DateTimeOffset zapisy DateTime na niższym poziomieUtf8JsonWriter:

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 analizowanie DateTime i DateTimeOffset dane:

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

Próba odczytania niezgodnych formatów za Utf8JsonReader pomocą polecenia spowoduje zgłoszenie błędu 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()

Serializowanie właściwości DateOnly i TimeOnly

Program .NET 7+ System.Text.Json obsługuje serializowanie i deserializacji DateOnly oraz TimeOnly typów. Rozważmy następujący obiekt:

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

Poniższy przykład serializuje Appointment obiekt, wyświetla wynikowy kod JSON, a następnie deserializuje go z powrotem do nowego wystąpienia Appointment typu. Na koniec oryginalne i nowo zdeserializowane wystąpienia są porównywane pod kątem równości, a wyniki są zapisywane w konsoli:

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

Powyższy kod:

  • Obiekt Appointment jest tworzone i przypisywane do zmiennej appointment .
  • Wystąpienie appointment jest serializowane do formatu JSON przy użyciu polecenia JsonSerializer.Serialize.
  • Wynikowy kod JSON jest zapisywany w konsoli programu .
  • Kod JSON jest deserializowany z powrotem do nowego wystąpienia Appointment typu przy użyciu polecenia JsonSerializer.Deserialize.
  • Oryginalne i nowo zdeserializowane wystąpienia są porównywane pod kątem równości.
  • Wynik porównania jest zapisywany w konsoli programu .

Obsługa niestandardowa dla i DateTimeDateTimeOffset

W przypadku korzystania z JsonSerializer

Jeśli chcesz, aby serializator wykonał niestandardowe analizowanie lub formatowanie, możesz zaimplementować niestandardowe konwertery. Oto kilka przykładów:

DateTime(Przesunięcie). Analizowanie i data/godzina(przesunięcie). Tostring

Jeśli nie możesz określić formatów reprezentacji wejściowych DateTime lub DateTimeOffset tekstowych, możesz użyć DateTime(Offset).Parse metody w logice odczytu konwertera. Ta metoda umożliwia użycie metody . Rozbudowana obsługa platformy NET do analizowania różnych DateTime formatów i DateTimeOffset tekstu, w tym ciągów innych niż ISO 8601 i formatów ISO 8601, które nie są zgodne z rozszerzonym profilem ISO 8601-1:2019. Takie podejście jest mniej wydajne niż użycie natywnej implementacji serializatora.

Do serializacji można użyć DateTime(Offset).ToString metody w logice zapisu konwertera. Ta metoda umożliwia zapisywanie DateTime i DateTimeOffset wartości przy użyciu dowolnego standardowego formatu daty i godziny oraz niestandardowych formatów daty i godziny. Takie podejście jest również mniej wydajne niż użycie natywnej implementacji serializatora.

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"

Uwaga

Podczas implementowania JsonConverter<T>parametru , i T ma DateTimewartość , typeToConvert parametr zawsze będzie mieć wartość typeof(DateTime). Parametr jest przydatny do obsługi przypadków polimorficznych i w przypadku używania typów ogólnych w celu uzyskania typeof(T) wydajnego sposobu.

Utf8Parser i Utf8Formatter

Możesz użyć szybkich metod analizowania i formatowania opartych na protokole UTF-8 w logice konwertera, jeśli reprezentacje wejściowe DateTime lub DateTimeOffset tekstowe są zgodne z jednym z "R", "l", "O" lub "G" standardowych ciągów formatu daty i godziny albo chcesz napisać zgodnie z jednym z tych formatów. Takie podejście jest znacznie szybsze niż użycie biblioteki sDateTime(Offset).Parse i DateTime(Offset).ToString.

W poniższym przykładzie przedstawiono niestandardowy konwerter, który serializuje i deserializuje DateTime wartości zgodnie ze standardowym formatem "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"

Uwaga

Standardowy format "R" zawsze będzie mieć długość 29 znaków.

Format "l" (małe litery "L") nie jest udokumentowany z innymi standardowymi ciągami formatu daty i godziny, ponieważ jest obsługiwany tylko przez Utf8Parser typy i Utf8Formatter . Format to małe litery RFC 1123 (mała wersja formatu "R"). Na przykład "thu, 25 lip 2019 06:36:07 gmt".

Użyj wartości DateTime(Offset). Analizowanie jako rezerwowe

Jeśli zazwyczaj oczekujesz, że dane wejściowe DateTime lub DateTimeOffset dane będą zgodne z rozszerzonym profilem ISO 8601-1:2019, możesz użyć natywnej logiki analizowania serializatora. Można również zaimplementować mechanizm rezerwowy. W poniższym przykładzie pokazano, że po niepowodaniu analizowania DateTime reprezentacji tekstu przy użyciu metody konwerter pomyślnie analizuje dane przy TryGetDateTime(DateTime)użyciu polecenia 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"

Korzystanie z formatu daty epoki systemu Unix

Następujące konwertery obsługują format epoki Unix z przesunięciem strefy czasowej lub bez tego przesunięcia (wartości, takie jak /Date(1590863400000-0700)/ lub /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);
    }
}

W przypadku korzystania z Utf8JsonWriter

Jeśli chcesz napisać reprezentację niestandardową DateTime lub DateTimeOffset tekstową za pomocą Utf8JsonWriterpolecenia , możesz sformatować niestandardową reprezentację w obiekcie String, ReadOnlySpan<Char>ReadOnlySpan<Byte>lub JsonEncodedText, a następnie przekazać ją do odpowiedniej Utf8JsonWriter.WriteStringValue metody lub Utf8JsonWriter.WriteString .

W poniższym przykładzie pokazano, jak można utworzyć format niestandardowy DateTime za pomocą ToString(String, IFormatProvider) metody , a następnie napisać za pomocą WriteStringValue(String) metody :

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

W przypadku korzystania z Utf8JsonReader

Jeśli chcesz odczytać niestandardową DateTime lub DateTimeOffset tekstową reprezentację za Utf8JsonReaderpomocą polecenia , możesz uzyskać wartość bieżącego tokenu JSON jako metodę String using GetString() , a następnie przeanalizować wartość przy użyciu logiki niestandardowej.

W poniższym przykładzie pokazano, jak można pobrać niestandardową DateTimeOffset reprezentację tekstu przy użyciu metody , a następnie przeanalizować przy użyciu GetString() metody 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

Rozszerzony profil ISO 8601-1:2019 w pliku System.Text.Json

Składniki daty i godziny

Rozszerzony profil ISO 8601-1:2019 zaimplementowany w programie System.Text.Json definiuje następujące składniki dla reprezentacji daty i godziny. Te składniki służą do definiowania różnych obsługiwanych poziomów szczegółowości podczas analizowania i formatowania DateTime i DateTimeOffset reprezentacji.

Składnik Format opis
Year „yyyy” 0001-9999
Month „MM” 01-12
Dzień „dd” 01-28, 01-29, 01-30, 01-31 na podstawie miesiąca/roku.
Godzina „HH” 00-23
Minuta „mm” 00-59
Second „ss” 00-59
Drugi ułamek „FFFFFFF” Co najmniej jedną cyfrę, maksymalnie 16 cyfr.
Przesunięcie czasu „K” "Z" lub "('+'/'')HH':'mm".
Czas częściowy "HH':'mm':'ss[FFFFFFF]" Czas bez informacji o przesunięcie UTC.
Pełna data "yyyy'-'MM'-'dd" Data kalendarza.
Pełny czas "'Częściowy czas'K" CZASU UTC dnia lub czasu lokalnego z przesunięciem czasu lokalnego między czasem lokalnym a UTC.
Data i godzina "'Full date'T'Full time'" Data i godzina kalendarza dnia, na przykład 2019-07-26T16:59:57-05:00.

Obsługa analizowania

Na potrzeby analizowania zdefiniowano następujące poziomy szczegółowości:

  1. "Pełna data"

    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" (Specyfikator formatu 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. "yyy'-'MM'-'dd'T'HH':'mm('+'/'-')HH':'mm"
  5. "Data i godzina"

    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"

    Ten poziom szczegółowości jest zgodny z RFC 3339, powszechnie przyjęty profil ISO 8601 używany do wymiany informacji o dacie i godzinie. Jednak w implementacji System.Text.Json istnieje kilka ograniczeń.

    • RFC 3339 nie określa maksymalnej liczby cyfr ułamkowych sekund, ale określa, że co najmniej jedna cyfra musi podążać za kropką, jeśli istnieje sekcja ułamkowa sekunda. Implementacja w programie System.Text.Json umożliwia maksymalnie 16 cyfr (w celu obsługi międzyoperacyjności z innymi językami programowania i strukturami), ale analizuje tylko pierwsze siedem. Wartość JsonException zostanie wyrzucona, jeśli podczas odczytywania DateTime i DateTimeOffset wystąpień występuje więcej niż 16 cyfr ułamkowych sekund.
    • RFC 3339 umożliwia odpowiednio znaki "T" i "Z" jako "t" lub "z", ale umożliwia aplikacjom ograniczenie obsługi tylko wariantów wyższej litery. Implementacja w programie System.Text.Json wymaga, aby były to "T" i "Z". Element JsonException zostanie zgłoszony, jeśli ładunki wejściowe zawierają wartość "t" lub "z" podczas odczytywania DateTime i DateTimeOffset wystąpień.
    • RFC 3339 określa, że sekcje daty i godziny są oddzielone znakiem "T", ale umożliwia aplikacjom oddzielenie ich spacją ("") zamiast tego. System.Text.Json wymaga oddzielenia sekcji daty i godziny od "T". Element JsonException zostanie zgłoszony, jeśli ładunki wejściowe zawierają spację (" ") podczas odczytywania DateTime i DateTimeOffset wystąpień.

Jeśli liczba ułamków dziesiętnych występuje w sekundach, musi istnieć co najmniej jedna cyfra. 2019-07-26T00:00:00. jest niedozwolona. Chociaż dozwolone jest maksymalnie 16 cyfr ułamkowych, tylko pierwsze siedem jest analizowanych. Wszystko poza tym jest uważane za zero. Na przykład zostanie przeanalizowany tak, 2019-07-26T00:00:00.1234567890 jakby był 2019-07-26T00:00:00.1234567to . Takie podejście utrzymuje zgodność z implementacją DateTime , która jest ograniczona do tej rozwiązania.

Sekundy przestępne nie są obsługiwane.

Obsługa formatowania

Do formatowania zdefiniowano następujące poziomy szczegółowości:

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

    1. "yyyy'-'MM'-'dd'T'HH':'mm':'ss" (Specyfikator formatu Sortable ("s")

      Służy do formatowania DateTime bez ułamkowych sekund i bez informacji przesunięcia.

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

      Służy do formatowania z DateTime ułamkowymi sekundami, ale bez informacji o przesunięciach.

  2. "Data i godzina"

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

      Służy do formatowania bez DateTime sekund ułamkowych, ale z przesunięciem UTC.

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

      Służy do formatowania z DateTime sekundami ułamkowymi i przesunięciem UTC.

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

      Służy do formatowania DateTime sekund ułamkowych lub DateTimeOffset bez, ale z przesunięciem lokalnym.

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

      Służy do formatowania wartości DateTime lub DateTimeOffset z sekundami ułamkowymi i z przesunięciem lokalnym.

    Ten poziom szczegółowości jest zgodny z RFC 3339.

Jeśli reprezentacja DateTimeDateTimeOffset w formacie dwukierunkowym wystąpienia ma końcowe zera w sekundach ułamkowych, a następnie JsonSerializerUtf8JsonWriter sformatuje reprezentację wystąpienia bez zer końcowych. Na przykład wystąpienie, którego reprezentacja DateTime formatu dwukierunkowego to 2019-04-24T14:50:17.1010000Z, zostanie sformatowane jako 2019-04-24T14:50:17.101Z i JsonSerializerUtf8JsonWriter.

Jeśli reprezentacja w formacie dwukierunkowym DateTime wystąpienia ma DateTimeOffset wszystkie zera w sekundach ułamkowych, a Utf8JsonWriter następnie JsonSerializer sformatuje reprezentację wystąpienia bez sekund ułamkowych. Na przykład wystąpienie, którego reprezentacja DateTime formatu dwukierunkowego to 2019-04-24T14:50:17.0000000+02:00, zostanie sformatowane jako 2019-04-24T14:50:17+02:00 i JsonSerializerUtf8JsonWriter.

Obcięcie zer w cyfrach ułamkowych i sekund pozwala na najmniejsze dane wyjściowe potrzebne do zachowania informacji w obie strony do zapisania.

Zapisywana jest maksymalna liczba cyfr z siedmiu ułamkowych sekund. Ta maksymalna wartość jest zgodna z implementacją DateTime , która jest ograniczona do tej rozdzielczości.