System.Text.Json의 DateTime 및 DateTimeOffset 지원

System.Text.Json 라이브러리는 ISO 8601-1:2019 확장 프로필에 따라 DateTimeDateTimeOffset 값을 구문 분석하고 씁니다. 변환기JsonSerializer는 직렬화 및 역직렬화에 대한 사용자 지정 지원을 제공할 이(가) 있습니다. Utf8JsonReaderUtf8JsonWriter을(를) 사용하여 사용자 지정 지원을 구현할 수도 있습니다.

ISO 8601-1:2019 형식 지원

JsonSerializer, Utf8JsonReader, Utf8JsonWriterJsonElement 형식은 ISO 8601-1:2019 형식의 확장된 프로필에 따라 DateTime 구문 분석 및 쓰기 DateTimeOffset 텍스트 표현을 작성합니다. 예들 들어 2019-07-26T16:59:57-05:00입니다.

DateTimeDateTimeOffset 데이터는 JsonSerializer사용하여 serialize 할 수 있습니다.

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

DateTimeDateTimeOffsetJsonSerializer을(를) 사용하여 역직렬화할 수도 있습니다.

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 프로필을 따라야 합니다. 프로필에 맞지 않는 표현을 역직렬화하려고 하면 JsonSerializerJsonException throw됩니다.

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.

JsonDocumentDateTimeDateTimeOffset 표현을 포함하여 JSON 페이로드의 콘텐츠에 대한 구조적 액세스를 제공합니다. 다음 예제에서는 온도 컬렉션에서 월요일의 평균 온도를 계산하는 방법을 보여 줍니다.

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 표현을 사용하여 페이로드가 지정된 평균 온도를 계산하려고 하면 JsonDocumentFormatException이(가) 발생합니다.

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

하위 수준 Utf8JsonWriterDateTimeDateTimeOffset 데이터를 씁니다.

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이(가) throw됩니다.

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.JsonDateOnlyTimeOnly 형식을 직렬화 및 역직렬화할 수 있습니다. 다음 개체를 고려해 보세요.

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(오프셋). 구문 분석 및 DateTime(오프셋). ToString

입력 DateTime 또는 DateTimeOffset 텍스트 표현의 형식을 확인할 수 없는 경우 변환기 읽기 논리에서 DateTime(Offset).Parse 메서드를 사용할 수 있습니다. 이 메서드를 사용하면 확장된 ISO 8601-1:2019 프로필을 준수하지 않는 ISO 8601 문자열 및 ISO 8601 형식을 포함하여 다양한 DateTimeDateTimeOffset 텍스트 형식을 구문 분석하기 위한 .NET의 광범위한 지원입니다. 이 방법은 serializer의 네이티브 구현을 사용하는 것보다 성능이 떨어집니다.

직렬화의 경우 변환기 쓰기 논리에서 DateTime(Offset).ToString 메서드를 사용할 수 있습니다. 이 메서드를 사용하면 표준 날짜 및 시간 형식사용자 지정 날짜 및 시간 형식 중 사용하여 DateTimeDateTimeOffset 값을 작성할 수 있습니다. 또한 이 방법은 serializer의 네이티브 구현을 사용하는 것보다 성능이 떨어집니다.

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"

참고 항목

JsonConverter<T>구현하고 T이(가) DateTime인 경우, typeToConvert 매개 변수는 항상 typeof(DateTime)입니다. 이 매개 변수는 다형 사례를 처리하고 성능이 좋은 방식으로 typeof(T)을(를) 얻기 위해 제네릭을 사용할 때 유용합니다.

Utf8ParserUtf8Formatter

입력 DateTime 또는 DateTimeOffset 텍스트 표현이 "R", "l", "O" 또는 "G" 표준 날짜 및 시간 형식 문자열 중 하나를 준수하거나 이러한 형식 중 하나에 따라 작성하려는 경우 변환기 논리에서 빠른 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"

참고 항목

"R" 표준 형식은 항상 29자 길이입니다.

"l"(소문자 "L") 형식은 Utf8ParserUtf8Formatter 형식에서만 지원되므로 다른 표준 날짜 및 시간 형식 문자열과 함께 문서화되지 않습니다. 형식은 소문자 RFC 1123("R" 형식의 소문자 버전)입니다. 예: "thu, 25 jul 2019 06:36:07 gmt".

DateTime(오프셋)을 사용합니다. 대체로 구문 분석

일반적으로 입력 DateTime 또는 DateTimeOffset 데이터가 확장된 ISO 8601-1:2019 프로필을 준수해야 하는 경우 serializer의 네이티브 구문 분석 논리를 사용할 수 있습니다. 대체 메커니즘을 구현할 수도 있습니다. 다음 예제에서는 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 epoch 날짜 형식 사용

다음 변환기는 표준 시간대 오프셋(/Date(1590863400000-0700)/ 또는 /Date(1590863400000)/ 같은 값)을 사용하거나 사용하지 않고 Unix epoch 형식을 처리합니다.

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 텍스트 표현을 작성하려면 사용자 지정 표현을 String, ReadOnlySpan<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 표현을 구문 분석하고 서식을 지정할 때 지원되는 다양한 수준의 세분성을 정의하는 데 사용됩니다.

구성 요소 서식 설명
Year "yyyy" 0001-9999
Month "MM" 01-12
요일 "dd" 월/연도 기준 01-28, 01-29, 01-30, 01-31
Hour "HH" 00-23
Minute "mm" 00-59
둘째 "ss" 00-59
두 번째 분수 "FFFFFFF" 최소 한 자리, 최대 16자리 숫자
시간 오프셋 "K" "Z" 또는 "('+'/'-')HH':'mm"입니다.
부분 시간 "HH':'mm':'ss[FFFFFFF]" UTC 오프셋 정보가 없는 시간입니다.
전체 날짜 "yyyy'-'MM'-'dd" 일정 날짜.
풀 타임 "'부분 시간'K" 현지 시간과 UTC 사이의 시간 오프셋이 있는 일 또는 현지 시간의 UTC입니다.
날짜 시간 "'전체 날짜''T''전체 시간'" 달력 날짜 및 시간(예: 2019-07-26T16:59:57-05:00).

구문 분석 지원

구문 분석을 위해 다음과 같은 세분성 수준이 정의됩니다.

  1. '전체 날짜'

    1. "yyyy'-'MM'-'dd"
  2. "'전체 날짜''T''시'':''분'"

    1. "yyyy'-'MM'-'dd'T'HH':'mm"
  3. "'전체 날짜''T''부분 시간'"

    1. "yyyy'-'MM'-'dd'T'HH':'mm':'ss"(정렬 가능("s") 형식 지정자)
    2. "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'FFFFFFF"
  4. "'전체 날짜''T''시간 시'':''분''시간 오프셋'"

    1. "yyyy'-'MM'-'dd'T'HH':'mmZ"
    2. "yyyy'-'MM'-'dd'T'HH':'mm('+'/'-')HH':'mm"
  5. '날짜 시간'

    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"

    이 수준의 세분성은 날짜 및 시간 정보를 교환하는 데 사용되는 ISO 8601의 널리 채택된 프로필인 RFC 3339를 준수합니다. 그러나 System.Text.Json 구현에는 몇 가지 제한 사항이 있습니다.

    • RFC 3339는 소수 자릿수 초 숫자의 최대 수를 지정하지 않지만 소수 자릿수 초 섹션이 있는 경우 적어도 한 자리가 마침표 뒤에 오도록 지정합니다. System.Text.Json 구현은 최대 16자리(다른 프로그래밍 언어 및 프레임워크와의 interop 지원)를 허용하지만 처음 7자리만 구문 분석합니다. DateTimeDateTimeOffset 인스턴스를 읽을 때 소수 자릿수가 16자리보다 많은 경우 JsonException이(가) throw됩니다.
    • RFC 3339는 "T" 및 "Z" 문자를 각각 "t" 또는 "z"로 허용하지만 애플리케이션은 대문자 변형으로만 지원을 제한할 수 있습니다. System.Text.Json을(를) 구현하려면 "T" 및 "Z"여야 합니다. DateTimeDateTimeOffset 인스턴스를 읽을 때 입력 페이로드에 "t" 또는 "z"가 포함된 경우 JsonException이(가) throw됩니다.
    • RFC 3339는 날짜 및 시간 섹션을 "T"로 구분하도록 지정하지만 애플리케이션은 대신 공백(" ")으로 구분할 수 있습니다. System.Text.Json 날짜 및 시간 섹션을 "T"로 구분해야 합니다. 입력 페이로드에 DateTimeDateTimeOffset 인스턴스를 읽을 때 공백(" ")이 포함된 경우 JsonException이(가) throw됩니다.

초 동안 소수 자릿수가 있는 경우 적어도 한 자리가 있어야 합니다. 2019-07-26T00:00:00.은(는) 허용되지 않습니다. 최대 16개의 소수 자릿수가 허용되지만 처음 7개만 구문 분석됩니다. 그 이상의 모든 것은 0으로 간주됩니다. 예를 들어 2019-07-26T00:00:00.1234567890이(가) 2019-07-26T00:00:00.1234567인 것처럼 구문 분석됩니다. 이 방법은 이 해결로 제한되는 DateTime 구현과의 호환성을 유지합니다.

윤초는 지원되지 않습니다.

서식 지정 지원

다음 세분성 수준은 서식 지정에 대해 정의됩니다.

  1. "'전체 날짜''T''부분 시간'"

    1. "yyyy'-'MM'-'dd'T'HH':'mm':'ss"(정렬 가능("s") 형식 지정자)

      소수 자릿수 초 및 오프셋 정보 없이 DateTime 서식을 지정하는 데 사용됩니다.

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

      오프셋 정보 없이 소수 자릿수 초로 DateTime 서식을 지정하는 데 사용됩니다.

  2. '날짜 시간'

    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 인스턴스의 표현을 소수 자릿수 초의 후행 0이 있는 경우 JsonSerializerUtf8JsonWriter 후행 0 없이 인스턴스의 표현 형식을 지정합니다. 예를 들어 왕복 형식 표현이 2019-04-24T14:50:17.1010000ZDateTime 인스턴스는 JsonSerializerUtf8JsonWriter에 의해 2019-04-24T14:50:17.101Z(으)로써 형식이 지정됩니다.

왕복 형식DateTime 또는 DateTimeOffset 인스턴스의 표현을 소수 자릿수 초의 모든 0이 있는 경우 JsonSerializerUtf8JsonWriter 소수 자릿수 초 없이 인스턴스의 표현 형식을 지정합니다. 예를 들어 왕복 형식 표현이 2019-04-24T14:50:17.0000000+02:00DateTime 인스턴스는 JsonSerializerUtf8JsonWriter에 의해 2019-04-24T14:50:17+02:00(으)로써 형식이 지정됩니다.

소수 자릿수 초 숫자로 0을 잘리면 왕복에 대한 정보를 작성하는 데 필요한 가장 작은 출력을 사용할 수 있습니다.

최대 7개의 소수 자릿수 초 숫자가 기록됩니다. 이 최대값은 이 해상도로 제한되는 DateTime 구현과 일치합니다.