12
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.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
guilherme.minglini
  • 564
  • 2
  • 6
  • 20
  • nothing wrong with the code, it fine. put break point & check whats wrong. – Pawan Rai Jun 06 '14 at 18:54
  • Nothing wrong with what you posted- runs fine on my end. Do you have a strange locale set on your device that could be interfering with your formatter? Per Apple's docs: `"Note that although setting a format string (setDateFormat:) in principle specifies an exact format, in practice it may nevertheless also be overridden by a user’s preferences—see Data Formatting Guide for more details."` – Stonz2 Jun 06 '14 at 18:54
  • @Stonz2 i updated the question, please read the (*) – guilherme.minglini Jun 06 '14 at 19:04
  • And you're not specifying a time along with the date? – Hot Licks Jun 06 '14 at 19:06
  • @pawan try the code with your device in "Brasilia, Brasil" timezone – guilherme.minglini Jun 06 '14 at 19:07
  • I think i found the error. Here in Brazil, 19/10/2014 - 00:00 does not exists because of the daylight saving. – guilherme.minglini Jun 06 '14 at 19:45
  • Yeah, I figured it might be a DST thing. NSDateFormatter is a little flaky when parsing dates without associated times. – Hot Licks Jun 06 '14 at 20:20
  • This bug is really interesting, why do you closed it? – conca Oct 29 '14 at 19:44

3 Answers3

23

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
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Thanks for your help. I have just tested NSDateComponents with the date "19/10/2014 00:00" and it automatically returns a date "19/10/2014 01:00", because of the daylight-saving here in Brazil. – guilherme.minglini Jun 06 '14 at 19:58
3

Rob Mayoff gave an excellent explanation and solution to this problem. As Rob pointed out, midnight on 19/10/2014 doesn't exist in the Brazilian time zone.

Another possible solution is to tell the date formatter to be "lenient". In that case it will return the first valid date on the given day:

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

The result is "2014-10-19 03:00:00 UTC" which is "2014-10-19 01:00:00 BRST", i.e. the first valid date on that day when daylight saving time started.

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
-1

If your date you wish to format is a specific format that you want all devices in all regions to understand, you will need to set a locale on your date formatter.

Without doing so, the date formatter will default to the devices locale. This would mean that Brazil's date format is seemingly not dd/MM/yyyy

You can force your date formatter to use a specific locale like so:

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

Hope this helps

W

William George
  • 6,735
  • 3
  • 31
  • 39