TimeOffset support in での DateTime と DateTimeOffset のサポート

System.Text.Json ライブラリにより、ISO 8601-1:2019 拡張プロファイルに従って、DateTimeDateTimeOffset 値が解析されて書き込まれます。 コンバーターには、JsonSerializer を使用したシリアル化と逆シリアル化のためのカスタム サポートが用意されています。 Utf8JsonReaderUtf8JsonWriter を使用して、カスタム サポートを実装することもできます。

ISO 8601-1:2019 形式のサポート

JsonSerializerUtf8JsonReaderUtf8JsonWriter、および JsonElement の各型により、ISO 8601-1:2019 形式の拡張プロファイルに従って、DateTimeDateTimeOffset のテキスト表現が解析されて書き込まれます。 たとえば、「 2019-07-26T16:59:57-05:00 」のように入力します。

DateTimeDateTimeOffset のデータは、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"}

DateTimeDateTimeOffset は、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

既定のオプションでは、DateTimeDateTimeOffset の入力テキスト表現が ISO 8601-1:2019 の拡張プロファイルに準拠している必要があります。 プロファイルに準拠していない表現を逆シリアル化しようとすると、JsonSerializer によって JsonException がスローされます。

using System.Text.Json;

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

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

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

JsonDocument によって、JSON ペイロードのコンテンツへの構造化されたアクセスが提供されます (DateTimeDateTimeOffset の表現を含む)。 次の例は、気温のコレクションから月曜日の平均気温を計算する方法を示しています。

using System.Text.Json;

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

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

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

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

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

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

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

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

準拠していない DateTime 表現のペイロードで平均温度を計算しようとすると、JsonDocument によって FormatException がスローされます。

using System.Text.Json;

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

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

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

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

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

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

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

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

下位レベルの Utf8JsonWriter によって、DateTimeDateTimeOffset のデータが書き込まれます。

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 によって、DateTimeDateTimeOffset のデータが解析されます。

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

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

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

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

Utf8JsonReader を使用して準拠していない形式を読み取ろうとすると、FormatException がスローされます。

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

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

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

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

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

DateOnly および TimeOnly プロパティをシリアル化する

.NET 7 以降で System.Text.Json は、DateOnly および TimeOnly 型のシリアル化と逆シリアル化をサポートしています。 次のオブジェクトを考えてみましょう。

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

次の例は、Appointment オブジェクトをシリアル化し、結果の JSON を表示し、それを Appointment 型の新しいインスタンスに逆シリアル化しています。 最後に、元と新しく逆シリアル化されたインスタンスが等しいかどうかが比較され、結果がコンソールに出力されています。

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

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

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

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

上のコードでは以下の操作が行われます。

  • Appointment オブジェクトがインスタンス化され、appointment 変数に割り当てられます。
  • appointment インスタンスが JsonSerializer.Serialize を使用して JSON にシリアル化されます。
  • 結果の JSON がコンソールに出力されます。
  • この JSON は、JsonSerializer.Deserialize を使用して Appointment 型の新しいインスタンスに逆シリアル化されます。
  • 元と新しく逆シリアル化されたインスタンスが等しいかどうかが比較されます。
  • 比較の結果がコンソールに出力されます。

DateTimeDateTimeOffset のカスタム サポート

JsonSerializer を使用するとき

シリアライザーでカスタム解析または書式設定が実行されるようにするには、カスタム コンバーターを実装できます。 次に例をいくつか示します。

DateTime(Offset).Parse and DateTime(Offset).ToString

入力した DateTime または DateTimeOffset のテキスト表現の形式を特定できない場合は、コンバーターの読み取りロジックに DateTime(Offset).Parse メソッドを使用できます。 このメソッドでは、ISO 8601-1:2019 拡張プロファイルに準拠しない ISO 8601 以外の文字列や ISO 8601 の形式を含め、DateTimeDateTimeOffset の各種テキストの形式を解析するために、.NET の拡張サポートを利用できます。 この方法では、シリアライザーのネイティブ実装を使用する場合よりも、パフォーマンスが低下します。

シリアル化する場合は、コンバーターの書き込みロジックで DateTime(Offset).ToString メソッドを使用できます。 このメソッドでは、標準の日付と時刻の書式、およびカスタムの日付と時刻の書式のいずれかを使用して、DateTimeDateTimeOffset の値を書き込むことができます。 この方法でも、シリアライザーのネイティブ実装を使用する場合よりもパフォーマンスが低下します。

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"

Note

JsonConverter<T> を実装する、かつ TDateTime の場合、typeToConvert パラメーターは常に typeof(DateTime) になります。 そのパラメーターは、ポリモーフィックなケースを処理する場合や、ジェネリックを使用してパフォーマンスの高い方法で typeof(T) を取得するときに便利です。

Utf8Parser および Utf8Formatter

入力した DateTime または DateTimeOffset のテキスト表現が "R"、"l"、"O"、または "G" の標準の日付と時刻の書式指定文字列のいずれかに準拠している、またはそれらの 1 つに従って書き込む必要がある場合は、コンバーターのロジックに高速な UTF-8 ベースの解析方法と書式設定方法を使用できます。 この方法は、DateTime(Offset).ParseDateTime(Offset).ToString を使用するよりもはるかに高速です。

次の例は、"R" 標準書式に従って DateTime の値をシリアル化および逆シリアル化するカスタム コンバーターを示しています。

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"

Note

"R" 標準書式の長さは常に 29 文字です。

"l" (小文字の "L") 形式は、Utf8ParserUtf8Formatter の型でのみサポートされているため、他の標準の日付と時刻の書式指定文字列と一緒には記載されていません。 その形式は小文字の RFC 1123 ("R" 形式の小文字バージョン) です。 たとえば、"thu, 25 jul 2019 06:36:07 gmt" などです。

フォールバックとして DateTime(Offset).Parse を使用する

一般に、DateTime または DateTimeOffset の入力データが ISO 8601-1:2019 拡張プロファイルに準拠していることが期待される場合は、シリアライザーのネイティブの解析ロジックを使用できます。 フォールバック メカニズムを実装することもできます。 次の例は、TryGetDateTime(DateTime) を使用して DateTime のテキスト表現の解析に失敗した後に、コンバーターで Parse(String) を使用してデータが正常に解析されていることを示しています。

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

namespace DateTimeConverterExamples;

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

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

        return value;
    }

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

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

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

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

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

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

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

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

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

Unix エポック日付形式を使用する

次のコンバーターにより、タイム ゾーン オフセットあり、またはなしの Unix エポック形式が処理されます (/Date(1590863400000-0700)//Date(1590863400000)/ などの値)。

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

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

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

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

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

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

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

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

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

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

        return s_epoch.AddMilliseconds(unixTime);
    }

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

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

Utf8JsonWriter を使用するとき

Utf8JsonWriter を使用して DateTime または DateTimeOffset のテキストのカスタム表現を書き込む必要がある場合は、カスタム表現を StringReadOnlySpan<Byte>ReadOnlySpan<Char>、または JsonEncodedText に書式設定してから、対応する Utf8JsonWriter.WriteStringValue メソッドまたは Utf8JsonWriter.WriteString メソッドに渡すことができます。

次の例は、ToString(String, IFormatProvider) を使用して DateTime のカスタム形式を作成してから、WriteStringValue(String) メソッドを使用して記述する方法を示しています。

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

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

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

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

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

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

Utf8JsonReader を使用するとき

Utf8JsonReader を使用して DateTime または DateTimeOffset のテキストのカスタム表現を読み取る必要がある場合は、GetString() メソッドを使用して現在の JSON トークンの値を String として取得してから、カスタム ロジックを使用して値を解析できます。

次の例は、GetString() メソッドを使用して DateTimeOffset のテキストのカスタム表現を取得してから、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

System.Text.Json の ISO 8601-1:2019 拡張プロファイル

日付と時刻のコンポーネント

System.Text.Json で実装されている ISO 8601-1:2019 拡張プロファイルでは、日付と時刻の表現に次のコンポーネントを定義します。 これらのコンポーネントは、DateTimeDateTimeOffset の表現の解析と書式設定の際に、サポートされるさまざまな細分性レベルを定義するために使用されます。

コンポーネント Format 説明
Year "yyyy" 0001-9999
Month "MM" 01-12
日間 "dd" 01-28、01-29、01-30、01-31 (月/年)。
時間 "HH" 00-23
"mm" 00-59
Second "ss" 00-59
Second fraction "FFFFFFF" 最小 1 桁、最大 16 桁。
Time offset "K" "Z" または "('+'/'-')HH':'mm"。
Partial time "HH':'mm':'ss[FFFFFFF]" UTC オフセット情報なしの時刻。
Full date "yyyy'-'MM'-'dd" カレンダーの日付。
Full time "'Partial time'K" 現地時刻と UTC 間のタイム オフセットありの日の UTC 時刻または現地時刻。
日時 "'Full date''T''Full time'" カレンダーの日付と時刻。例: 2019-07-26T16:59:57-05:00。

解析のサポート

解析のために次の細分性レベルが定義されています。

  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" (並べ替え可能な ("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"

    この細分性レベルは、RFC 3339 に準拠しています。これは、日付と時刻の情報を置き換えるために使用される、広く採用されている ISO 8601 のプロファイルです。 ただし、System.Text.Json 実装にはいくつかの制限があります。

    • RFC 3339 では、秒の小数部の最大桁数の指定はありませんが、秒に小数部が存在する場合は、少なくとも 1 つの桁がピリオドの後に続く必要があることを指定しています。 System.Text.Json の実装では、(他のプログラミング言語やフレームワークとの相互運用をサポートするために) 最大で 16 桁まで使用できますが、最初の 7 桁のみを解析します。 DateTimeDateTimeOffset のインスタンスを読み取るときに、秒の小数点以下の桁数が 16 を超えていると、JsonException がスローされます。
    • RFC 3339 では、"T" と "Z" の文字をそれぞれ "t" または "z" にすることができますが、アプリケーションで大文字のバリアントのみにサポートを制限することができます。 System.Text.Json の実装では、"T" と "Z" である必要があります。 DateTimeDateTimeOffset のインスタンスを読み取るときに、入力ペイロードに "t" または "z" が含まれている場合は、JsonException がスローされます。
    • RFC 3339 では、日付と時刻のセクションを "T" で区切ることが指定されていますが、アプリケーションでは、代わりにスペース (" ") で区切ることができます。 System.Text.Json では、日付と時刻のセクションを "T" で区切る必要があります。 DateTimeDateTimeOffset のインスタンスを読み取るときに、入力ペイロードにスペース (" ") が含まれている場合は、JsonException がスローされます。

秒に小数部がある場合は、少なくとも 1 つの桁が必要です。 2019-07-26T00:00:00. は許可されません。 最大で 16 桁の小数部が許可されますが、最初の 7 桁のみが解析されます。 それを超えるものはゼロと見なされます。 たとえば、2019-07-26T00:00:00.12345678902019-07-26T00:00:00.1234567 として解析されます。 この方法では、DateTime 実装との互換性が維持されます。これは、この解決策に限定されます。

うるう秒はサポートされていません。

書式設定のサポート

書式設定のために次の細分性レベルが定義されています。

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

    1. "yyyy'-'MM'-'dd'T'HH':'mm':'ss" (並べ替え可能な ("s") 書式指定子)

      秒の小数部がなく、かつオフセット情報がない DateTime を書式設定するために使用されます。

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

      秒の小数部があるものの、オフセット情報がない DateTime を書式設定するために使用されます。

  2. 'Date time'

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

      秒の小数部がないものの、UTC オフセットがある DateTime を書式設定するために使用されます。

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

      秒の小数部があり、かつ UTC オフセットがある DateTime を書式設定するために使用されます。

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

      秒の小数部がないものの、ローカル オフセットがある DateTime または DateTimeOffset を書式設定するために使用されます。

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

      秒の小数部があり、かつローカル オフセットがある DateTime または DateTimeOffset を書式設定するために使用されます。

    この細分性レベルは、RFC 3339 に準拠しています。

DateTime または DateTimeOffset のインスタンスのラウンドトリップ形式の表現の秒の小数部の末尾にゼロがある場合、JsonSerializerUtf8JsonWriter によってそのインスタンスの表現の書式が末尾のゼロなしに設定されます。 たとえば、ラウンドトリップ形式の表現が 2019-04-24T14:50:17.1010000Z である DateTime インスタンスは、JsonSerializerUtf8JsonWriter によって書式が 2019-04-24T14:50:17.101Z に設定されます。

DateTime または DateTimeOffset インスタンスのラウンドトリップ形式の表現の秒の小数部がすべてゼロである場合、JsonSerializerUtf8JsonWriter によってそのインスタンスの表現の書式が小数部なしに設定されます。 たとえば、ラウンドトリップ形式の表現が 2019-04-24T14:50:17.0000000+02:00 である DateTime インスタンスは、JsonSerializerUtf8JsonWriter によって書式が 2019-04-24T14:50:17+02:00 に設定されます。

秒の小数部の桁の 0 を切り捨てることで、情報を保持するためにラウンド トリップ時に書き込まれる出力を最小にすることができます。

秒の小数部は最大で 7 桁書き込まれます。 この最大値は DateTime の実装に沿っており、この解決策に限定されます。