Разбор дат rfc3339 с NSDateFormatter в iOS 4.x и MacOS X 10.6: невозможно?
В общем случае анализ даты rfc3339 с помощью NSDateFormatter представляется невозможным. Я ошибся? [Редактировать 2 года спустя: теперь есть способ! Смотрите ниже и сноску.]
Не особо податливый веб-сервис кормит меня такими датами, как:
2009-12-31T00:00:00-06:00
Rfc3339-совместимый вывод по умолчанию используемой библиотеки jaxb. Обратите внимание на двоеточие, которое требуется rfc3339, когда смещение не является литералом "z":
time-numoffset = ("+" / "-") time-hour ":" time-minute time-offset = "Z" / time-numoffset
Я хочу разобрать их в NSDates.
NSDateFormatter хочет шаблоны в синтаксисе, заданном Unicode, который предлагает символы поля даты для часовых поясов, таких как "PDT", "-0800", "GMT-08:00", но не "-08:00".
Поиск в Google и другие подобные вопросы SO, производит только форматы даты, такие как
[myDateParser setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"];
/* or: */ [myDateParser setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
Последний из которых требует буквального "Z", а первый настаивает либо на отсутствии двоеточия, либо на "GMT". Тем не менее, они работали до ios 4.x (возможно, путем полного сброса смещения tz; мои данные не ясны).
Мои варианты на данный момент очень печальны:
- найдите какой-нибудь недокументированный спецификатор формата или какой-то странный режим для помещения NSDateFormatter, который будет принимать случайное двоеточие: longshot, вероятно, не существует. [Примечание]
- убедите моего издателя сервиса перевести все даты в зулусское время и указать "Z": политически сложный.
- написать свой собственный подкласс NSFormatter или исследовать старый добрый
strptime_l
: Работа.:) - управляйте моим вводом и удаляйте последний двоеточие: хрупкий и уродливый, но вероятный путь наименьшего сопротивления.
Правильно ли я понял ситуацию, что текущий NSDateFormatter следует юникоду строго без расширений; а форматы юникода недостаточны для полного описания даты rfc3339?
[FOOTNOTE] Я вернусь к этому три года спустя, чтобы добавить небольшое дополнение: Unicode и Apple добавили эту функцию в строки формата, начиная с iOS6/OSX10.8. Сравните последнюю редакцию на момент написания этой статьи с ее непосредственным предшественником и обратите внимание на добавление 5 "Z", что дает формат зоны, например "-08:00". Так что, если вам удастся избежать поддержки ditching для 5.x/10.7, есть новый правильный способ сделать это. Я оставлю предыдущий ответ на вопрос, так как это все еще лучший подход, когда требуется обратная совместимость.
3 ответа
Анализ строки даты в Какао может быть болезненным, особенно если вам приходится иметь дело с датами, генерируемыми веб-сервисами на основе.NET.
Я бы посоветовал взглянуть на категорию NSDate+InternetDateTime, которую Майкл Уотерфолл имеет в NSDate, в рамках своего проекта MWFeedParser на github. Это хорошо сработало для меня, разбирая именно тот формат даты, который вы описываете.
- getObjectValue:forString:range:error: действительно может правильно проанализировать даты RFC3339. Я понятия не имею, почему - dateWithString: не может:
// RFC3339 date formatting
NSString *dateString = @"2012-04-11T18:34:19+00:00";
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
NSDate *date;
NSError *error;
[formatter getObjectValue:&date forString:dateString range:nil error:&error];
Символы формата строки нигде не описаны в документации Apple. Вместо этого в каком-то документе скрыта ссылка, указывающая на стандарт Unicode на
http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html
Используя информацию по этой ссылке, это довольно просто:
- (NSDate*)dateFromRFC3339String:(NSString*)aString
{
static NSDateFormatter* sRFC3339DateFormatter = nil;
static NSDateFormatter* sRFC3339DateFormatterSubSeconds = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
sRFC3339DateFormatter = [[NSDateFormatter alloc] init];
[sRFC3339DateFormatter setLocale:enUSPOSIXLocale];
[sRFC3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssXXXXX"];
[sRFC3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
sRFC3339DateFormatterSubSeconds = [[NSDateFormatter alloc] init];
[sRFC3339DateFormatterSubSeconds setLocale:enUSPOSIXLocale];
[sRFC3339DateFormatterSubSeconds setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSSSSXXXXX"];
[sRFC3339DateFormatterSubSeconds setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
});
NSDate* date = [sRFC3339DateFormatter dateFromString:aString];
if (date == nil)
date = [sRFC3339DateFormatterSubSeconds dateFromString:aString];
return date;
}