Технические вопросы и ответы QA1480

NSDateFormatter и интернет-даты

Q: Я использую NSDateFormatter для парсинга даты интернет-стиля, но это перестало работать для некоторых пользователей в некоторых областях. Я установил определенную строку формата даты; это не должно вынуждать NSDateFormatter работать независимо от настроек области пользователя?

A: Нет. В то время как установка строки формата даты, будет казаться, будет работать на большинство пользователей, это не правильное решение этой проблемы. Существует много мест, где строки формата ведут себя неожиданными способами. Например:

Для решения этой проблемы должным образом необходимо понять, что NSDateFormatter имеет две общих роли:

Если Вы работаете с видимыми пользователем датами, необходимо избежать устанавливать фиксированную строку формата даты, потому что очень трудно предсказать, как строка формата будет выражена во всех возможных пользовательских конфигурациях. Скорее необходимо ограничить себя установкой стилей даты и времени (через -[NSDateFormatter setDateStyle:] и -[NSDateFormatter setTimeStyle:]) или генерируйте свою строку формата даты из шаблона (использование +[NSDateFormatter dateFormatFromTemplate:options:locale:]).

С другой стороны, если Вы работаете с датами фиксированного формата, необходимо сначала установить локаль средства форматирования даты к чему-то подходящему для фиксированного формата. В большинстве случаев лучшая локаль для выбора является «en_US_POSIX», локаль, это специально предназначено для получения английских результатов США и независимо от пользовательских настроек и независимо от установок системы. «en_US_POSIX» является также инвариантным своевременно (если США, в некоторый момент в будущем, изменят способ, которым это форматирует даты, «то en_US» изменится для отражения нового поведения, но «en_US_POSIX» не будет), и между машинами («en_US_POSIX» работы то же на iOS, как это делает на OS X, и как он это делает на других платформах).

Как только Вы установили «en_US_POSIX» как локаль средства форматирования даты, можно тогда установить строку формата даты, и средство форматирования даты будет последовательно вести себя для всех пользователей.

Перечисление 1 показывает, как использовать NSDateFormatter для обеих из ролей, описанных выше. Сначала это создает «en_US_POSIX» средство форматирования даты для парсинга входящей строки даты RFC 3339, с помощью фиксированной строки формата даты и UTC как часовой пояс. Затем, это создает стандартное средство форматирования даты для рендеринга даты как строка для отображения пользователю.

Перечисление 1  , Анализирующее разовый датой RFC 3339

- (NSString *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString
    // Returns a user-visible date time string that corresponds to the
    // specified RFC 3339 date time string. Note that this does not handle
    // all possible RFC 3339 date time strings, just one of the most common
    // styles.
{
    NSString *          userVisibleDateTimeString;
    NSDateFormatter *   rfc3339DateFormatter;
    NSLocale *          enUSPOSIXLocale;
    NSDate *            date;
    NSDateFormatter *   userVisibleDateFormatter;
 
    userVisibleDateTimeString = nil;
 
    // Convert the RFC 3339 date time string to an NSDate.
 
    rfc3339DateFormatter = [[[NSDateFormatter alloc] init] autorelease];
 
    enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
 
    [rfc3339DateFormatter setLocale:enUSPOSIXLocale];
    [rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
    [rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
 
    date = [rfc3339DateFormatter dateFromString:rfc3339DateTimeString];
    if (date != nil) {
 
        // Convert the NSDate to a user-visible date string.
 
        userVisibleDateFormatter = [[[NSDateFormatter alloc] init] autorelease];
 
        [userVisibleDateFormatter setDateStyle:NSDateFormatterShortStyle];
        [userVisibleDateFormatter setTimeStyle:NSDateFormatterShortStyle];
 
        userVisibleDateTimeString = [userVisibleDateFormatter stringFromDate:date];
    }
    return userVisibleDateTimeString;
}

Код в Перечислении 1 корректен, но это не столь эффективно, как это могло быть. В частности это создает средство форматирования даты, использует его один раз, и затем выбрасывает его. Лучший подход является одним показанным в Перечислении 2. Это держится за его средства форматирования даты для последующего повторного использования.

Перечисление 2  , Анализирующее RFC 3339, разовый датой более эффективно

static NSDateFormatter *    sUserVisibleDateFormatter;
 
- (NSString *)userVisibleDateTimeStringForRFC3339DateTimeString2:(NSString *)rfc3339DateTimeString
    // Returns a user-visible date time string that corresponds to the
    // specified RFC 3339 date time string. Note that this does not handle
    // all possible RFC 3339 date time strings, just one of the most common
    // styles.
{
    static NSDateFormatter *    sRFC3339DateFormatter;
    NSString *                  userVisibleDateTimeString;
    NSDate *                    date;
 
    // If the date formatters aren't already set up, do that now and cache them
    // for subsequence reuse.
 
    if (sRFC3339DateFormatter == nil) {
        NSLocale *                  enUSPOSIXLocale;
 
        sRFC3339DateFormatter = [[NSDateFormatter alloc] init];
 
        enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
 
        [sRFC3339DateFormatter setLocale:enUSPOSIXLocale];
        [sRFC3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
        [sRFC3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    }
 
    if (sUserVisibleDateFormatter == nil) {
        sUserVisibleDateFormatter = [[NSDateFormatter alloc] init];
 
        [sUserVisibleDateFormatter setDateStyle:NSDateFormatterShortStyle];
        [sUserVisibleDateFormatter setTimeStyle:NSDateFormatterShortStyle];
    }
 
    // Convert the RFC 3339 date time string to an NSDate.
    // Then convert the NSDate to a user-visible date string.
 
    userVisibleDateTimeString = nil;
 
    date = [sRFC3339DateFormatter dateFromString:rfc3339DateTimeString];
    if (date != nil) {
        userVisibleDateTimeString = [sUserVisibleDateFormatter stringFromDate:date];
    }
    return userVisibleDateTimeString;
}

При кэшировании средств форматирования даты или каких-либо других объектов, зависящих от текущей локали пользователя, необходимо подписаться на NSCurrentLocaleDidChangeNotification уведомление и обновление Ваши кэшируемые объекты, когда изменяется текущая локаль. Код в Перечислении 2 определяет sUserVisibleDateFormatter за пределами метода так, чтобы другой код, не показанный, мог обновить его по мере необходимости. Напротив, sRFC3339DateFormatter определяется в методе, потому что проектом это не зависит от настроек локали пользователя.

Наконец, если Вы готовы смотреть на решения за пределами пространства Какао, это очень просто и эффективно проанализировать и генерировать даты фиксированного формата с помощью функций стандартной библиотеки для C strptime_l и strftime_l. Знайте, что библиотека C также имеет идею текущей локали. Для гарантии фиксированного формата даты необходимо передать NULL к loc параметр этих подпрограмм. Это заставляет их использовать локаль POSIX (также известный как локаль C), который эквивалентен «en_US_POSIX» локали Какао.



История версии документа


ДатаПримечания
14.08.2014

Удаленный ссылка на ошибку в +autoupdatingCurrentLocale (r. 7792724), который был фиксирован в iOS 4 (r. 15794178). Добавленный ссылка на шаблоны средства форматирования даты. Внесенные незначительные редакционные изменения.

29.04.2010

RFC 3 339 дат всегда находятся в UTC, так устанавливает часовой пояс на средстве форматирования RFC 3339 даты к UTC.

31.03.2010

Новый документ, объясняющий, как использовать NSDateFormatter с датами фиксированного формата, как те в различных Протоколах Интернета.