web-dev-qa-db-ja.com

NSDateFormatterロケール「feechur」に対処する最良の方法は何ですか?

NSDateFormatterには予期せぬ「機能」があるようです:次のような単純な「固定」フォーマット操作を行う場合:

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

その後、米国およびほとんどのロケールでUNTILが正常に機能します。24時間の地域に設定された電話を持つユーザーは、設定の12/24時間切り替えを12に設定します。結果の文字列の終わり。

(たとえば、 NSDateFormatterを参照してください。何か間違っているのですか、それともバグですか?

(そして https://developer.Apple.com/library/content/qa/qa1480/_index.html を参照)

どうやらAppleはこれを「悪い」と宣言しています-Broken As Designedであり、修正するつもりはありません。

回避策は明らかに、特定の地域、一般的には米国の日付フォーマッターのロケールを設定することですが、これは少し厄介です:

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

おっと二人でそれほど悪くはありませんが、私は約10種類のアプリを扱っており、最初に見たアプリにはこのシナリオのインスタンスが43個あります。

だから、マクロ/オーバーライドされたクラス/何でも、コードを不明瞭にすることなく、すべてを変更する労力を最小限にするための賢いアイデアはありますか? (私の最初の本能は、NSDateFormatterをinitメソッドでロケールを設定するバージョンでオーバーライドすることです。alloc/ init行と追加されたインポートの2行を変更する必要があります。)

追加しました

これは私がこれまでに思いついたものです-すべてのシナリオで動作するようです:

@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に設定されています。2つの結果に明確な違いがあり、カテゴリバージョンが間違っていると判断します。カテゴリバージョンISのログが実行される(およびコードに配置されたストップがヒットする)ので、コードが何らかの理由で使用されないというだけではないことに注意してください。

バウンティアップデート:

私はまだ適切な返信を受け取っていないので、賞金期限をあと1日または2日延長します。

バウンティは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です。カテゴリ内の初期化メソッドについて何か「奇妙な」ことがあるようです。

最終回答

以下の 受け入れられた答え を参照してください。

163
Hot Licks

ああ!

時々「あは!!」瞬間、時には「ダウ!!」これは後者です。 initWithSafeLocaleのカテゴリでは、 "super" initself = [super init];としてコーディングされていました。これはNSDateFormatterのスーパークラスを初期化しますが、initNSDateFormatterオブジェクト自体は初期化しません。

おそらく、この初期化がスキップされると、おそらくオブジェクトのデータ構造が欠落しているために、setLocaleが「跳ね返ります」。 initself = [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
63
Hot Licks

サブクラス化する代わりに、ロケールの割り当てを処理する初期化子を追加する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がOSの将来のバージョンでこのようなメソッドを追加することを決定した場合に備えて、名前の競合を避けるために何らかの方法でカテゴリメソッドにプレフィックスを付けることができます。

常に同じ日付形式を使用している場合、特定の構成(+sharedRFC3339DateFormatter)。ただし、NSDateFormatterはスレッドセーフではないため、ロックまたは@synchronizedは、複数のスレッドから同じインスタンスを使用している場合にブロックします。

38
omz

正直に言うと、これはすべてウサギの穴を駆け抜けているからです。

NSDateFormatterを1つ使用してdateFormatを設定し、localeen_US_POSIXに強制して(サーバー/ APIから)日付を受信する必要があります。

次に、NSDateFormatter/timeStyleプロパティを設定するUIに別のdateStyleを使用する必要があります。この方法では、明示的にdateFormatを自分で設定する必要がないため、その形式が使用されると誤って仮定します。

つまり、UIはユーザー設定(午前/午後vs 24時間、およびユーザー設定に合わせて正しくフォーマットされた日付文字列-iOS設定から)によって駆動されますが、アプリに「入ってくる」日付は常にNSDateに正しく「解析」されますあなたが使用するため。

6
Daniel

Swiftバージョンでのその問題の解決策を次に示します。 Swiftでは、カテゴリの代わりに拡張子を使用できます。そのため、ここではDateFormatterの拡張機能を作成し、その中にinitWithSafeLocaleが関連するロケールでDateFormatterを返すようにします。

  • スイフト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))")
    
3
Tech