处理NSDateFormatter语言环境“feechur”的最佳方法是什么?

看来, NSDateFormatter有一个“function”,意想不到的咬你:如果你做一个简单的“固定”格式的操作,如:

 NSDateFormatter* fmt = [[NSDateFormatter alloc] init]; [fmt setDateFormat:@"yyyyMMddHHmmss"]; NSString* dateStr = [fmt stringFromDate:someDate]; [fmt release]; 

然后,它在美国和大多数地区正常工作,直到…一个人的手机设置为24小时的地区设置12/24小时开关设置为12。然后上面开始加上“AM”或“PM”上结果string的结尾。

(见,例如, NSDateFormatter,我做错了什么或这是一个错误? )

(请参阅https://developer.apple.com/library/content/qa/qa1480/_index.html )

显然苹果已经宣布这是“坏” – 破碎devise,他们不会解决它。

规避显然是为特定地区(通常是美国)设置date格式化程序的语言环境,但是这有点麻烦:

 NSLocale *loc = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; [df setLocale: loc]; [loc release]; 

不是太糟糕了,但是我正在处理大约10个不同的应用程序,而我所看到的第一个应用程序有43个这种情况。

因此,对于macros/重写类/任何的任何聪明的想法,以尽量减less努力改变一切,而不使代码晦涩? (我的第一个直觉是用一个版本来覆盖NSDateFormatter,这个版本会在init方法中设置locale。需要改变两行 – alloc / init行和添加的导入。)

添加

这是我到目前为止 – 似乎在所有情况下工作:

 @implementation BNSDateFormatter -(id)init { static NSLocale* en_US_POSIX = nil; NSDateFormatter* me = [super init]; if (en_US_POSIX == nil) { en_US_POSIX = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; } [me setLocale:en_US_POSIX]; return me; } @end 

赏金!

我会奖励我在周二中午看到的最好的(合法的)build议/批评。 [见下 – 延期截止date。]

更新

重新OMZ的build议,这是我正在发现 –

这里是类别版本 – h文件:

 #import <Foundation/Foundation.h> @interface NSDateFormatter (Locale) - (id)initWithSafeLocale; @end 

类别m文件:

 #import "NSDateFormatter+Locale.h" @implementation NSDateFormatter (Locale) - (id)initWithSafeLocale { static NSLocale* en_US_POSIX = nil; self = [super init]; if (en_US_POSIX == nil) { en_US_POSIX = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; } NSLog(@"Category's locale: %@ %@", en_US_POSIX.description, [en_US_POSIX localeIdentifier]); [self setLocale:en_US_POSIX]; return self; } @end 

代码:

 NSDateFormatter* fmt; NSString* dateString; NSDate* date1; NSDate* date2; NSDate* date3; NSDate* date4; fmt = [[NSDateFormatter alloc] initWithSafeLocale]; [fmt setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; dateString = [fmt stringFromDate:[NSDate date]]; NSLog(@"dateString = %@", dateString); date1 = [fmt dateFromString:@"2001-05-05 12:34:56"]; NSLog(@"date1 = %@", date1.description); date2 = [fmt dateFromString:@"2001-05-05 22:34:56"]; NSLog(@"date2 = %@", date2.description); date3 = [fmt dateFromString:@"2001-05-05 12:34:56PM"]; NSLog(@"date3 = %@", date3.description); date4 = [fmt dateFromString:@"2001-05-05 12:34:56 PM"]; NSLog(@"date4 = %@", date4.description); [fmt release]; fmt = [[BNSDateFormatter alloc] init]; [fmt setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; dateString = [fmt stringFromDate:[NSDate date]]; NSLog(@"dateString = %@", dateString); date1 = [fmt dateFromString:@"2001-05-05 12:34:56"]; NSLog(@"date1 = %@", date1.description); date2 = [fmt dateFromString:@"2001-05-05 22:34:56"]; NSLog(@"date2 = %@", date2.description); date3 = [fmt dateFromString:@"2001-05-05 12:34:56PM"]; NSLog(@"date3 = %@", date3.description); date4 = [fmt dateFromString:@"2001-05-05 12:34:56 PM"]; NSLog(@"date4 = %@", date4.description); [fmt release]; 

结果:

 2011-07-11 17:44:43.243 DemoApp[160:307] Category's locale: <__NSCFLocale: 0x11a820> en_US_POSIX 2011-07-11 17:44:43.257 DemoApp[160:307] dateString = 2011-07-11 05:44:43 PM 2011-07-11 17:44:43.264 DemoApp[160:307] date1 = (null) 2011-07-11 17:44:43.272 DemoApp[160:307] date2 = (null) 2011-07-11 17:44:43.280 DemoApp[160:307] date3 = (null) 2011-07-11 17:44:43.298 DemoApp[160:307] date4 = 2001-05-05 05:34:56 PM +0000 2011-07-11 17:44:43.311 DemoApp[160:307] Extended class's locale: <__NSCFLocale: 0x11a820> en_US_POSIX 2011-07-11 17:44:43.336 DemoApp[160:307] dateString = 2011-07-11 17:44:43 2011-07-11 17:44:43.352 DemoApp[160:307] date1 = 2001-05-05 05:34:56 PM +0000 2011-07-11 17:44:43.369 DemoApp[160:307] date2 = 2001-05-06 03:34:56 AM +0000 2011-07-11 17:44:43.380 DemoApp[160:307] date3 = (null) 2011-07-11 17:44:43.392 DemoApp[160:307] date4 = (null) 

手机[使iPod Touch]被设置为英国,12/24开关设置为12.两个结果有明显的区别,我认为类别版本是错误的。 请注意,类别版本IS中的日志得到执行(并停止放置在代码中),所以这不仅仅是代码没有被使用的情况。

赏金更新:

由于我还没有得到任何适用的答复,我将延长一两天的赏金截止date。

赏金在21小时内结束 – 即使答案在我的情况下并不真正有用,它也会尽力帮助。

一个奇怪的观察

略微修改了类别的实现:

 #import "NSDateFormatter+Locale.h" @implementation NSDateFormatter (Locale) - (id)initWithSafeLocale { static NSLocale* en_US_POSIX2 = nil; self = [super init]; if (en_US_POSIX2 == nil) { en_US_POSIX2 = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; } NSLog(@"Category's locale: %@ %@", en_US_POSIX2.description, [en_US_POSIX2 localeIdentifier]); [self setLocale:en_US_POSIX2]; NSLog(@"Category's object: %@ and object's locale: %@ %@", self.description, self.locale.description, [self.locale localeIdentifier]); return self; } @end 

基本上只是改变了静态语言环境variables的名称(以防与子类中声明的静态语言有冲突),并添加了额外的NSLog。 但看看NSLog打印什么:

 2011-07-15 16:35:24.322 DemoApp[214:307] Category's locale: <__NSCFLocale: 0x160550> en_US_POSIX 2011-07-15 16:35:24.338 DemoApp[214:307] Category's object: <NSDateFormatter: 0x160d90> and object's locale: <__NSCFLocale: 0x12be70> en_GB 2011-07-15 16:35:24.345 DemoApp[214:307] dateString = 2011-07-15 04:35:24 PM 2011-07-15 16:35:24.370 DemoApp[214:307] date1 = (null) 2011-07-15 16:35:24.378 DemoApp[214:307] date2 = (null) 2011-07-15 16:35:24.390 DemoApp[214:307] date3 = (null) 2011-07-15 16:35:24.404 DemoApp[214:307] date4 = 2001-05-05 05:34:56 PM +0000 

正如你所看到的,setLocale根本就没有。 格式化程序的语言环境仍然是en_GB。 看起来,类别中的init方法有些“奇怪”。

最终答案

请参阅下面的接受答案 。

咄!

有时候你有一个“啊哈” 有时候更多的是“呃!!” 这是后者。 在initWithSafeLocale类中,“super” init被编码为self = [super init]; 。 这在NSDateFormatter的SUPERCLASS中,但不会init NSDateFormatter对象本身。

显然,当这个初始化被跳过时, setLocale “反弹”,可能是因为对象中缺less一些数据结构。 将init更改为self = [self init]; 导致NSDateFormatter初始化发生,并且setLocale又开心了。

以下是类别.m的“最终”来源:

 #import "NSDateFormatter+Locale.h" @implementation NSDateFormatter (Locale) - (id)initWithSafeLocale { static NSLocale* en_US_POSIX = nil; self = [self init]; if (en_US_POSIX == nil) { en_US_POSIX = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; } [self setLocale:en_US_POSIX]; return self; } @end 

你可以创build一个NSDateFormatter类别, NSDateFormatter需要创build一个NSDateFormatter类别,它有一个额外的初始化工具,负责分配语言环境,还可能还有一个格式化string,所以在初始化之后你就可以使用一个随时可以使用的格式化工具了。

 @interface NSDateFormatter (LocaleAdditions) - (id)initWithPOSIXLocaleAndFormat:(NSString *)formatString; @end @implementation NSDateFormatter (LocaleAdditions) - (id)initWithPOSIXLocaleAndFormat:(NSString *)formatString { self = [super init]; if (self) { NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; [self setLocale:locale]; [locale release]; [self setFormat:formatString]; } return self; } @end 

那么你可以在代码中的任何地方使用NSDateFormatter ,只需:

 NSDateFormatter* fmt = [[NSDateFormatter alloc] initWithPOSIXLocaleAndFormat:@"yyyyMMddHHmmss"]; 

为了避免名称冲突,您可能希望以某种方式为分类方法添加前缀,以防苹果决定在未来版本的操作系统中添加此类方法。

如果您始终使用相同的date格式,则还可以添加使用特定configuration返回单例实例的类别方法(如+sharedRFC3339DateFormatter )。 但是请注意, NSDateFormatter不是线程安全的,并且当您使用多个线程中的相同实例时,您必须使用locking或@synchronized块。

我可以提出一些完全不同的东西,因为说实话,这一切都有点落在兔子的洞里。

您应该使用一个NSDateFormatterdateFormat集合和locale强制en_US_POSIX接收date(从服务器/ API)。

那么你应该使用一个不同的NSDateFormatter的用户界面,你将设置timeStyle / dateStyle属性 – 这样你没有一个明确的dateFormat自己设置,从而错误地假定格式将被使用。

这意味着用户界面是由用户喜好驱动的(上午/下午与24小时,datestring格式正确的用户select – 从iOS设置),而“进入”你的应用程序的date总是正确地“parsing” NSDate供你使用。

试试这一个….

 -(NSDate *)getDateInCurrentSystemTimeZone { NSDate* sourceDate = [NSDate date]; NSTimeZone* sourceTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; NSTimeZone* destinationTimeZone = [NSTimeZone systemTimeZone]; NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:sourceDate]; NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:sourceDate]; NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset; NSDate* destinationDate = [[NSDate alloc] initWithTimeInterval:interval sinceDate:sourceDate]; return destinationDate; }