簡體   English   中英

處理 NSDateFormatter 語言環境“feechur”的最佳方法是什么?

[英]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並且強制localeen_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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM