Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSDateFormatter and Strings with Timezone Format "+HH:mm"

Update

As of iOS 7, NSDateFormatter does indeed create an NSDate when presented with a string in this format:

NSDateFormatter *formatter = [NSDateFormatter new];
[formatter setDateFormat:@"@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ""];

NSLog(@"non–nil date, even honoring the 7–minute–offset in the time–zone on iOS 7: %@",
     [formatter dateFromString:@"2011-07-12T18:07:31+02:07"]);

For iOS 6, the answer is to not use an NSDateFormatter…


Okay, up to this point I have read

  • the Docs and
  • Technotes at Apple,
  • the important part of the Unicode reference and
  • quite a few questions and answers here

regarding how to use NSDateFormatter in order to create an NSDate out of a string.

I have stumbled upon Peter Hosey's ISO8601DateFormatter, as well.
Looking into his implementation, I wonder:

Isn't there a way that is both correct and sane to get a string like this one 2011-07-12T18:07:31+02:00 into an NSDate?

  • It would be no problem if the last colon was missing.
  • It would be no problem if there was a GMT prefixing the "+"-sign, but...
  • that is not the case.

I can hack it to work for my application (using the format @"yyyy'-'MM'-'dd'T'HH':'mm':'ssz':'00") but that is — of course — incorrect because it will discard the minute-information of the timezone.

I could also replace the last colon with an empty string, but I would consider that a hack as well.

So, is there some secret sauce to make NSDateFormatter take that string from above and give me a valid and correct NSDate?


Aside:

I have somewhere found the tip, that one could use +[NSDate dateWithNaturalLanguageString:] to achieve my goal. This — however — only sets the date, but not the time! (Well it does set the time, but only taking the timezone-offset into account and not the HH:mm:ss part...)

like image 360
danyowdee Avatar asked Jul 12 '11 16:07

danyowdee


People also ask

Is NSDateFormatter thread safe?

Thread Safety On earlier versions of the operating system, or when using the legacy formatter behavior or running in 32-bit in macOS, NSDateFormatter is not thread safe, and you therefore must not mutate a date formatter simultaneously from multiple threads.

How do I set the timezone in DateFormatter?

You can make use of the following DateFormat. SimpleDateFormat myDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); myDate. setTimeZone(TimeZone.

What is NSDate?

NSDate objects encapsulate a single point in time, independent of any particular calendrical system or time zone. Date objects are immutable, representing an invariant time interval relative to an absolute reference date (00:00:00 UTC on 1 January 2001).

How do I change the date format in Swift?

let date = Date(); let dateFormatter = DateFormatter(); Date will give us the current date and time with respect to our current time zone. For me, it's Wednesday, June 1st, 2022. dateFormatter is an instance of the DateFormatter class that allows us to operate various formatting functions on our date .


2 Answers

This question is a bit old, but I was having the same problem. I came up with some code which is an answer and might be useful to others...

I use a regex to parse an ISO-8601 string and grab the output into a bunch of strings you can then use to create your own string to pass into NSDateFormatter (i.e. removing colons etc) or if you always are going to want the same output string, just create that from the results of a call to NSRegularExpression.

//    ISO-8601 regex: 
//        YYYY-MM-DDThh:mm[:ss[.nnnnnnn]][{+|-}hh:mm]
// Unfortunately NSDateFormatter does not parse iso-8601 out of the box,
// so we need to use a regex and build up a date string ourselves.
static const char * REGEX_ISO8601_TIMESTAMP = 
            "\\A(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})" // Mandatory - YYYY-MM-DDThh:mm
            "(?:"
            ":(\\d{2})"                                       // Optional - :ss
            "(?:"
            "[.](\\d{1,6})"                                   // Optional - .nnnnnn
            ")?"
            ")?"
            "(?:"
            "([+-])(\\d{2}):(\\d{2})|Z"                       // Optional -[+-]hh:mm or Z
            ")?\\z";

// Extract all the parts of the timestamp
NSError *error = NULL;
NSString *regexString = [[NSString alloc] initWithUTF8String:REGEX_ISO8601_TIMESTAMP];

NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString
                                                                       options:NSRegularExpressionCaseInsensitive
                                                                         error:&error];

NSArray *matches = [regex matchesInString:timestamp
                              options:0
                                range:NSMakeRange(0, [timestamp length])];

// Groups:
//
// elements start at 1 in the array returned from regex, as [0] contains the original string.
//
// MANDATORY - must exist as per ISO standard
//  1  - YYYY
//  2  - MM
//  3  - DD
//  4  - hh
//  5  - mm
// OPTIONAL (each one can be optional)
//  6  - ss
//  7  - nn (microseconds)
//  8  - offset sign (+/-)
//  9  - offset hour
//  10 - offset min
// put the parts into a string which will then be recognised by NSDateFormatter
// (which is acutally RFC822 format)

// mandatory init'd to nil, optional set to defaults.
NSString *YYYY, *MM, *DD, *hh, *mm, *ss, *nn, *sign, *Zhh, *Zmm;
NSRange tempRange;

for (NSTextCheckingResult *match in matches) {
    NSRange matchRange = [match range];
    NSInteger matchCount = [match numberOfRanges] - 1;
    NSUInteger idx = 1;

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        YYYY = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        MM   = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
         tempRange = [match rangeAtIndex:idx++];
         DD   = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        hh   = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        mm   = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        ss   = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        nn = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        sign = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        Zhh  = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        Zmm  = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }
}

Hope this helps someone!

like image 162
Stretch Avatar answered Sep 21 '22 08:09

Stretch


Old question, but I found the right answer on someone's gist :

https://gist.github.com/soffes/840291

It parses and creates ISO-8601 Strings, and it's quicker than NSDateFormatter

Here is the code:

+ (NSDate *)dateFromISO8601String:(NSString *)string {
    if (!string) {
        return nil;
    }

    struct tm tm;
    time_t t;    

    strptime([string cStringUsingEncoding:NSUTF8StringEncoding], "%Y-%m-%dT%H:%M:%S%z", &tm);
    tm.tm_isdst = -1;
    t = mktime(&tm);

    return [NSDate dateWithTimeIntervalSince1970:t + [[NSTimeZone localTimeZone] secondsFromGMT]];
}


- (NSString *)ISO8601String {
    struct tm *timeinfo;
    char buffer[80];

    time_t rawtime = [self timeIntervalSince1970] - [[NSTimeZone localTimeZone] secondsFromGMT];
    timeinfo = localtime(&rawtime);

    strftime(buffer, 80, "%Y-%m-%dT%H:%M:%S%z", timeinfo);

    return [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
}
like image 29
Vincent NOCK Avatar answered Sep 23 '22 08:09

Vincent NOCK