2012-07-31

JSON, .NET and NSDate Redux

Here’s a version of my JSON date to NSDate conversion function updated to convert correctly to the local timezone.


/**
 * Converts a .NET JSON date in the format "/Date(x)/" to an NSDate object.
 * @param string The NSString to convert.
 * @return the NSDate object.
 */
(NSDate*)jsonStringToNSDate(NSString* string) {

    if (string == nil) return nil;

    // If we encounter .NET's minimum date value we treat it as nil.
    if ([string isEqualToString:@"/Date(-59011459200000)/"]) return nil;

    // Extract the numeric part of the date.  Dates should be in the format
    // "/Date(x)/", where x is a number.  This format is supplied automatically
    // by JSON serialisers in .NET.
    NSRange range = NSMakeRange(6, [string length] - 8);
    NSString* substring = [string substringWithRange:range];

    NSNumberFormatter* formatter = [[NSNumberFormatter alloc] init];
    NSNumber* milliseconds = [formatter numberFromString:substring];

    [formatter release];

    // NSTimeInterval is specified in seconds.  The value we get back from the
    // web service is specified in milliseconds.  Both values are since 1st Jan
    // 1970 (epoch).
    NSTimeInterval seconds = [milliseconds longLongValue] / 1000.0;

    seconds += [[NSTimeZone localTimeZone] secondsFromGMT];

    return [NSDate dateWithTimeIntervalSince1970:seconds];
}

Edit: Fixed to return milliseconds as the fractional part of the “seconds” value.

Comments

Jeff on 2012-09-11 at 09:26 said:

Actually, I’ve been looking at this recently and it turns out that you aren’t completely correct, though close enough for government work.

Apparently, the date format is only supposed to be recognised if the “/” characters are escaped with “\“. ie, “\/Date(nnnnn)\/”

http://weblogs.asp.net/bleroy/archive/2008/01/18/dates-and-json.aspx

The “escaping” of “/” is one of those oddities in the JSON format that allows this peculiarity. You don’t need to escape “/” but if you do, it will process correctly.

The downside is that tp recognise dates correctly, you need to be inside the JSON lexical analyser, you can’t just recognise the output strings since the escaping gets lost. If you want to be 100% by the spec.

Oh, and you are supposed to be able to put +HHMM for a time zone adjustment. ie,

“\/Date(nnnnnn+hhmm)\/”

Ant on 2012-09-11 at 14:10 said:

I haven’t found a JSON serialiser in .NET that correctly prepends and appends the backslashes (at least not with the default configuration options), which is why I’ve left it out. EDIT: Hmm, the system I’m working on at the moment doesn’t spit out any dates. I’ll have to test that statement later… You’re right; it’d probably be a good idea to add that check in. The same goes for the time zone offset, but then if the serialisers aren’t escaping the Date() object, they can’t use the offset either (the two integers in the constructor argument would be added together and treated as a single value).

Being able to correctly recognise a date is rather moot (at least for my use cases) because I typically know what the schema is supposed to be. If I’m expecting a date I’ll try and parse it as a date; if I’m expecting a string I’ll treat it is a string. But yep: I’ve taken shortcuts in the interest of dealing with a limited subset of possible uses.

Jeff on 2012-09-16 at 00:38 said:

Yes, the workaround of only trying to parse as a date if you know the value should be a date is one I’m going to be using as well. I was hoping to make a lower-level decoder just present “the right data type” all the time, but I don’t see a way to keep it efficient, just to support those annoying corner cases where people want to pass strings that accidentally contain encoded dates.