使用System.Text.Json在ASP.NET Core 3.0中格式化DateTime

71

我正在将一个Web API从.NET Core 2.2迁移到3.0,并想使用新的System.Text.Json。在使用Newtonsoft时,我可以使用下面的代码格式化DateTime。如何实现相同的效果?

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

1
实际问题是什么?如何将DateTime视为UTC,即使它不是?JSON.NET和System.Text.Json默认使用ISO8601。如果DateTimeKind为UTC,则在字符串末尾添加“Z”。本地时间将包括本地时区偏移量。 - Panagiotis Kanavos
我想问如何在使用新的 System.Text.Json 时,在 Startup.cs 中全局设置日期格式。 - D. English
2
但是你的代码并不是这样做的,因为 JSON.NET 已经使用了 ISO8601 格式-与您使用的相同格式。你所做的是强制它对所有 [DateTime kinds](https://learn.microsoft.com/en-us/dotnet/api/system.datetime.kind?view=netframework-4.8)使用 UTC。而我已经解释过,System.Text.Json 已经处理了 DateTime.Kind 为 UTC 的日期。这意味着您要存储的日期是本地时间或未指定时间。 - Panagiotis Kanavos
3
为什么你想转换为UTC时间呢?为什么不让System.Text.Json输出偏移量呢?无论如何,日期格式化在[系统中System.Text.Json的DateTime和DateTimeOffset支持]中有解释。除了创建自定义格式化程序外,没有强制格式的方法。您可以确保使用的所有日期都是UTC时间,或者使用DateTimeOffset确保指定了偏移量。 - Panagiotis Kanavos
5
我想将DateTime序列化为没有小数秒的UTC时间。当使用Swift(iOS应用程序)访问我的API时,小数秒和偏移量会导致JSON解析失败。 - D. English
1
相关问题请参考此链接:https://github.com/dotnet/runtime/issues/1566 - mkb
5个回答

98

使用自定义格式化程序解决。感谢Panagiotis的建议。

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

这段代码有两个问题。1)NewtonSoft.Json并不总是在值上调用.ToUniversalTime(),它取决于其DateTimeKind。提供的格式字符串剥离了NewtonSoft.Json维护的7位小数精度。如果您同意,我可以更新您的答案以包含正确的代码。否则,我可以创建一个新的答案来包含正确的代码。 - Carlos Muñoz
4
这件事涉及到的是 System.Text.Json 而不是 NewtonSoft.Json。 - mkb
由于我在客户端代码中已经使用了UTC,因此我排除了ToUniversalTime(),我所需要的只是日期字符串末尾的Z,这就完成了我的需求,我想这是正确的方法吧?!另外,显然reader有一个reader.GetDateTime()方法,可以在Read方法中使用。 - mkb
2
15行代码,启动时进行了一次脆弱的修改,只是为了在我的API输出中拥有完整明确的日期。这会影响可信度。 - bbsimonbb
注意时区!上述代码将假定您的C# DateTime为本地时间,并在序列化为UTC之前将其转换为UTC。如果为了保持清醒,所有日期都已经是UTC,则在此步骤中将会悄悄地出现不需要的时区差异。(要解决这个问题,只需在“Write()”方法中删除“ToUniversalTime()”即可。) - bbsimonbb
Read方法的建议 - 检查reader.GetString()是否为null(它返回可空字符串),如果为null则返回JsonException。例如,当JSON值为“null”时,默认的JsonSerializer对于DateTime类型执行此操作:if (value is null) throw new JsonException(); - Nick Brooks

19

迁移到Core 3时,我必须通过以下方式替换System.Text.Json来再次使用Newtonsoft:

services.AddControllers().AddNewtonsoftJson();

但是我在一个Angular应用程序中遇到了UTC日期的相同问题,我不得不添加以下内容以获取UTC日期:

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

在您的情况下,您应该能够做到这一点:

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

它有效,我希望它能帮到你...


11
问题与Newtonsoft.Json无关。 - Anton Shakalo
1
这是一个很好的解决方案。谢谢,伙计,你救了我的一天。 - Necip Sunmaz
1
我会接受这个答案,好的。谢谢。 - AntonIva

5
这基本上与其他人建议的一样,但多了一步将格式字符串作为属性参数进行处理。
格式化程序:
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));
    }
}

由于JsonConverterAttribute未密封,因此我们可以这样做:

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

1

这个 asp.net core 日期序列化/反序列化 的垃圾堆火可能更容易理解,当你看到 Date.Parse() 和 Date.ParseExact() 的垃圾堆火 时。我们正在将日期传递给 JavaScript,并从 JavaScript 中传递日期,因此我们不想进行格式化。我们只想在 DateTime 和 ISO 8601 UTC 之间透明地进行序列化和反序列化

这不是默认设置,也没有简单的配置选项,而且解决方案非常奇怪和脆弱,这会破坏可信度。目前对我有效的是基于D.English's answer的编写,以及链接答案的阅读,并使用this answer正确访问 JsonDocument...

更新 这是针对模型绑定的垃圾桶火灾。对于查询字符串解析的垃圾桶火灾,在这里

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

DateTime.SpecifyKind 不起作用。如果你往返一个日期,它会受到你本地时区的影响而偏差。 - Jonathan Allen

0

现在在 asp.net 6/7 中,如果您将 DateTime.Kind 设置为 Utc,那么您的 DateTime 对象将被序列化为带有尾随 'Z' 的字符串,以指示 UTC 时区。

如果您的数据来自 efcore,则可以告诉它将所有 DateTime 数据视为 UTC,例如 像这样


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接