Every time when some new technology wave appears, there is at least one same thing which makes me crazy. My pain is called “DateTime”.
Although we know how to deal with DateTime, it seems still to be unsolvable issue. In year 2007 we learned that using of DateTime on the wire is not good idea.
At this time I described how to deal with DateTime in context of WCF and SOAP services. Then we did it correct way by introducing new type DateTimeOffset. At this point of time WCF (SOAP) has been the default networking stack.
Now this are a bit different. REST is cool now, but as expected we will need a long time to integrate all those WCS lessons learned in the new stack.
This lesion is called “DateTIme Pain”.
The root of the confusion is the JSON format of DateTime (example: \/Date(-62135596800000)\/) , which is in fact the good one. The problem seems to be missing of conversion components which converts it to .NET ISO Date Time format. You can call it human readable format.
One of solutions, which helps a bit is IsoDateTimeConverter which is a converter compatible with Newtonsoft JSON. Unfortunately this one does not work well with Windows Store Apps. And so on, and so on.
I learned that that the DateTime is not suitable for networking and this statement will probably keep forever, even when some new guys introduce some new framework. You can do whatever you want, but DateTime type does not contain any information about zone. After the instance of DateTime is serialized and is flying on the wire, the zone information is lost and it mast be assumed.
For this reason 6 years ago Microsoft introduced type DateTimeOffset. When serialized the instance of this type looks like:
{"DateTime":"\/Date(-62135596800000)\/","OffsetMinutes":120}
As you see it contains explicitly the information about zone. The ‘120’ in this case means +2 hours zone offset.
The class HttpConfiguration in the ASP.NET MVC provides a property config.Formatters.JsonFormatter.SerializerSettings.Converters, which can be customized.In other words, you can install chain of formatters, which manipulates any type.
So, let’s implement the type converter which deals with DateTimeOffset. I will call it UtcDateTimeConverter.
First of all, we have to register the converter:
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new UtcDateTimeConverter() { DateTimeFormat = "yyyy-MM-ddTHH:mm:ss" });
We will also provide a constructor which defines the format of the datetime which will be retrieved to the client.
/// <summary> /// holds ISO format to be used. /// </summary> public string DateTimeFormat { get; set; } /// <summary> /// Sets the custom ISO format /// </summary> public UtcDateTimeConverter() { this.DateTimeFormat = "yyyy-MM-ddTHH:mm:ss"; } |
There are two overrideable methods of interest:
- WriteJson (writes JSON to client – serializes DateTime offset)
- ReadJson (reads JSON sent from client – Deserializes the DateTime offset and converts it to UTC time.
Following code snippet shows how to serialize the DateTimeOffset. but, please, please note that we are not doing any assumption here. We will reject to serialize the DateTime, because
it does not contains information about offset. In fact this type should be rejected from networking, as shown below:
public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) { if (value is DateTime) { throw new NotSupportedException("The DateTime value is not UTC time. UtcDateTimeConverter does allow you to use DateTime only if the value is of kind UTC. You can also use DateTimeOffset instead."); } else if (value is DateTimeOffset) { DateTimeOffset offs = (DateTimeOffset)value; writer.WriteRawValue(
//Format here depends on requirement. This is one example only! String.Format("{{\"DateTime\":\"{0}\",\"OffsetMinutes\":{1}}}", offs.ToString(this.DateTimeFormat), offs.Offset.Minutes)); } } |
When the client sends the DateTime instance ReadJson is called by MVC framework. In the code snippet below, we will again reject DateTime.
But we will read DateTimeOffset and convert it to UTC.
public override object ReadJson( Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) { if (objectType == typeof(DateTime)) { throw new NotSupportedException("UtcDateTimeConverter does not allow you to use DateTime. You must use DateTimeOffset instead."); } else if (objectType == typeof(DateTimeOffset)) { reader.Read();// move to datetime element reader.Read();//move to datetime value DateTime dt = (DateTime)reader.Value; reader.Read();// move to offset element reader.Read();//move to offset value Int64 offsetinMin = (Int64)reader.Value; DateTime utcTime = new DateTime(dt.AddMinutes(-offsetinMin).Ticks, DateTimeKind.Utc); reader.Read();//ensures that reader is left in valid state return new DateTimeOffset(utcTime, TimeSpan.FromMinutes(0)); } else return null; } |
Please note that converter described in this example deals with Time Offset and formats the DateTime to required format. If you just want to avoid JSON DateTime formatting and use ISO format,
you do not need offset and UTC code contained in example.
Last but not least, this example works in WebApi and does not work in ASP.NET MVC!
Posted
Oct 03 2013, 10:37 PM
by
Damir Dobric