다음을 통해 공유


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 형식의 확장 프로필에 따라 DateTimeDateTimeOffset 텍스트 표현을 구문 분석하고 작성합니다. 예를 들어 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 프로필을 따라야 합니다. 프로필을 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()

하위 수준 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
// }

Utf8JsonReaderDateTimeDateTimeOffset 데이터를 구문 분석합니다.

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.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은 Appointment를 사용하여 JsonSerializer.Deserialize 형식의 새 인스턴스로 다시 역직렬화됩니다.
  • 원래 인스턴스와 새로 역직렬화된 인스턴스가 같은지 비교됩니다.
  • 비교 결과는 콘솔에 기록됩니다.

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.보다 훨씬 빠릅니다.

다음 예제에서는 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(Offset).Parse를 대체 수단으로 사용하십시오.

일반적으로 입력 DateTime 또는 DateTimeOffset 데이터가 확장된 ISO 8601-1:2019 프로필을 따를 것으로 예상되는 경우, serializer의 네이티브 구문 분석 로직을 사용할 수 있습니다. 대체 메커니즘을 구현할 수도 있습니다. 다음 예제에서는 DateTime을(를) 사용하여 TryGetDateTime(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 : System.Text.Json.Serialization.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, NumberStyles.Integer, CultureInfo.InvariantCulture, out long unixTime)
                || !int.TryParse(match.Groups[3].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int hours)
                || !int.TryParse(match.Groups[4].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int minutes))
        {
            throw new System.Text.Json.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 = value.ToUnixTimeMilliseconds();

        TimeSpan utcOffset = value.Offset;

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

        writer.WriteStringValue(formatted);
    }
}
sealed class UnixEpochDateTimeConverter : System.Text.Json.Serialization.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, NumberStyles.Integer, CultureInfo.InvariantCulture, out long unixTime))
        {
            throw new System.Text.Json.JsonException();
        }

        return s_epoch.AddMilliseconds(unixTime);
    }

    public override void Write(
        Utf8JsonWriter writer,
        DateTime value,
        JsonSerializerOptions options)
    {
        long unixTime = (value - s_epoch).Ticks / TimeSpan.TicksPerMillisecond;

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

Utf8JsonWriter을(를) 사용하는 경우

DateTime을(를) 사용하여 사용자 지정 DateTimeOffset 또는 Utf8JsonWriter 텍스트 표현을 작성하려면 사용자 지정 표현을 String, ReadOnlySpan<Byte>, ReadOnlySpan<Char> 또는 JsonEncodedText 서식을 지정한 다음 해당 Utf8JsonWriter.WriteStringValue 또는 Utf8JsonWriter.WriteString 메서드에 전달할 수 있습니다.

다음 예제에서는 DateTime을(를) 사용하여 사용자 지정 ToString(String, IFormatProvider) 형식을 만든 다음 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을(를) 사용하는 경우

사용자 지정 DateTime 또는 DateTimeOffset 텍스트 표현을 Utf8JsonReader과 함께 읽으려면, String 메서드를 사용하여 현재 JSON 토큰을 GetString()로 가져온 다음, 사용자 지정 논리를 활용하여 값을 구문 분석할 수 있습니다.

다음 예제에서는 DateTimeOffset 메서드를 사용하여 사용자 지정 GetString() 텍스트 표현을 검색한 다음 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 표현을 구문 분석하고 서식을 지정할 때 지원되는 다양한 수준의 세분성을 정의하는 데 사용됩니다.

구성 요소 서식 설명
연도 "yyyy" 0001-9999
"MM" 01-12
요일 "dd" 월/연도 기준 01-28, 01-29, 01-30, 01-31
한 시간 "HH" 00-23
"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자리만 구문 분석합니다. JsonExceptionDateTime 인스턴스를 읽을 때 소수 자릿수가 16자리보다 많은 경우 DateTimeOffset이(가) throw됩니다.
    • RFC 3339는 "T" 및 "Z" 문자를 각각 "t" 또는 "z"로 허용하지만 애플리케이션은 대문자 변형으로만 지원을 제한할 수 있습니다. System.Text.Json을(를) 구현하려면 "T" 및 "Z"여야 합니다. JsonExceptionDateTime 인스턴스를 읽을 때 입력 페이로드에 "t" 또는 "z"가 포함된 경우 DateTimeOffset이(가) throw됩니다.
    • RFC 3339는 날짜 및 시간 섹션을 "T"로 구분하도록 지정하지만 애플리케이션은 대신 공백(" ")으로 구분할 수 있습니다. System.Text.Json 날짜 및 시간 섹션을 "T"로 구분해야 합니다. 입력 페이로드에 JsonExceptionDateTime 인스턴스를 읽을 때 공백(" ")이 포함된 경우 DateTimeOffset이(가) 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을 생략한 상태로 인스턴스를 형식화합니다. 예를 들어 DateTime의 왕복 형식 표현이 2019-04-24T14:50:17.1010000Z 인스턴스는 2019-04-24T14:50:17.101ZJsonSerializer에 의해 Utf8JsonWriter으로 형식이 지정됩니다.

왕복 형식DateTime 또는 DateTimeOffset 인스턴스의 표현에서 소수 초 부분이 모두 0인 경우, JsonSerializerUtf8JsonWriter는 소수 초 없이 인스턴스를 형식화합니다. 예를 들어 DateTime의 왕복 형식 표현이 2019-04-24T14:50:17.0000000+02:00 인스턴스는 2019-04-24T14:50:17+02:00JsonSerializer에 의해 Utf8JsonWriter으로 형식이 지정됩니다.

초 자릿수의 0을 절단하면 왕복 과정에서 정보 보존을 위해 필요한 최소한의 출력이 생성됩니다.

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