guilherme.minglini guilherme.minglini - 1 month ago 13
iOS Question

Why NSDateFormatter is returning null for a 19/10/2014 in a Brazilian time zone?

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


Why
myDate
is null for this specific date (19/10/2014)??

If i change the
dateString
to
@"25/10/2014"
,
dateFormatter
return the date correctly... What is wrong with my code?

* This code returns null when my iPhone time zone is "Brasilia, Brasil". When my time zone is "Washington, D.C., EUA" for example, the code returns the correct date.

Answer

We can reproduce your problem by explicitly setting the time zone to “Brazil/East”:

#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;
}

Here's the output:

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

Since you didn't give a time in your dateString, the system assumes midnight. But midnight on that date doesn't exist in the Brazilian time zone.

Brazil changes from BRT (daylight-saving time zone) to BRST (non-daylight-saving time zone) on October 19, 2014, skipping directly from the last moment of “18/10/2014” to “19/10/2014 01:00:00”.

Since “19/10/2014 00:00:00” doesn't exist, NSDateFormatter returns nil. I think this is bad behavior on the part of NSDateFormatter, but we have to deal with it. -[NSDateFormatter dateFromString:] eventually calls CFDateFormatterGetAbsoluteTimeFromString, which uses the udat_parseCalendar function from the International Components for Unicode (icu) library to parse the date.

You can work around the problem by making the parser use noon instead of midnight as the default time. No time zones change to/from daylight saving time at noon. Let's write a helper function that returns noon of some arbitrary date in a given time zone:

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];
}

Then we set the date formatter's defaultDate to this noon date:

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;
}

And here's the output:

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