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

Javascriptasp.netEntity Frameworkjson.net

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

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

Solution 2 - Javascript

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

Solution 3 - Javascript

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

Solution 4 - Javascript

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

Solution 5 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionC.J.View Question on Stackoverflow
Solution 1 - JavascriptJames Newton-KingView Answer on Stackoverflow
Solution 2 - JavascriptJustMaierView Answer on Stackoverflow
Solution 3 - JavascriptEkusView Answer on Stackoverflow
Solution 4 - JavascriptIlkkaView Answer on Stackoverflow
Solution 5 - JavascriptIainView Answer on Stackoverflow