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


168

看起来 NSDateFormatter有一个“功能”意外地咬了你:如果执行简单的“固定”格式操作,例如:

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

然后在美国和大多数语言环境中都可以正常工作...某人将手机设置为24小时制,则将12/24小时切换设置设为12。然后,上面的操作将“ AM”或“ PM”添加到结果字符串的末尾。

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

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

显然,苹果公司宣布它为“ BAD”-按设计要求被破坏,他们将不予修复。

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

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

onsies-twosies还不错,但是我正在处理大约十种不同的应用程序,而我所研究的第一个应用程序有43种这种情况。

那么,对于宏/重写的类/如何在不使代码晦涩的情况下最大程度地减少更改所有工作的任何巧妙构想?(我的第一个本能是使用将在init方法中设置语言环境的版本覆盖NSDateFormatter。需要更改两行-分配/初始化行和添加的导入。)

添加

到目前为止,这是我要提出的内容-似乎适用于所有情况:

@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

赏金!

我会在星期二中午之前将赏金授予我所见的最佳(正当)建议/批评。[见下文-截止日期已延长。]

更新资料

关于OMZ的建议,这就是我所发现的-

这是类别版本-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。这两个结果有明显的区别,我认为类别版本是错误的。请注意,类别版本中的日志正在执行(并且命中了停在代码中的停顿),因此,这不只是某种程度上由于某种原因而没有被使用的代码。

赏金更新:

由于我尚未收到任何适用的回复,因此我将赏金截止日期再延长一两天。

赏金会在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

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

最终答案

请参阅下面的可接受答案


5
Moshe,我不知道您为什么选择编辑标题。“ Feechur”是本领域中的合法术语(已经使用了30年左右),表示某些软件的某个方面或功能,即使作者拒绝承认,也被认为是错误,因此被认为是错误。
热门点击

1
将字符串转换为日期时,字符串必须与格式器描述完全匹配-这是与您所在地区有关的切线问题。
bshirley

各种日期字符串可用来测试正确和错误的不同可能配置。我知道,鉴于格式设置字符串,其中一些无效。
热门点击

您是否尝试过不同的值- (NSDateFormatterBehavior)formatterBehavior
bshirley

还没有尝试过。该规范是否可以在iOS中进行更改是矛盾的。主要描述为“ iOS注意:iOS仅支持10.4+行为”,而NSDateFormatterBehavior部分指出这两种模式均可用(但可能只在讨论常量)。
热门点击

Answers:


67

h!

有时您会听到“啊哈!” 此刻,有时更像是““!” 这是后者。在initWithSafeLocale“超级” 类别中,init编码为self = [super init];。这inits的SUPERCLASS NSDateFormatter但不initNSDateFormatter对象本身。

显然,跳过此初始化时,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

“ NSString * dateStr = @” 2014-04-05T04:00:00.000Z“的日期格式是什么;” ?
座席座席。


@tbag-您的问题不应该与NSDateFormatter有关吗?
热门点击

@HotLicks是的,我不好。我吃了NSDateFormatter。
tbag

@tbag-规格说明了什么?
热门点击

41

除了子类化之外,您还可以创建一个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在多个线程中使用同一实例时,必须使用锁或块。


是否可以在类别中使用静态的NSLocale(如我的建议)?
热门点击

是的,这也应该适用于类别。我省略了它,以使示例更简单。
omz

奇怪的是,类别方法不起作用。category方法已执行,并且得到的语言环境与其他版本完全相同(我先后执行它们,首先是类别版本)。只是以某种方式setLocale显然不会“占用”。
热门点击

找出为什么这种方法行不通将很有趣。如果没有人提出更好的建议,我将悬赏以最好的方式解释这个明显的错误。
热门点击

好吧,我将赏金授予OMZ,因为他是唯一为此做出明显努力的人。
热门点击

7

我是否可以提出完全不同的建议,因为老实说,所有这些都在一个兔子洞中流淌。

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

然后,您应该NSDateFormatter为UI设置timeStyle/ dateStyle属性使用其他设置-这样,您就不会自己进行显式dateFormat设置,因此错误地假定将使用该格式。

这意味着UI由用户偏好设置驱动(上午/下午与24小时相对应,日期字符串的格式正确设置为用户的选择-来自iOS设置),而“进入”应用程序的日期始终会被正确“解析”为NSDatefor你用。


有时此方案有效,有时无效。一种危险是,您的方法可能需要修改格式化程序的日期格式,并在进行日期格式化操作时更改由调用您的代码设置的格式。在其他情况下,必须重复更改时区。
热门点击

我不知道为什么更改timeZone格式化程序的值会妨碍此方案,您能否详细说明?同样要明确的是,您将放弃更改格式。如果需要这样做,则将在“导入”格式化程序上发生,因此将在单独的格式化程序上发生。
丹尼尔(Daniel)

任何时候更改全局对象的状态都是很危险的。容易忘记其他人也在使用它。
热门点击

3

这是快速版本中针对该问题的解决方案。我们可以迅速使用扩展名而不是类别。因此,在这里我创建了DateFormatter的扩展名,并且initWithSafeLocale返回带有相关语言环境的DateFormatter,在我们的例子中是en_US_POSIX,此外还提供了几种日期形成方法。

  • 斯威夫特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))")
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.