'Formatting DateTime in ASP.NET Core 3.0 using System.Text.Json

I am migrating a web API from .NET Core 2.2 to 3.0 and want to use the new System.Text.Json. When using Newtonsoft I was able to format DateTime using the code below. How can I accomplish the same?

.AddJsonOptions(options =>
    {
        options.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
        options.SerializerSettings.DateFormatString = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ";
    });


Solution 1:[1]

Solved with a custom formatter. Thank you Panagiotis for the suggestion.

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

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"));
    }
}


// in the ConfigureServices()
services.AddControllers()
    .AddJsonOptions(options =>
     {
         options.JsonSerializerOptions.Converters.Add(new DateTimeConverter());
     });

Solution 2:[2]

Migrating to Core 3 I had to replace System.Text.Json to use Newtonsoft again by :

services.AddControllers().AddNewtonsoftJson();

But I was having same issue with UTC dates in an Angular app and I had to add this to get dates in UTC:

services.AddControllers().AddNewtonsoftJson(
       options => options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc);

In your case you should be able to do this:

services.AddControllers().AddNewtonsoftJson(options =>
    {
        options.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
        options.SerializerSettings.DateFormatString = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ";
    });

It works and I hope it helps...

Solution 3:[3]

This dumpster fire of asp.net core date serialization/deserialization is maybe easier to understand when you see the dumpster fire of Date.Parse() and Date.ParseExact(). We're passing dates to and from javascript, so we don't want to be formatting. We just want to transparently serialize and deserialize between DateTime and ISO 8601 in UTC.

That this is not the default, and that there's no easy configuration option, and that the solution is so funky and fragile, is credibility-destroying. This is currently what's working for me, based on D.English's answer for writing, and the linked answer for reading, and using this answer to access the JsonDocument correctly...

Update this is for the dumptser fire of model binding. For the dumpster fire of query string parsing, it's over here

// in Startup.cs ConfigureServices()

services.AddMvc().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.Converters.Add(new UtcDateTimeConverter());
});


public class BobbyUtcDateTimeConverter : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        using (var jsonDoc = JsonDocument.ParseValue(ref reader))
        {
            var stringValue = jsonDoc.RootElement.GetRawText().Trim('"').Trim('\'');
            var value = DateTime.Parse(stringValue, null, System.Globalization.DateTimeStyles.RoundtripKind);
            return value;
        }
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", System.Globalization.CultureInfo.InvariantCulture));
    }
}

Solution 4:[4]

This is more or less the same as others have suggested, but with an additional step to take the format string as a parameter in the attribute.

The formatter:

public class DateTimeFormatConverter : JsonConverter<DateTime>
{
    private readonly string format;

    public DateTimeFormatConverter(string format)
    {
        this.format = format;
    }

    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return DateTime.ParseExact(
            reader.GetString(),
            this.format,
            CultureInfo.InvariantCulture);
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        ArgumentNullException.ThrowIfNull(writer, nameof(writer));

        writer.WriteStringValue(value
            .ToUniversalTime()
            .ToString(
                this.format,
                CultureInfo.InvariantCulture));
    }
}

Since JsonConverterAttribute is not sealed, we can do something like this:

public sealed class JsonDateTimeFormatAttribute : JsonConverterAttribute
{
    private readonly string format;

    public JsonDateTimeFormatAttribute(string format)
    {
        this.format = format;
    }

    public string Format => this.format;

    public override JsonConverter? CreateConverter(Type typeToConvert)
    {
        return new DateTimeFormatConverter(this.format);
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 D. English
Solution 2 Juan
Solution 3 Jonathan Allen
Solution 4 Dan Leonard