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

时间:2011-07-07 15:30:15

标签: ios objective-c iphone locale nsdateformatter

似乎NSDateFormatter有一个“功能”意外地咬你:如果你做一个简单的“固定”格式操作,例如:

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

然后它在美国和大多数区域设置工作正常UNTIL ...将手机设置为24小时区域的人将设置中的12/24小时开关设置为12.然后上面开始添加“AM”或“ PM“在结果字符串的末尾。

(参见,例如,NSDateFormatter, am I doing something wrong or is this a bug?

(见https://developer.apple.com/library/content/qa/qa1480/_index.html

显然苹果宣称这是“不好” - 按设计划分,他们不会修复它。

规避显然是为特定地区(通常是美国)设置日期格式化程序的区域设置,但这有点乱:

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

onsies-twosies也不错,但是我正在处理大约十个不同的应用程序,而我看到的第一个应用程序有43个这种情况的实例。

因此,对于宏/重写类/任何最小化改变所有内容的努力的任何聪明的想法,而不使代码模糊? (我的第一直觉是使用一个在init方法中设置语言环境的版本来覆盖NSDateFormatter。需要更改两行 - 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

恩惠!

我将奖励给我在周二中午看到的最佳(合法)建议/批评奖励。 [见下文 - 延长期限。]

更新

Re OMZ的建议,这是我找到的 -

这是类别版本 - h file:

#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中的日志正在执行(并且代码中的停止位置被命中),因此不仅仅是代码的某种情况不会被使用。

Bounty更新:

由于我还没有收到任何适用的回复,我会将赏金截止日期延长一两天。

Bounty在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

基本上只是更改了静态语言环境变量的名称(如果与子类中声明的静态存在某些冲突)并添加了额外的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方法存在“奇怪”。

最终答案

请参阅下面的accepted answer

4 个答案:

答案 0 :(得分:63)

咄!!

有时候你有一个“啊哈!!”那一刻,有时它更像是“Duh !!”这是后者。在initWithSafeLocale的类别中,“超级”init被编码为self = [super init];。这取决于NSDateFormatter的SUPERCLASS,但init对象本身没有NSDateFormatter

显然,当跳过此初始化时,setLocale“反弹”,可能是因为对象中缺少某些数据结构。将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

答案 1 :(得分:38)

您可以使用额外的初始化程序创建NSDateFormatter类别而不是子类化,该初始化程序负责分配区域设置,也可能是格式字符串,因此您在初始化后就可以使用现成的格式化程序它

@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"];

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

如果您总是使用相同的日期格式,您还可以添加返回具有特定配置的单例实例的类别方法(类似+sharedRFC3339DateFormatter)。但请注意,NSDateFormatter不是线程安全的,当您从多个线程使用相同的实例时,必须使用锁或@synchronized块。

答案 2 :(得分:6)

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

您应该使用一个NSDateFormatter设置dateFormatlocale强制设置en_US_POSIX来接收日期(来自服务器/ API)。

然后您应该使用不同的NSDateFormatter用于设置timeStyle / dateStyle属性的用户界面 - 这样您就没有明确的dateFormat由你自己设定,因此错误地假设将使用该格式。

这意味着UI由用户首选项驱动(上午/下午vs 24小时,日期字符串格式正确地根据用户选择 - 来自iOS设置),而“进入”您的应用的日期始终正在“正确解析”到NSDate供您使用。

答案 3 :(得分:3)

以下是swift版本中该问题的解决方案。在swift中,我们可以使用扩展而不是类别。 所以,在这里我创建了DateFormatter的扩展,并在initWithSafeLocale中 返回带有相关Locale的DateFormatter,在我们的例子中是en_US_POSIX,除此之外还提供了几个日期形成方法。

  • Swift 4

    extension DateFormatter {
    
    private static var dateFormatter = DateFormatter()
    
    class func initWithSafeLocale(withDateFormat dateFormat: String? = nil) -> DateFormatter {
    
        dateFormatter = DateFormatter()
    
        var en_US_POSIX: Locale? = nil;
    
        if (en_US_POSIX == nil) {
            en_US_POSIX = Locale.init(identifier: "en_US_POSIX")
        }
        dateFormatter.locale = en_US_POSIX
    
        if dateFormat != nil, let format = dateFormat {
            dateFormatter.dateFormat = format
        }else{
            dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        }
        return dateFormatter
    }
    
    // ------------------------------------------------------------------------------------------
    
    class func getDateFromString(string: String, fromFormat dateFormat: String? = nil) -> Date? {
    
        if dateFormat != nil, let format = dateFormat {
            dateFormatter = DateFormatter.initWithSafeLocale(withDateFormat: format)
        }else{
            dateFormatter = DateFormatter.initWithSafeLocale()
        }
        guard let date = dateFormatter.date(from: string) else {
            return nil
        }
        return date
    }
    
    // ------------------------------------------------------------------------------------------
    
    class func getStringFromDate(date: Date, fromDateFormat dateFormat: String? = nil)-> String {
    
        if dateFormat != nil, let format = dateFormat {
            dateFormatter = DateFormatter.initWithSafeLocale(withDateFormat: format)
        }else{
            dateFormatter = DateFormatter.initWithSafeLocale()
        }
    
        let string = dateFormatter.string(from: date)
    
        return string
    }   }
    
  • 用法说明:

    let date = DateFormatter.getDateFromString(string: "11-07-2001”, fromFormat: "dd-MM-yyyy")
    print("custom date : \(date)")
    let dateFormatter = DateFormatter.initWithSafeLocale(withDateFormat: "yyyy-MM-dd HH:mm:ss")
    let dt = DateFormatter.getDateFromString(string: "2001-05-05 12:34:56")
    print("base date = \(dt)")
    dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
    let dateString = dateFormatter.string(from: Date())
    print("dateString = " + dateString)
    let date1 = dateFormatter.date(from: "2001-05-05 12:34:56")
    print("date1 = \(String(describing: date1))")
    let date2 = dateFormatter.date(from: "2001-05-05 22:34:56")
    print("date2 = \(String(describing: date2))")
    let date3 = dateFormatter.date(from: "2001-05-05 12:34:56PM")
    print("date3 = \(String(describing: date3))")
    let date4 = dateFormatter.date(from: "2001-05-05 12:34:56 PM")
    print("date4 = \(String(describing: date4))")