为什么NSDateFormatter在巴西时区为19/10/2014返回null?

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

为什么myDate在这个特定的date为null(19/10/2014)?

如果我将dateString更改为@"25/10/2014" ,则dateFormatter正确返回date…我的代码有什么问题?

*当我的iPhone时区是“巴西利亚,巴西”时,此代码返回null。 例如,当我的时区是“华盛顿特区,EUA”时,代码会返回正确的date。

我们可以通过将时区明确设置为“巴西/东部”来重现您的问题:

 #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给出时间,因此系统假定午夜。 但巴西时区不存在午夜

巴西从2014年10月19日的BRT(夏时制时区)变为BRST(非夏令时区) ,直接从“ 2014年10月18日 ”的最后一刻跳到“ 2014年10月19日 01 :00:00” 。

由于“19/10/2014 00:00:00”不存在, NSDateFormatter返回nil 。 我认为这是NSDateFormatter不良行为,但我们必须处理它。 -[NSDateFormatter dateFromString:]最终调用CFDateFormatterGetAbsoluteTimeFromString ,它使用国际组件Unicode(icu)库中的udat_parseCalendar函数来parsingdate。

您可以通过使parsing器使用中午而不是午夜作为默认时间来解决该问题。 中午的夏令时没有时区变化。 让我们来写一个帮助函数,它返回给定时区中某个任意date的中午:

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

然后我们把date格式化器的defaultDate为这个中午的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; } 

这里是输出:

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

Rob Mayoff对这个问题给出了一个很好的解释和解决办法 。 正如罗布所指出的那样,巴西时区不存在19/10/2014午夜。

另一个可能的解决scheme是告诉date格式化程序是“宽松的”。 在这种情况下,它将返回给定date的第一个有效date:

 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”,即夏令时开始当天的第一个有效date。

如果您希望格式化的date是希望所有地区的所有设备都能理解的特定格式,则需要在date格式化程序上设置语言环境。

如果不这样做,date格式化程序将默认为设备区域设置。 这意味着巴西的date格式看起来不是dd / MM / yyyy

您可以强制您的date格式化程序使用特定的区域设置,如下所示:

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

希望这可以帮助

w ^