Is it possible to specify an offset for JSON.NET d

2019-05-26 04:59发布

问题:

The following statement prints "1/1/0001 4:00:00 PM -05:00"

Console.WriteLine(JsonConvert.DeserializeObject<DateTimeOffset>("\"0001-01-01T16:00:00\""));

This is because when json.net deserializes a DateTime string (which doesn't have an offset) to a DateTimeOffset object, it assigns the local offset, which in this case is -05:00.

What if I don't want to use the local offset? Is there any way I can specify an offset to use for this deserialization?

(The use case is the database server and the web server are in different time zones, and I need the incoming requests that have zone-unspecified time to have the database server's offset after deserialization.)

Update: I can't control the incoming time string's format. I have a data transfer object class which has a DateTimeOffset property and I need to store the incoming time data to this property.

回答1:

The type you're deserializing into should match the data you are expecting. If you're not expecting an offset to be included, then don't deserialize into a DateTimeOffset. Instead, deserialize to a DateTime. It will have DateTimeKind.Unspecified for its .Kind property.

The knowledge you have about the web server's time zone is extraneous to the task of deserialization. So apply it separately, after-the-fact.

// deserialize the json
DateTime dt = JsonConvert.DeserializeObject<DateTime>("\"2014-01-01T00:00:00\"");

// find your target time zone
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");

// apply the time zone to determine the offset, and create the DateTimeOffset
DateTimeOffset dto = new DateTimeOffset(dt, tz.GetUtcOffset(dt));

Update

Per comments, if you need to do this conversion in the manner you've requested, you'll need a custom json converter. This should do the trick:

public class CustomDateTimeConverter : IsoDateTimeConverter
{
    private readonly string defaultTimeZoneId;

    public CustomDateTimeConverter(string defaultTimeZoneId)
    {
        this.defaultTimeZoneId = defaultTimeZoneId;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (objectType != typeof (DateTimeOffset) && objectType != typeof (DateTimeOffset?))
            return base.ReadJson(reader, objectType, existingValue, serializer);

        var dateText = reader.Value.ToString();
        if (objectType == typeof(DateTimeOffset?) && string.IsNullOrEmpty(dateText))
            return null;

        if (dateText.IndexOfAny(new[] { 'Z', 'z', '+'}) == -1 && dateText.Count(c => c == '-') == 2)
        {
            var dt = DateTime.Parse(dateText);
            var tz = TimeZoneInfo.FindSystemTimeZoneById(this.defaultTimeZoneId);
            var offset = tz.GetUtcOffset(dt);
            return new DateTimeOffset(dt, offset);
        }

        return DateTimeOffset.Parse(dateText);
    }
}

Then you can wire it up during conversion:

var settings = new JsonSerializerSettings();
settings.DateParseHandling = DateParseHandling.None;
settings.Converters.Add(new CustomDateTimeConverter(defaultTimeZoneId: "Eastern Standard Time"));
DateTimeOffset dto = JsonConvert.DeserializeObject<DateTimeOffset>("\"2014-01-01T00:00:00\"", settings);

Be sure to used a valid time zone id. Do not use a fixed offset.

Also, this will not be the correct approach if you are trying to pass a time without a date. That is a completely different problem, and passing in 0001-01-01 for the date is not a great approach. I'll be happy to discuss with you in chat.



回答2:

If you have the offset in the original string (-8:00):

Console.WriteLine(JsonConvert.DeserializeObject<DateTimeOffset>("\"0001-01-01T16:00:00-08:00\""));

If not, try this

var dateTime = JsonConvert.DeserializeObject<DateTime>("\"0001-01-01T16:00:00\"");
DateTimeOffset dto = new DateTimeOffset(dateTime, TimeSpan.FromHours(-8));