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


168

それはNSDateFormatter予期せずあなたに噛み付く「機能」を持っているようです:あなたが次のような単純な「固定された」フォーマット操作を行う場合:

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

その後、米国およびほとんどのロケールで正常に動作します...電話が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];

onsies-twosiesではそれほど悪くありませんが、私は約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つの結果には明らかな違いがあり、カテゴリバージョンは間違っていると判断します。カテゴリバージョンのログが実行される(そしてコードに配置されたストップがヒットする)ことに注意してください。そのため、コードが何らかの理由で使用されないだけではありません。

バウンティの更新:

該当する返信がまだないので、賞金の締め切りをもう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

基本的に、静的ロケール変数の名前を変更し(サブクラスで宣言された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年ほど前から存在)、作者がそれを認めることを拒否しても、バグと見なされるほど十分に誤解されている一部のソフトウェアの側面または機能を意味します。
Hot Licks

1
文字列を日付に変換する場合、文字列はフォーマッタの説明と正確に一致している必要があります。これは、地域の問題の正接問題です。
bshirley

さまざまな可能性のある構成をテストするために、さまざまな日付文字列があります。書式文字列を考えると、一部が無効であることはわかっています。
Hot Licks

さまざまな値を試してみました- (NSDateFormatterBehavior)formatterBehaviorか?
bshirley

実験していない。この仕様は、iOSでも変更できるかどうかについては矛盾しています。主な説明には「iOS Note:iOSは10.4+の動作のみをサポートする」とありますが、NSDateFormatterBehaviorセクションには両方のモードが利用可能であると記載されています(ただし、定数についてのみ説明している場合があります)。
ホットリックス

回答:


67

ああ!

時々「あはは!」瞬間、時々それはもっと「Duh !!」これは後者です。initWithSafeLocale「スーパー」のカテゴリでは、initとしてコーディングされていましたself = [super init];。これは、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についてではありませんか?
Hot Licks

@HotLicksはい、私の悪いです。NSDateFormatterを使います。
tbag 2016年

@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が将来のバージョンのOSでそのようなメソッドを追加することを決定した場合に備えて、名前の競合を回避するために、何らかの方法でカテゴリメソッドにプレフィックスを付けることができます。

常に同じ日付形式を使用している場合は、特定の構成(など+sharedRFC3339DateFormatter)のシングルトンインスタンスを返すカテゴリメソッドを追加することもできます。ただし、これNSDateFormatterはスレッドセーフではなく@synchronized、複数のスレッドから同じインスタンスを使用している場合はロックまたはブロックを使用する必要があることに注意してください。


静的なNSLocale(私の提案のように)はカテゴリで機能しますか?
Hot Licks

はい、それはカテゴリでも機能するはずです。例を簡単にするために省略しました。
omz

奇妙なことに、カテゴリーアプローチは機能しません。カテゴリメソッドが実行され、他のバージョンとまったく同じロケールが取得されます(最初にカテゴリバージョンを実行します)。どういうわけか、setLocaleはどうやら「受け取らない」ようです。
Hot Licks

このアプローチが機能しない理由を見つけることは興味深いでしょう。誰もより良い何かを思い付かない場合、私はこの明らかなバグの最も良い説明に賞金を授与します。
ホットリックス

まあ、私はOMZに賞金を授与しています。これは彼がこれに明らかに努力した唯一の人だからです。
Hot Licks

7

正直に言うと、これらすべてがややウサギの穴を駆け抜けているので、私はまったく違う何かを提案できますか?

(サーバー/ APIからの)日付の受信にはNSDateFormatterdateFormatsetおよびlocaleforceを使用する必要がありen_US_POSIXます。

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

あなたのアプリ「に入ってくる」されている日付は常にに正しく「解析さ」されているのに対し、 -この手段は、UIはユーザー設定(iOSの設定から24時間対AM / PM、および日付文字列ユーザーの選択に正しくフォーマット)によって駆動されるNSDateため使用します。


このスキームが機能する場合と機能しない場合があります。1つの危険性は、メソッドがフォーマッターの日付形式を変更し、その際に、日付のフォーマット操作の最中に、呼び出し元のコードによって設定された形式を変更する必要があることです。タイムゾーンを繰り返し変更する必要があるシナリオは他にもあります。
Hot Licks、2015年

timeZoneフォーマッタの値を変更すると、このスキームの邪魔になる理由がわかりません。詳しく説明してください。また、明確にするために、フォーマットの変更を避けます。そうする必要がある場合、これは「インポート」フォーマッター、つまり別のフォーマッターで発生します。
Daniel

グローバルオブジェクトの状態を変更するときはいつでも危険です。他の人もそれを使用していることを忘れがちです。
Hot Licks、2015年

3

以下は、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))")
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.