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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6613110/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 20:39:21  来源:igfitidea点击:

What is the best way to deal with the NSDateFormatter locale "feechur"?

iosobjective-ciphonelocalensdateformatter

提问by Hot Licks

It seems that NSDateFormatterhas a "feature" that bites you unexpectedly: If you do a simple "fixed" format operation such as:

似乎NSDateFormatter有一个“功能”让你意想不到:如果你做一个简单的“固定”格式操作,比如:

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

Then it works fine in the US and most locales UNTIL ... someone with their phone set to a 24-hour region sets the 12/24 hour switch in settings to 12. Then the above starts tacking "AM" or "PM" onto the end of the resulting string.

然后它在美国和大多数语言环境中都可以正常工作,直到……将手机设置为 24 小时区域的人将设置中的 12/24 小时开关设置为 12。然后上面开始添加“AM”或“PM”结果字符串的结尾。

(See, eg, NSDateFormatter, am I doing something wrong or is this a bug?)

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

(And see https://developer.apple.com/library/content/qa/qa1480/_index.html)

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

Apparently Apple has declared this to be "BAD" -- Broken As Designed, and they aren't going to fix it.

显然,Apple 已经宣布这是“坏的”——按设计损坏,他们不会修复它。

The circumvention is apparently to set the locale of the date formatter for a specific region, generally the US, but this is a bit messy:

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

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

Not too bad in onsies-twosies, but I'm dealing with about ten different apps, and the first one I look at has 43 instances of this scenario.

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

So any clever ideas for a macro/overridden class/whatever to minimize the effort to change everything, without making the code to obscure? (My first instinct is to override NSDateFormatter with a version that would set the locale in the init method. Requires changing two lines -- the alloc/init line and the added import.)

那么对于宏/覆盖类/任何可以最大限度地减少更改所有内容的工作的聪明想法,而不会使代码变得模糊?(我的第一直觉是用一个可以在 init 方法中设置语言环境的版本覆盖 NSDateFormatter。需要更改两行—— alloc/init 行和添加的导入。)

Added

添加

This is what I've come up with so far -- seems to work in all scenarios:

到目前为止,这就是我想出的——似乎适用于所有场景:

@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

Bounty!

赏金!

I'll award the bounty to the best (legitimate) suggestion/critique I see by mid-day Tuesday. [See below -- deadline extended.]

我将奖励奖励给我在星期二中午之前看到的最佳(合法)建议/批评。[见下文——截止日期已延长。]

Update

更新

Re OMZ's proposal, here is what I'm finding --

关于 OMZ 的提议,这就是我发现的——

Here is the category version -- h file:

这是类别版本 - h 文件:

#import <Foundation/Foundation.h>


@interface NSDateFormatter (Locale)
- (id)initWithSafeLocale;
@end

Category m file:

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

The code:

编码:

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

The result:

结果:

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)

The phone [make that an iPod Touch] is set to Great Britain, with the 12/24 switch set to 12. There's a clear difference in the two results, and I judge the category version to be wrong. Note that the log in the category version IS getting executed (and stops placed in the code are hit), so it's not simply a case of the code somehow not getting used.

手机[make that an iPod Touch]设置为英国,12/24开关设置为12。两个结果有明显差异,我判断类别版本是错误的。请注意,类别版本中的日志正在执行(并命中放置在代码中的停止),因此这不仅仅是代码以某种方式未被使用的情况。

Bounty update:

赏金更新:

Since I haven't gotten any applicable replies yet I'll extend the bounty deadline for another day or two.

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

Bounty ends in 21 hours -- it'll go to whoever makes the most effort to help, even if the answer isn't really useful in my case.

赏金将在 21 小时后结束——它会流向最努力提供帮助的人,即使答案对我来说并不是真的有用。

A curious observation

一个好奇的观察

Modified the category implementation slightly:

稍微修改了类别实现:

#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

Basically just changed the name of the static locale variable (in case there was some conflict with the static declared in the subclass) and added the extra NSLog. But look what that NSLog prints:

基本上只是更改了静态语言环境变量的名称(以防与子类中声明的静态变量存在冲突)并添加了额外的 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

As you can see, the setLocale simply didn't. The locale of the formatter is still en_GB. It appears that there is something "strange" about an init method in a category.

如您所见, setLocale 根本没有。格式化程序的语言环境仍然是 en_GB。类别中的 init 方法似乎有些“奇怪”。

Final answer

最终答案

See the accepted answerbelow.

请参阅下面接受的答案

采纳答案by Hot Licks

Duh!!

呸!!

Sometimes you have an "Aha!!" moment, sometimes it's more of a "Duh!!" This is the latter. In the category for initWithSafeLocalethe "super" initwas coded as self = [super init];. This inits the SUPERCLASS of NSDateFormatterbut does not initthe NSDateFormatterobject itself.

有时你会“啊哈!!” 片刻,有时更像是“Duh!!” 这是后者。在类别中initWithSafeLocale,“超级”init被编码为self = [super init];。这inits的SUPERCLASSNSDateFormatter但不initNSDateFormatter对象本身。

Apparently when this initialization is skipped, setLocale"bounces off", presumably because of some missing data structure in the object. Changing the initto self = [self init];causes the NSDateFormatterinitialization to occur, and setLocaleis happy again.

显然,当这个初始化被跳过时,setLocale“反弹”,大概是因为对象中缺少一些数据结构。更改inittoself = [self init];导致NSDateFormatter初始化发生,并setLocale再次高兴。

Here is the "final" source for the category's .m:

这是该类别 .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

回答by omz

Instead of subclassing, you could create an NSDateFormattercategory with an additional initializer that takes care of assigning the locale and possibly also a format string, so you'd have a ready-to-use formatter right after initializing it.

您可以创建一个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

Then you could use NSDateFormatteranywhere in your code with just:

然后你可以NSDateFormatter在你的代码中的任何地方使用:

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

You might want to prefix your category method somehow to avoid name conflicts, just in case Apple decides to add such a method in a future version of the OS.

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

In case you're always using the same date format(s), you could also add category methods that return singleton instances with certain configurations (something like +sharedRFC3339DateFormatter). Be aware however that NSDateFormatteris not thread-safe and you have to use locks or @synchronizedblocks when you're using the same instance from multiple threads.

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

回答by Daniel

May I suggest something totally different because to be honest all of this is somewhat running down a rabbit hole.

我是否可以提出一些完全不同的建议,因为老实说,所有这些都有些跑题了。

You should be using one NSDateFormatterwith dateFormatset and localeforced to en_US_POSIXfor receiving dates (from servers/APIs).

你应该用一个NSDateFormatterdateFormat套,并locale被迫en_US_POSIX接受日期(从服务器/原料药)。

Then you should be using a different NSDateFormatterfor the UI which you will set the timeStyle/dateStyleproperties - this way you're not having an explicit dateFormatset by yourself, thus falsely assuming that format will be used.

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

This means UI is driven by user preferences (am/pm vs 24 hour, and date strings formatted correctly to user choice - from iOS settings), whereas dates that are "coming into" your app are always being "parsed" correctly to an NSDatefor you to use.

这意味着 UI 是由用户首选项驱动的(上午/下午与 24 小时,以及根据用户选择正确格式化的日期字符串 - 来自 iOS 设置),而“进入”您的应用程序的日期总是被正确“解析”为NSDatefor你用。

回答by Tech

Here is the solution for that problem in the swift version. In swift we can use extension instead of category. So, Here I have created the extension for the DateFormatter and inside that initWithSafeLocale returns the DateFormatter with the relevant Locale, Here in our case that is en_US_POSIX, Apart from that also provided couple of date formation methods.

这是 swift 版本中该问题的解决方案。在 swift 中,我们可以使用扩展而不是类别。所以,在这里我为 DateFormatter 创建了扩展,在 initWithSafeLocale 中返回带有相关区域设置的 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
    }   }
    
  • usage description:

    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))")
    
  • 斯威夫特 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))")