[英]What is the best way to deal with the NSDateFormatter locale "feechur"?
似乎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 )
顯然,Apple 已經宣布這是“糟糕的”——Broken As Design,他們不會修復它。
規避顯然是為特定地區(通常是美國)設置日期格式化程序的語言環境,但這有點混亂:
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
我將在星期二中午之前將賞金獎勵給我看到的最佳(合法)建議/批評。 [見下文——截止日期已延長。]
關於 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 小時內結束——即使答案對我來說並不是真正有用,它也會向盡最大努力提供幫助的人提供 go。
稍微修改了類別實現:
#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 語言環境變量的名稱(以防與子類中聲明的 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 方法有些“奇怪”。
請參閱下面接受的答案。
有時你有一個“啊哈”。 時刻,有時它更像是一個“Duh!!” 這是后者。 在initWithSafeLocale
類別中,“超級” init
被編碼為self = [super init];
. 這會初始化NSDateFormatter
的 SUPERCLASS,但不會init
NSDateFormatter
object 本身。
顯然,當這個初始化被跳過時, setLocale
“反彈”,大概是因為 object 中缺少一些數據結構。 將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
您可以創建一個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 決定在未來版本的操作系統中添加此類方法。
如果您始終使用相同的日期格式,您還可以添加返回 singleton 實例的類別方法,這些實例具有某些配置(類似於+sharedRFC3339DateFormatter
)。 但是請注意, NSDateFormatter
不是線程安全的,當您使用來自多個線程的同一個實例時,您必須使用鎖或@synchronized
塊。
我可以建議一些完全不同的東西,因為老實說,所有這些都有點像兔子洞。
您應該使用一個帶有dateFormat
集的NSDateFormatter
並且強制locale
為en_US_POSIX
來接收日期(來自服務器/API)。
然后,您應該為 UI 使用不同的NSDateFormatter
,您將設置timeStyle
/ dateStyle
屬性 - 這樣您就沒有自己設置明確的dateFormat
,因此錯誤地假設將使用該格式。
這意味着 UI 是由用戶偏好驅動的(am/pm vs 24 小時,日期字符串根據用戶選擇正確格式化 - 來自 iOS 設置),而“進入”您的應用程序的日期總是被正確“解析”為NSDate
供您使用。
這是 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 } }
使用說明:
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))")
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.