Подтвердить что ты не робот

Почему NSDateFormatter возвращает null за 19/10/2014 в бразильском часовом поясе?

NSString *dateString = @"19/10/2014";
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"dd/MM/yyyy"];
NSDate *myDate = [dateFormatter dateFromString:dateString];

Почему myDate имеет значение null для этой конкретной даты (19/10/2014)??

Если я сменил dateString на @"25/10/2014", dateFormatter правильно вернул дату... Что не так с моим кодом?

* Этот код возвращает null, когда в моем часовом поясе iPhone находится "Бразилиа, Бразилиа". Например, когда мой часовой пояс - "Вашингтон, округ Колумбия, ЕСА", код возвращает правильную дату.

4b9b3361

Ответ 1

Мы можем воспроизвести вашу проблему, явно установив часовой пояс на "Бразилия/Восток":

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        NSString *dateString = @"19/10/2014";
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"Brazil/East"];
        [dateFormatter setDateFormat:@"dd/MM/yyyy"];
        NSDate *myDate = [dateFormatter dateFromString:dateString];
        NSLog(@"myDate = %@", myDate);
    }
    return 0;
}

Здесь вывод:

2014-06-06 14:22:28.254 commandLine[31169:303] myDate = (null)

Поскольку вы не указали время в dateString, система принимает полночь. Но полночь в эту дату не существует в бразильском часовом поясе.

Бразилия изменила с BRT (летнее время) на BRST (не дневной свет часовой пояс) 19 октября 2014 года, пропуская непосредственно с последнего момента "18/10/2014" до "19/10/2014 01:00:00".

Так как "19/10/2014 00:00:00" не существует, NSDateFormatter возвращает nil. Я думаю, что это плохое поведение со стороны NSDateFormatter, но мы должны справиться с этим. -[NSDateFormatter dateFromString:] в конечном итоге вызывает CFDateFormatterGetAbsoluteTimeFromString, в котором используется функция udat_parseCalendar из Международные компоненты для библиотеки Unicode (icu) для анализа даты.

Вы можете обойти проблему, заставив парсер использовать полдень вместо полуночи как время по умолчанию. Никакие часовые пояса не меняются в/из летнего времени в полдень. Давайте напишем вспомогательную функцию, которая возвращает полдень некоторой произвольной даты в данном часовом поясе:

static NSDate *someDateWithNoonWithTimeZone(NSTimeZone *timeZone) {
    NSDateComponents *components = [[NSDateComponents alloc] init];
    components.timeZone = timeZone;
    components.era = 1;
    components.year = 2001;
    components.month = 1;
    components.day = 1;
    components.hour = 12;
    components.minute = 0;
    components.second = 0;
    return [[NSCalendar autoupdatingCurrentCalendar] dateFromComponents:components];
}

Затем мы устанавливаем формат даты defaultDate к этой дате полудня:

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        NSString *dateString = @"19/10/2014";
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"Brazil/East"];
        dateFormatter.dateFormat = @"dd/MM/yyyy";
        dateFormatter.defaultDate = someDateWithNoonWithTimeZone(dateFormatter.timeZone);
        NSDate *myDate = [dateFormatter dateFromString:dateString];
        NSLog(@"myDate = %@", myDate);
    }
    return 0;
}

И вот вывод:

2014-06-06 14:52:31.939 commandLine[31982:303] myDate = 2014-10-19 14:00:00 +0000

Ответ 2

Роб Майофф дал отличное объяснение и решение этой проблемы. Как Роб отметил, что полночь 19/10/2014 не существует в бразильском часовом поясе.

Еще одно возможное решение - указать формат даты "Мягкий". В этом случае он вернет первую действительную дату на данный день:

NSString *dateString = @"19/10/2014";
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"America/Sao_Paulo"];
dateFormatter.dateFormat = @"dd/MM/yyyy";

dateFormatter.lenient = YES;

NSDate *myDate = [dateFormatter dateFromString: dateString];
NSLog(@"myDate = %@", myDate);
// myDate = 2014-10-19 03:00:00 +0000

В результате получается "2014-10-19 03:00:00 UTC", который "2014-10-19 01:00:00 BRST", то есть первая действительная дата в тот день, когда переход на летнее время начало.

Ответ 3

Если ваша дата, которую вы хотите отформатировать, является определенным форматом, который вы хотите, чтобы все устройства во всех регионах были понятны, вам необходимо установить языковой стандарт в своем форматировании даты.

Без этого формат даты по умолчанию будет соответствовать языку. Это будет означать, что формат даты в Бразилии, по-видимому, не является dd/MM/yyyy

Вы можете заставить свой формат даты использовать конкретный язык так:

[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"]];

Надеюсь, что это поможет

Ш