Can you tell JSON.Net to serialize DateTime as Utc even if unspecified?

Dates in my database are stored as Utc. But when I retreieve them w/ the entity framework they come out as type unspecified.

When JSON.Net serializes them they are not in Utc format. Is there a way to tell JSON.Net to serialize DateTimes as Utc even if their type is not specified as Utc?

Answers:

Answer

Set DateTimeZoneHandling on JsonSerializerSettings to Utc. That will convert all dates to UTC before serializing them.

public void SerializeObjectDateTimeZoneHandling()
{
  string json = JsonConvert.SerializeObject(
    new DateTime(2000, 1, 1, 1, 1, 1, DateTimeKind.Unspecified),
    new JsonSerializerSettings
    {
      DateTimeZoneHandling = DateTimeZoneHandling.Utc
    });

  Assert.AreEqual(@"""2000-01-01T01:01:01Z""", json);
}

Documentation: DateTimeZoneHandling setting

Answer

The response above totally works, and so I used that to create an attribute to convert an API response from PST to UTC.

First I needed to create a JsonConverter

public class UTCDateTimeConverter : Newtonsoft.Json.JsonConverter {
    private TimeZoneInfo pacificZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
    public override bool CanConvert(Type objectType) {
        return objectType == typeof(DateTime);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
        if (reader.Value == null) return null;
        var pacificTime = DateTime.Parse(reader.Value.ToString());
        return TimeZoneInfo.ConvertTimeToUtc(pacificTime, pacificZone);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
        writer.WriteValue(TimeZoneInfo.ConvertTimeFromUtc((DateTime) value, pacificZone));
    }
}

Then I had to apply that to the properties that needed to be converted

public class Order{
    [JsonConverter(typeof(UTCDateTimeConverter))]
    public DateTime OrderDate {get;set;}
}
Answer

As @dez mentioned in a comment, you can "mark" the DateTime objects as UTC directly in .net code right after LOADING them from DB and BEFORE serializing them:

var item = GetItemFromDb(...);

// mark appropriate DateTime fields manually as needed
item.OrderDate = DateTime.SpecifyKind(item.OrderDate, DateTimeKind.Utc);

// now it will be serialized to "2018-10-17T16:21:23.507Z" with the Z at the end
// and javascript will parse it properly and convert to local timezone as needed
Answer

To me, it was simpler to create the UTC converter for DateTime properties (based on the implementation of the Newtonsoft.Json.Converters.IsoDateTimeConverter).

public class UtcJsonDateTimeConverter : DateTimeConverterBase
{
    private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFZ";

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        string text;

        if (value is DateTime dateTime)
        {
            text = dateTime.ToString(DefaultDateTimeFormat, CultureInfo.InvariantCulture);
        }
        else
        {
            throw new JsonSerializationException(
                $"Unexpected value when converting date. Expected DateTime or DateTimeOffset, got {value.GetType()}.");
        }

        writer.WriteValue(text);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        bool nullable = objectType == typeof(DateTime?);
        if (reader.TokenType == JsonToken.Null)
        {
            if (!nullable)
            {
                throw new JsonSerializationException($"Cannot convert null value to {objectType}.");
            }

            return null;
        }

        if (reader.TokenType == JsonToken.Date)
        {
            return reader.Value;
        }
        else if (reader.TokenType != JsonToken.String)
        {
            throw new JsonSerializationException($"Unexpected token parsing date. Expected String, got {reader.TokenType}.");
        }

        string date_text = reader.Value.ToString();

        if (string.IsNullOrEmpty(date_text) && nullable)
        {
            return null;
        }

        return DateTime.Parse(date_text, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
    }
}

public class SomeEntity
{

   [JsonProperty(PropertyName = "id", Order = 1)]
   public int ID { get; set; }

   [JsonProperty(PropertyName = "created", Order = 2)]
   [JsonConverter(typeof(UtcJsonDateTimeConverter))]
   public DateTime Created { get; set; }
}
Answer

I used the accepted answer however applied to the default settings:

        JsonConvert.DefaultSettings = (() =>
        {
            var settings = new JsonSerializerSettings();
            settings.Converters.Add(new StringEnumConverter());
            settings.Formatting = Formatting.Indented;
            settings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
            return settings;
        });

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.