使用NSDateFormatter解析RFC 822日期

16

我正在使用NSDateFormatter来解析iPhone上的RFC 822日期。然而,在日期格式中没有办法指定可选元素。在RFC 822规范中有一些可选部分,这破坏了日期解析器。如果没有其他方法,我可能需要编写一个自定义解析器来遵守规范。

例如,规范中的星期几名称是可选的。因此,下面这两个日期都是有效的:

Tue, 01 Dec 2009 08:48:25 +0000将使用格式EEE,dd MMM yyyy HH:mm:ss z进行解析 01 Dec 2009 08:48:25 +0000将使用格式dd MMM yyyy HH:mm:ss z进行解析

这是我目前正在使用的内容:

+ (NSDateFormatter *)rfc822Formatter {
    static NSDateFormatter *formatter = nil;
    if (formatter == nil) {
        formatter = [[NSDateFormatter alloc] init];
        NSLocale *enUS = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
        [formatter setLocale:enUS];
        [enUS release];
        [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss z"];
    }
    return formatter;
}

+ (NSDate *)dateFromRFC822:(NSString *)date {
    NSDateFormatter *formatter = [NSDate rfc822Formatter];
    return [formatter dateFromString:date];
}

并将日期解析为如下格式:

self.entry.published = [NSDate dateFromRFC822:self.currentString];

一种方法是尝试使用两种格式,然后取非空值。但是,规范中有两个可选部分(星期几和秒),因此会有4种可能的组合。虽然还不算太糟糕,但有点 hacky。

4个回答

6

我使用了以下方法来解析RFC822日期。我相信它最初来自于MWFeedParser

+ (NSDate *)dateFromRFC822String:(NSString *)dateString {

    // Create date formatter
    static NSDateFormatter *dateFormatter = nil;
    if (!dateFormatter) {
        NSLocale *en_US_POSIX = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setLocale:en_US_POSIX];
        [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
        [en_US_POSIX release];
    }

    // Process
    NSDate *date = nil;
    NSString *RFC822String = [[NSString stringWithString:dateString] uppercaseString];
    if ([RFC822String rangeOfString:@","].location != NSNotFound) {
        if (!date) { // Sun, 19 May 2002 15:21:36 GMT
            [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss zzz"]; 
            date = [dateFormatter dateFromString:RFC822String];
        }
        if (!date) { // Sun, 19 May 2002 15:21 GMT
            [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm zzz"]; 
            date = [dateFormatter dateFromString:RFC822String];
        }
        if (!date) { // Sun, 19 May 2002 15:21:36
            [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss"]; 
            date = [dateFormatter dateFromString:RFC822String];
        }
        if (!date) { // Sun, 19 May 2002 15:21
            [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm"]; 
            date = [dateFormatter dateFromString:RFC822String];
        }
    } else {
        if (!date) { // 19 May 2002 15:21:36 GMT
            [dateFormatter setDateFormat:@"d MMM yyyy HH:mm:ss zzz"]; 
            date = [dateFormatter dateFromString:RFC822String];
        }
        if (!date) { // 19 May 2002 15:21 GMT
            [dateFormatter setDateFormat:@"d MMM yyyy HH:mm zzz"]; 
            date = [dateFormatter dateFromString:RFC822String];
        }
        if (!date) { // 19 May 2002 15:21:36
            [dateFormatter setDateFormat:@"d MMM yyyy HH:mm:ss"]; 
            date = [dateFormatter dateFromString:RFC822String];
        }
        if (!date) { // 19 May 2002 15:21
            [dateFormatter setDateFormat:@"d MMM yyyy HH:mm"]; 
            date = [dateFormatter dateFromString:RFC822String];
        }
    }
    if (!date) NSLog(@"Could not parse RFC822 date: \"%@\" Possibly invalid format.", dateString);
    return date;

}

更改日期格式字符串是昂贵的,您可能希望为每个日期创建一个日期格式化程序,并将其挂起来,以便在应用程序的生命周期内重复使用。 - SomeGuy

4

在决定使用哪种格式化程序之前,请计算关键字符的数量。例如,您提供的两个字符串中逗号和空格的数量不同。如果没有已知的格式与字符数量匹配,则您甚至知道这不是日期解析的尝试。


这对我来说似乎是最实际的解决方案,因为月份和日期名称具有固定长度,所有其他值都是固定长度的数字。远比尝试各种格式直到找到可行方案要便宜得多! - Kendall Helmstetter Gelner
实现了一个基本的解决方案。并不是很满意,但这是目前最好的解决方案 :) 逗号表示星期几的存在,两个冒号帮助标识秒数。如果日期包含对其遵循的规范的参考,那就太好了,因为在许多语言中,由于格式众多,日期解析真的很麻烦。 - Anurag

1

我相信RFC 822规定了日期时间中的两个可选组件:星期几和小时后的秒数。

作为一种技巧,可以使用短星期几的符号:

NSArray *shortWeekSymbols = [NSArray arrayWithObjects:@"Sun,", @"Mon,", @"Tue,", @"Wed,", @"Thu,", @"Fri,", @"Sat,", nil];
        [formatter setShortWeekdaySymbols:shortWeekSymbols];

如果您将日期格式更改为:EEEdd MMM yyyy HH:mm:ss z。您将能够解析没有星期几的时间。这似乎也允许逗号后面有空格。

为了安全起见,您不应该盲目地设置这样的符号。您应该使用setShortWeekdaySymbols并迭代它们,在末尾添加逗号。原因是它们可能对每个语言环境都不同,第一天可能不是星期日。

有趣的是,格式EEE, dd MMM yyyy HH:mm:ss z将解析没有星期几的时间,但必须有逗号,例如, 01 Dec 2009 08:48:25 +0000。因此,您可以像Steve所说的那样做,然后去掉日期并传递给格式化程序。在格式中没有逗号似乎不允许星期可选。奇怪。

不幸的是,这仍然无法解决格式中可选的:ss问题。但它可能允许您拥有两种格式而不是四种。


谢谢你的建议。我认为RFC 822没有提到本地化,只使用英文格式。尽管这样,附加逗号而不是硬编码值仍然是个好主意。但由于我仍需要检查两种组合,最好先检查字符而不是尝试两次。 - Anurag

0
如果对其他人有帮助的话,这里是基于Simucal's answer的NSDate+RFC822String.swift扩展。
它还缓存了上次成功使用的日期格式,因为设置dateFormatter.dateFormat是很昂贵的。
import Foundation

private let dateFormatter: NSDateFormatter = {
    let dateFormatter = NSDateFormatter()
    dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
    dateFormatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)

    return dateFormatter
}()

private let dateFormatsWithComma = ["EEE, d MMM yyyy HH:mm:ss zzz", "EEE, d MMM yyyy HH:mm zzz", "EEE, d MMM yyyy HH:mm:ss", "EEE, d MMM yyyy HH:mm"]
private let dateFormatsWithoutComma = ["d MMM yyyy HH:mm:ss zzz", "d MMM yyyy HH:mm zzz", "d MMM yyyy HH:mm:ss", "d MMM yyyy HH:mm"]

private var lastUsedDateFormatString: String?

extension NSDate {
    class func dateFromRFC822String(RFC822String: String) -> NSDate? {
        let RFC822String = RFC822String.uppercaseString

        if lastUsedDateFormatString != nil {
            if let date = dateFormatter.dateFromString(RFC822String) {
                return date
            }
        }

        if RFC822String.containsString(",") {
            for dateFormat in dateFormatsWithComma {
                dateFormatter.dateFormat = dateFormat
                if let date = dateFormatter.dateFromString(RFC822String) {
                    lastUsedDateFormatString = dateFormat
                    return date
                }
            }
        } else {
            for dateFormat in dateFormatsWithoutComma {
                dateFormatter.dateFormat = dateFormat
                if let date = dateFormatter.dateFromString(RFC822String) {
                    lastUsedDateFormatString = dateFormat
                    return date
                }
            }
        }

        return nil
    }
}

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