NSDateFormatter和带有时区格式"+HH:mm"的字符串

6

更新

iOS 7及以上版本,NSDateFormatter在接收到符合以下格式的字符串时会生成一个NSDate对象:

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"]);

对于iOS 6,答案是不使用NSDateFormatter...

好的,到目前为止我已经阅读了:

我也遇到了 Peter Hosey 的 ISO8601DateFormatter
研究他的实现,我想知道:

有没有一种既正确又合理的方法将这样一个字符串2011-07-12T18:07:31+02:00转换为NSDate?如果最后一个冒号缺失,或者在“+”符号之前加上GMT前缀,那就没问题了,但现实并非如此。我可以通过使用格式@"yyyy'-'MM'-'dd'T'HH':'mm':'ssz':'00"来使其工作(应用于我的应用程序),但这当然是不正确的,因为它会丢弃时区的分钟信息。我也可以用空字符串替换最后一个冒号,但我认为这也是一种hack方法。那么,有没有什么秘诀可以让NSDateFormatter接受上述字符串并给出一个有效和正确的NSDate呢?

顺便说一下:

我在某个地方找到了一个提示,可以使用+[NSDate dateWithNaturalLanguageString:]来实现我的目标。然而,这只设置了日期,但没有设置时间!(好吧,它确实设置了时间,但只考虑了时区偏移量,而没有考虑HH:mm:ss部分...)


你找到了ISO8601偏移的实际解决方案(而不是hack)吗? - matm
很遗憾,不是的 :-( - danyowdee
谢谢您的回复 :) 我发现Peter Hosey的ISO8601DateFormatter值得考虑,但据报道它非常慢(请参见https://dev59.com/FXI95IYBdhLWcg3wrACw) - matm
2个回答

2

这个问题有点老了,但我也遇到了同样的问题。我写了一些代码来解决这个问题,希望对其他人有用...

我使用正则表达式来解析ISO-8601字符串,并将输出存储在一堆字符串中,然后可以使用这些字符串创建自己的字符串以传递给NSDateFormatter(即删除冒号等),或者如果您总是想要相同的输出字符串,只需从调用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;
    }
}

希望这能帮助到某些人!

我完全没有想到还会有人回答这个问题。看到正则表达式让我想起为什么以前我没有走这条路;-) 但是当然,你的答案是正确的,所以+1。顺便说一句:你可以通过使用NSString * const VARIABLE_NAME = …;直接定义NSString常量。 - danyowdee

1

虽然这是一个老问题,但我在某人的代码片段中找到了正确的答案:

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

它解析和创建ISO-8601字符串,比NSDateFormatter更快。
这是代码:
+ (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];
}

现在这是一个仅包含链接的回答。这是有问题的,因为链接可能会失效。请复制(并注明来源)重要细节到您的帖子中。 - JasonMArcher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接