2012-11-06

Yet Another iOS JSON Date Parser

Here’s yet another fix for my JSON date parser for Objective-C. This version includes support for dates with a timezone offset, which Jeff requested a while ago and which I’ve finally run into (Grumpydev updated Nancy to produce this date format back in April).

#import <Foundation/Foundation.h>

@interface SZJSONDate : NSObject

+ (NSObject *)dateToJson:(NSDate *)date;
+ (NSDate *)jsonStringToNSDate:(NSString *)string;

@end


#import "SZJSONDate.h"

@implementation SZJSONDate

+ (NSObject *)dateToJson:(NSDate *)date {
    if (date == nil) return [NSNull null];

    NSString *value = [NSNumberFormatter localizedStringFromNumber:[NSNumber numberWithDouble:date.    timeIntervalSince1970 * 1000] numberStyle:NSNumberFormatterNoStyle];

    return [NSString stringWithFormat:@"/Date(%@)/", value];
}

+ (NSDate *)jsonStringToNSDate:(NSString *)string {

    if (string == nil) 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.  Alternatively, dates can be supplied with
    // the time zone suffixed after x as a 5 character timezone offset,
    // consisting of a positive/negative indicator ('+' or '-'), two digit hour
    // offset, and two digit minute offset (ie. '-0700' for MST).
    NSRange range = NSMakeRange(6, [string length] - 8);
    NSString* substring = [string substringWithRange:range];

    NSTimeInterval seconds;

    NSNumberFormatter* formatter = [[NSNumberFormatter alloc] init];

    // We need to parse out the date portion and the timezone portion
    // separately.  It's possible that we won't have a timezone and the date is
    // negative, so we have to be more specific when searching for the 
    // separator.
    unichar timezoneSeparator = [substring characterAtIndex:substring.length - 5];

    NSString *date = nil;

    if (timezoneSeparator == '+' || timezoneSeparator == '-') {

        date = [substring substringToIndex:substring.length - 5];

        NSString *timeZone = [substring substringFromIndex:substring.length - 4];
        NSString *timeZoneHours = [timeZone substringToIndex:2];
        NSString *timeZoneMinutes = [timeZone substringFromIndex:2];

        NSNumber* milliseconds = [formatter numberFromString:date];

        int hours = [[formatter numberFromString:timeZoneHours] intValue];
        int minutes = [[formatter numberFromString:timeZoneMinutes] intValue];
        int offset = (minutes + (hours * 60)) * 60;

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

        if (timezoneSeparator == '+') {
            seconds += offset;
        } else {
            seconds -= offset;
        }
    } else {

        date = substring;

        NSNumber* milliseconds = [formatter numberFromString:date];
        seconds = [milliseconds longLongValue] / 1000.0;
    }

    [formatter release];

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

    return [NSDate dateWithTimeIntervalSince1970:seconds];
}

@end

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.

2012-01-24

JSON, .NET and NSDate

If you’re trying to access a JSON web service created using .NET from an iOS device, you’ve probably discovered that dates are produced in a less than useful format:

/Date(1233423345345)/

The rationale for the date format can be found here, but it’s basically a call to the JavaScript Date object constructor. The numeric value represents the number of milliseconds since 1970-01-01.

Below I’ve added a handy Objective-C function that will extract the value and return an NSDate object. Note that we’re losing accuracy by magnitudes as we translate from .NET DateTime objects (which measure time in ticks) to JavaScript Date objects (which measure time in milliseconds) and finally to iOS NSDate objects (which measure time in seconds).


/**
 * 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) {

    // 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];

    // Have to use a number formatter to extract the value from the string into
    // a long long as the longLongValue method of the string doesn't seem to
    // return anything useful - it is always grossly incorrect.
    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;

    return [NSDate dateWithTimeIntervalSince1970:seconds];
}