36

Forgive me as I'm new to Objective C.

I am getting back dates from a .NET webservice in the /Date(xxxxxxxxxxxxx-xxxx)/ format. I'm looking for some direction on how to best parse this into an NSDate object. I've tried using dateWithTimeIntervalSince1970 on it but it comes back with a date in the year 1969 for a date I know is in 2006.

Looking for some direction on the proper way to handle JSON dates.

Thanks in advance!

Anupdas
  • 10,211
  • 2
  • 35
  • 60
user213517
  • 511
  • 2
  • 5
  • 6

7 Answers7

47

I just wrote this for iOS 4.0+ (because it uses NSRegularExpression). It handles dates with or without timezone offsets. Seems to work pretty well, what do you think?

+ (NSDate *)mfDateFromDotNetJSONString:(NSString *)string {
    static NSRegularExpression *dateRegEx = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        dateRegEx = [[NSRegularExpression alloc] initWithPattern:@"^\\/date\\((-?\\d++)(?:([+-])(\\d{2})(\\d{2}))?\\)\\/$" options:NSRegularExpressionCaseInsensitive error:nil];
    });
    NSTextCheckingResult *regexResult = [dateRegEx firstMatchInString:string options:0 range:NSMakeRange(0, [string length])];

    if (regexResult) {
        // milliseconds
        NSTimeInterval seconds = [[string substringWithRange:[regexResult rangeAtIndex:1]] doubleValue] / 1000.0;
        // timezone offset
        if ([regexResult rangeAtIndex:2].location != NSNotFound) {
            NSString *sign = [string substringWithRange:[regexResult rangeAtIndex:2]];
            // hours
            seconds += [[NSString stringWithFormat:@"%@%@", sign, [string substringWithRange:[regexResult rangeAtIndex:3]]] doubleValue] * 60.0 * 60.0;
            // minutes
            seconds += [[NSString stringWithFormat:@"%@%@", sign, [string substringWithRange:[regexResult rangeAtIndex:4]]] doubleValue] * 60.0;
        }

        return [NSDate dateWithTimeIntervalSince1970:seconds];
    }
    return nil;
}
jasongregori
  • 11,451
  • 5
  • 43
  • 41
  • 1
    +1 This one supports dates before and after 1/1/1970 unlike other methods here with "unsigned" milliseconds. My solution required TZ info so I just commented out that part and off to the races. – J3RM Jan 18 '12 at 17:46
  • +1, this is working fine. What is the use of @"^\\/date\\((-?\\d++)(?:([+-])(\\d{2})(\\d{2}))?\\)\\/$" . I am not able to understand whats going on it. – Ganesh G May 10 '13 at 05:11
  • @g-ganesh that's regex. It's parsing this format: "/Date(xxxxxxxxxxxxx-xxxx)/". The first part being unix time, the second being timezone. First off, there are all those double "\" because "\" needs to be escaped in an NSString object and then the single "\" escape things in the regex. So we have "^\/date\(" which matches "/date(" at the beginning of the string only. Then we have "(-?\d++)" which captures the optionally negative seconds with at least two numbers in it (I don't remember why it has to have at least two). – jasongregori May 10 '13 at 15:33
  • Then "(?:([+-])(\d{2})(\d{2}))?". This is a big one, the outer part "(?:...)" creates a group but does not capture it, which is needed so the "?" at the end can make it optional. The rest capture the timezone in three parts. The sign "([+-])" which can be plus or minus. The timezone hours "(\d{2})" which have 2 digits and the timezone seconds "(\d{2})" which have 2 digits as well. Then last we need to match the last "/" at the end of the string. Does that help? – jasongregori May 10 '13 at 15:33
  • @jasongregori When I pass in a value, for example, "/Date(1369851691000)/" to the above method I get a date value returned but it is 1 hour ahead of the actual time that I passed in. Why is this? – motionpotion May 29 '13 at 19:53
  • @motionpotion You should have gotten "Wed, 29 May 2013 18:21:31 GMT". What did you get? Are you taking timezone into account? – jasongregori May 29 '13 at 21:00
  • @jasongregori thank you for responding. I get "Wed, 29 May 2013 19:21:31". So I get 1 hour ahead because it seems to be automatically taking Daylight Savings Time into account. I was expecting to get back "Wed, 29 May 2013 18:21:31" as you have mentioned in your comment above but I get 1 hour in ahead of the date I pass into the method. How do I take GMT into account so that it will work when DST changes? – motionpotion May 29 '13 at 21:05
  • @motionpotion what time zone are you in? And what method are you using to print out the date you are getting? – jasongregori May 30 '13 at 21:21
  • I'm in IST which is Irish Summer Time (not India Standard Time). I'm using the following but it will not format correctly when I post it in this comment box: – motionpotion Jun 02 '13 at 17:38
  • @motionpotion I can't actually see your code. But it sounds like your .net time is in GMT and your printed NSDate is in your local time zone which is correct. If you would like to see the time in GMT use NSDateFormatter and setTimeZone: – jasongregori Jun 03 '13 at 09:23
  • Nice answer, but it causes an exception with null values, could use a check at the start: `if ((NSNull *)string == [NSNull null]) return nil;` – nevan king Jan 23 '14 at 15:55
  • Does not work with timezones for me. For example "/Date(1414796400000+0100)/" returns an NSDate 2014-11-01 01:00:00 CET(+01.00), which is wrong according to epochconverter.com. Had to uncomment the section about timezones. – Zeezer Mar 04 '15 at 11:32
18

I was in the same boat whilst using json-framework which doesn't support the date format as it's not official JSON. My source is from an API built using JSON.Net. This is what I came up with:

- (NSDate*) getDateFromJSON:(NSString *)dateString
{
    // Expect date in this format "/Date(1268123281843)/"
    int startPos = [dateString rangeOfString:@"("].location+1;
    int endPos = [dateString rangeOfString:@")"].location;
    NSRange range = NSMakeRange(startPos,endPos-startPos);
    unsigned long long milliseconds = [[dateString substringWithRange:range] longLongValue];
    NSLog(@"%llu",milliseconds);
    NSTimeInterval interval = milliseconds/1000;
    return [NSDate dateWithTimeIntervalSince1970:interval];
}

I don't have the appended portion in the date format that you do so I haven't dealt with that like the answer above. No error catching either, it's all new to me at this point.

toxaq
  • 6,745
  • 3
  • 46
  • 56
  • There is one FLAW in this method, it only supports DateTimes AFTER 1/1/1970. Dates before 1/1/1970 will return really wrong dates. The reason this happens is that the milliseconds is unsigned which means it only supports Positive numbers, so anything negative will result in undesirable results. – J3RM Jan 18 '12 at 17:43
  • 2
    Thanks for the downvote. If you read the question, it says they're looking for "some direction", not a bullet proof answer to every single possible dateformat. A comment probably would've been sufficient but whatever floats your boat. Getting downvoted for actually contributing a useful answer, that makes me want to help others. – toxaq Jan 19 '12 at 04:20
11

I actually found the snippet with NSRegularExpression pretty useful, till i came up with another solution that uses NSCharecterSet for stipping off the milliseconds.

+ (NSDate*) dateFromJSONString:(NSString *)dateString
{
    NSCharacterSet *charactersToRemove = [[ NSCharacterSet decimalDigitCharacterSet ] invertedSet ];
    NSString* milliseconds = [dateString stringByTrimmingCharactersInSet:charactersToRemove];   

    if (milliseconds != nil && ![milliseconds isEqualToString:@"62135596800000"]) {
        NSTimeInterval  seconds = [milliseconds doubleValue] / 1000;
        return [NSDate dateWithTimeIntervalSince1970:seconds];
    }
    return nil;
}

Saves a lot of the manual string processing and makes the code much cleaner.

Mehfuz
  • 325
  • 2
  • 8
9

As a .NET programmer learning Objective-C I had the same problem when I tried to consume a .Net WebService.

At first I thought I would be able to use the NSDateFormatter... I found a really good reference for it's symbols here, but I quickly realized that I needed to convert the number from milliseconds to seconds.

I wrote the code to do it... I'm still learning Obj-C but I dont think It should've been this hard...

- (NSDate *) getJSONDate{
    NSString* header = @"/Date(";
    uint headerLength = [header length];

    NSString*  timestampString;

    NSScanner* scanner = [[NSScanner alloc] initWithString:self];
    [scanner setScanLocation:headerLength];
    [scanner scanUpToString:@")" intoString:&timestampString];

    NSCharacterSet* timezoneDelimiter = [NSCharacterSet characterSetWithCharactersInString:@"+-"];
    NSRange rangeOfTimezoneSymbol = [timestampString rangeOfCharacterFromSet:timezoneDelimiter];

    [scanner dealloc];

    if (rangeOfTimezoneSymbol.length!=0) {
        scanner = [[NSScanner alloc] initWithString:timestampString];

        NSRange rangeOfFirstNumber;
        rangeOfFirstNumber.location = 0;
        rangeOfFirstNumber.length = rangeOfTimezoneSymbol.location;

        NSRange rangeOfSecondNumber;
        rangeOfSecondNumber.location = rangeOfTimezoneSymbol.location + 1;
        rangeOfSecondNumber.length = [timestampString length] - rangeOfSecondNumber.location;

        NSString* firstNumberString = [timestampString substringWithRange:rangeOfFirstNumber];
        NSString* secondNumberString = [timestampString substringWithRange:rangeOfSecondNumber];

        unsigned long long firstNumber = [firstNumberString longLongValue];
        uint secondNumber = [secondNumberString intValue];

         NSTimeInterval interval = firstNumber/1000;

        return [NSDate dateWithTimeIntervalSince1970:interval];
    }

    unsigned long long firstNumber = [timestampString longLongValue];
    NSTimeInterval interval = firstNumber/1000;

    return [NSDate dateWithTimeIntervalSince1970:interval];
}

Hopefully someone can provide a better Obj-C solution. If not I may keep this or look for a way to change the serialization format in .NET

EDIT:

About that JSON DateTime format... If you have any control on the service it would probably be best to convert the date to a string in your DataContract objects.

Formatting to RFC1123 seems like a good idea to me right now. As I can probably pick it up easily using a NSDateFormatter.

Quote from Rick Strahl

There's no JavaScript date literal and Microsoft engineered a custom date format that is essentially a marked up string. The format is a string that's encoded and contains the standard new Date(milliseconds since 1970) value.

Stanley.Goldman
  • 4,317
  • 2
  • 23
  • 25
5

Theory: MS encoded the C# DateTime in JSON as milliseconds since 1970. Solution:

NSString*
    dateAsString = @"/Date(1353720343336+0000)/";
    dateAsString = [dateAsString stringByReplacingOccurrencesOfString:@"/Date("
                                                           withString:@""];
    dateAsString = [dateAsString stringByReplacingOccurrencesOfString:@"+0000)/"
                                                           withString:@""];

unsigned long long milliseconds = [dateAsString longLongValue];
NSTimeInterval interval = milliseconds/1000;
NSDate* date = [NSDate dateWithTimeIntervalSince1970:interval];

This is the shortest solution I can think of.

Coding Mash
  • 3,338
  • 5
  • 24
  • 45
0

Use an NSDateFormatter's dateFromString: method after setting the date format.

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • 2
    can you please post an example of how to do this? – odyth Mar 05 '11 at 22:23
  • 1
    -1 I'd have to say this is wrong until dave provides code on how to do this. Json dates are in seconds since a specific date and time. So how would setting the format return a correct NSDate. There is nothing in the documentation links that ref how to set the format to a different paradigm other than MM DD YY styles. – J3RM Jan 18 '12 at 16:19
0

-(NSString*)convertToUTCTime:(NSString*)strDate{

NSDate *currentDate = [NSDate date];
myDate = [commonDateFormatter dateFromString: strDate];
NSTimeInterval distanceBetweenDates = [currentDate timeIntervalSinceDate:myDate];
return [self stringFromTimeInterval:distanceBetweenDates];

} - (NSString *)stringFromTimeInterval:(NSTimeInterval)interval { NSInteger ti = (NSInteger)interval; NSInteger minutes = (ti / 60) % 60; NSInteger hours = (ti / 3600);

if (hours > 24) {
    NSInteger days = hours/24;
    if (days > 30) {
       NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
       [dateFormatter setDateFormat:@"EEE d MMM, h:mm a"];
       //[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"IST"]];
        NSString *daydate = [dateFormatter stringFromDate:myDate];
        return daydate;
    }
    else{
    return [NSString stringWithFormat:@" %2ldd",(long)days];
    }
}else{
    if (hours == 0 && minutes < 1) {
        return [NSString stringWithFormat:@"Today"];
    }
    else if (hours == 0 && minutes < 60){
        return [NSString stringWithFormat:@"%2ldm ",(long)minutes];
    }
    else{
    return [NSString stringWithFormat:@" %2ldh",(long)hours];
    }
}

}

shankar
  • 145
  • 10