Json.NET deserializing DateTimeOffset value fails for DateTimeOffset.MinValue without timezone
The problem seems reproducible only when the machine's time zone TimeZoneInfo.Local
has a positive offset from UTC, e.g. (UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
. I was unable to reproduce it in time zones with a non-positive offset such as UTC-05:00
or UTC itself.
Specifically, in JsonReader.ReadDateTimeOffsetString()
a call is made to DateTimeOffset.TryParse
using DateTimeStyles.RoundtripKind
:
if (DateTimeOffset.TryParse(s, Culture, DateTimeStyles.RoundtripKind, out dt))
{
SetToken(JsonToken.Date, dt, false);
return dt;
}
This apparently causes an underflow error in time zones with a positive UTC offset. If in the debugger I parse using DateTimeStyles.AssumeUniversal
instead, the problem is avoided.
You might want to report an issue about this to Newtonsoft. The fact that deserialization of a specific DateTimeOffset
string fails only when the computer's time zone has certain values seems wrong.
The workaround is to use IsoDateTimeConverter
to deserialize your DateTimeOffset
properties with IsoDateTimeConverter.DateTimeStyles
set to DateTimeStyles.AssumeUniversal
. In addition it is necessary to disable the automatic DateTime
recognition built into JsonReader
by setting JsonReader.DateParseHandling = DateParseHandling.None
, which must be done before the reader begins to parse the value for your DateTimeOffset
properties.
First, define the following JsonConverter
:
public class FixedIsoDateTimeOffsetConverter : IsoDateTimeConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTimeOffset) || objectType == typeof(DateTimeOffset?);
}
public FixedIsoDateTimeOffsetConverter() : base()
{
this.DateTimeStyles = DateTimeStyles.AssumeUniversal;
}
}
Now, if you can modify the JsonSerializerSettings
for your controller, use the following settings:
var settings = new JsonSerializerSettings
{
DateParseHandling = DateParseHandling.None,
Converters = { new FixedIsoDateTimeOffsetConverter() },
};
If you cannot easily modify your controller's JsonSerializerSettings
you will need to grab DateParseHandlingConverter
from this answer to How to prevent a single object property from being converted to a DateTime when it is a string and apply it as well as FixedIsoDateTimeOffsetConverter
to your model as follows:
[JsonConverter(typeof(DateParseHandlingConverter), DateParseHandling.None)]
public class RootObject
{
[JsonProperty("revisedDate", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(FixedIsoDateTimeOffsetConverter))]
public DateTimeOffset? RevisedDate { get; set; }
}
DateParseHandlingConverter
must be applied to the model itself rather than the RevisedDate
property because the JsonReader
will already have recognized 0001-01-01T00:00:00
as a DateTime
before the call to FixedIsoDateTimeOffsetConverter.ReadJson()
is made.
Update
In comments, @RenéSchindhelm writes, I created an issue to let Newtonsoft know. It is Deserialization of DateTimeOffset value fails depending on system's timezone #1731.
This is what I am using to fix the issue in .NET Core 3.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.MetadataPropertyHandling = MetadataPropertyHandling.Ignore;
options.SerializerSettings.DateParseHandling = DateParseHandling.None;
options.SerializerSettings.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal });
});
...
Change DateTimeOffset
to DateTime
solved the problem.