7

I am using Json.net 7.0.1 in C# to consume a rest API. The trouble is with the date format the API uses in its JSON response. It looks like this:

/Date(1445301615000-0700)/

That is meant to represent a UTC time of 2015-10-19 17:40:15

If you plug 1445301615000 into an epoch time converter, you see that it is 2015-10-20 00:40:15 . So it is 7 hours ahead of UTC. Then they include the -0700 presumably to offset that back to UTC. So, in an effort to give me a UTC time, they are sending me UTC+7-0700. Why they do it this way I have no idea, but I can't change that.

My question is, how best to make Json.NET parse that date string and come up with a DateTime of 2015-10-19 17:40:15 UTC. I can write a custom JsonConverter to hijack the value and manipulate it manually, but I was wondering if there is a more native method.

I have tried changing the JsonSerializerSettings DateTimeZoneHandling property to all its different values. Setting it to Utc just ignores the time zone offset, yielding 2015-10-20 00:40:15. Setting it to Local, Unspecified, or RoundtripKind all yield 2015-10-19 20:40:15, which I believe is because my local timezone is UTC-4, so it is trying to apply that adjustment to the main date value of 2015-10-20 00:40.

I also considered using the DateFormatString property to represent the expected date string format. But I could not find the right format string characters to represent this epochtime-offset format.

Here's a simplified example:

Person person;
string json = @"{ 'Name': 'John', 
                  'LastSeen':'/Date(1445301615000-0700)/' }";   // 1445301615000 = 2015-10-20 00:40:15

person = JsonConvert.DeserializeObject<Person>(json);
Console.WriteLine(person.LastSeen);     // 10/19/2015 8:40:15 PM    Kind = Local

person = JsonConvert.DeserializeObject<Person>(json, new JsonSerializerSettings { DateFormatHandling = DateFormatHandling.MicrosoftDateFormat });
Console.WriteLine(person.LastSeen);     // 10/19/2015 8:40:15 PM    Kind = Local

person = JsonConvert.DeserializeObject<Person>(json, new JsonSerializerSettings { DateTimeZoneHandling = DateTimeZoneHandling.Utc });
Console.WriteLine(person.LastSeen);     // 10/20/2015 00:40:15 PM    Kind = Utc

// In all three, the -0700 portion is being ignored. I'd like person.LastSeen to be 10/19/2015 17:40:15.

Again, I could just know that the API is going to give me UTC+7 and do the adjustment myself to get real UTC. But I wondered if Json.NET has a native way to handle this type of date string.

2
  • Have you read newtonsoft.com/json/help/html/DatesInJSON.htm ? Commented Oct 19, 2015 at 21:56
  • I have indeed, and much of the surrounding documentation. I found plenty about parsing a normal "Microsoft Format" date string like Date(1445301615000), but nothing about this particular format with the -0700 offset added. Commented Oct 19, 2015 at 21:59

2 Answers 2

7

/Date(1445301615000-0700)/

That is meant to represent a UTC time of 2015-10-19 17:40:15

Sorry, that's incorrect. The UTC time is 2015-10-20 00:45:15. Your value corresponds to the local time, in a time zone with a -07:00 offset at that instant.

In this screwy format, the timestamp portion is still based solely on UTC. The offset is extra information. It doesn't change the timestamp. You can give a different offset, or omit it entirely and it's still the same moment in time.

All of the following are equivalent, with regard to point-in-time.

/Date(1445301615000-0700)/
/Date(1445301615000)/
2015-10-20T00:40:15Z
2015-10-19T17:40:15-07:00

Notice that in in the ISO format, the offset does change the value, but in the MS format it does not.

It would be best if you did not use this format, as ISO8601 is a much saner choice for JSON. However if you're stuck with it, then it's best not to deserialize it to a DateTime. Instead, use a DateTimeOffset.

Consider:

string s = "\"/Date(1445301615000-0700)/\"";
DateTime dt = JsonConvert.DeserializeObject<DateTime>(s);
Console.WriteLine(dt.Kind); // Local

That's no good. basically, if there is any offset, it thinks it's your local time zone, which it might be, but it might not be.

string s = "\"/Date(1445301615000)/\"";
DateTime dt = JsonConvert.DeserializeObject<DateTime>(s);
Console.WriteLine(dt.Kind); // Utc

This is ok, but you've lost track of that local time.

string s = "\"/Date(1445301615000-0700)/\"";
DateTimeOffset dto = JsonConvert.DeserializeObject<DateTimeOffset>(s);
Console.WriteLine(dto); // 10/19/2015 5:40:15 PM -07:00

That's much better. And if you do indeed want a UTC DateTime, then:

string s = "\"/Date(1445301615000-0700)/\"";
DateTimeOffset dto = JsonConvert.DeserializeObject<DateTimeOffset>(s);
DateTime utc = dto.UtcDateTime;
Console.WriteLine(utc); // 10/20/2015 12:40:15 AM

So the key lesson is, regardless of format, if there is time zone offset information present in the data, then deserialize to DateTimeOffset. While using DateTime might work in some cases, you are asking .NET to interpret the offset and apply default behavior, which often will not be the desired behavior.

Sign up to request clarification or add additional context in comments.

2 Comments

Yep, I agree 100% that the makers of the API are misusing the date format. I've raised it as a bug with them, but not expecting much... for now, in their minds /Date(1445301615000-0700)/ = UTC 2015-10-19 17:40:15. Crazy, I know, but hence my problems. Thanks for the excellent explanation, and the suggestion to use DateTimeOffset instead. I think that will be the cleanest way to go. Thanks!
You could compensate on your side, but then when they fix the bug then you'll have to unwind. To compensate, do dto.UtcDateTime + dto.Offset. (what a hack, indeed!) Point them at this post if they need clarification - or give them my email. :)
3

The -hhmm indicates that a local time was serialized, not a UTC time. .NET, like many other platforms, recognizes the concept of time zones. In .NET, for example, the DateTime class has a property indicating what kind of date/time you're dealing with. You can explicitly construct date/times of different kinds. The debugger is terrible for not pointing this out, but you can see it with the following code.

var dt1 = new DateTime(2015, 01, 01, 00, 00, 00); // defaults to DateTimeKind.Unspecified
var dt2 = new DateTime(2015, 01, 01, 00, 00, 00, DateTimeKind.Local);
var dt3 = new DateTime(2015, 01, 01, 00, 00, 00, DateTimeKind.Utc);
var dt4 = new DateTime(2015, 01, 01, 00, 00, 00, DateTimeKind.Unspecified);
Debug.WriteLine(dt1.Kind); // writes "Unspecified"
Debug.WriteLine(dt2.Kind); // writes "Local"
Debug.WriteLine(dt3.Kind); // writes "Utc"
Debug.WriteLine(dt4.Kind); // writes "Unspecified"

You can then see the effect the DateTimeKind has on the Json with the following

// local time -- default datetime handling from JSON.NET
{
    var dateTime = DateTime.Now;
    var jsonObject = new JObject {["dateTime"] = dateTime};
    var jsonString = jsonObject.ToString();
    Debug.WriteLine(jsonString); // uses "2015-10-19T18:13:53.4698565-04:00" form
}
// UTC time -- default datetime handling from JSON.NET
{
    var dateTime = DateTime.Now.ToUniversalTime();
    var jsonObject = new JObject {["dateTime"] = dateTime };
    var jsonString = jsonObject.ToString();
    Debug.WriteLine(jsonString); // uses "2015-10-19T22:13:53.5166571Z" form
}
// local time -- Microsoft-like datetime handling from JSON.NET
{
    var dateTime = DateTime.Now;
    var jsonObject = new JObject {["dateTime"] = dateTime };
    var jsonString = JsonConvert.SerializeObject(jsonObject, new JsonSerializerSettings { DateFormatHandling = DateFormatHandling.MicrosoftDateFormat });
    Debug.WriteLine(jsonString); // uses "/Date(1445292833516-0400)/" format
}
// local time -- Microsoft-like datetime handling from JSON.NET
{
    var dateTime = DateTime.Now.ToUniversalTime();
    var jsonObject = new JObject {["dateTime"] = dateTime };
    var jsonString = JsonConvert.SerializeObject(jsonObject, new JsonSerializerSettings { DateFormatHandling = DateFormatHandling.MicrosoftDateFormat });
    Debug.WriteLine(jsonString); // uses "/Date(1445292833579)/" form
}

3 Comments

Good info, thanks. Of course in my case I'm trying to deserialize rather than serialize. I believe setting DateFormatHanding = MicrosoftDataFormat only affects how json is written, not how it is read. I'll add a more detailed example to my question.
This is mostly good info, though I recommend DateTime.UtcNow instead of DateTime.Now.ToUniversalTime(). However, it doesn't quite address the deserialization concern. Still a +1.
Understood. I was just supplying the background on how that value got put in there. Was assuming OP could figure the rest out himself. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.