วิธีที่ดีที่สุดในการจัดการกับ NSDateFormatter locale“ 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 )

เห็นได้ชัดว่า Apple ประกาศว่า "BAD" ไม่ดีเท่าที่ออกแบบมาและพวกเขาจะไม่แก้ไข

การหลีกเลี่ยงนั้นดูเหมือนจะกำหนดสถานที่ของตัวจัดรูปแบบวันที่สำหรับภูมิภาคเฉพาะซึ่งโดยทั่วไปคือสหรัฐอเมริกา แต่นี่มันค่อนข้างยุ่งเหยิง:

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

ไม่เลวร้ายนักใน onsies-twosies แต่ฉันต้องจัดการกับแอพที่ต่างกันประมาณสิบแอพและแอพแรกที่ฉันดูมี 43 อินสแตนซ์ของสถานการณ์นี้

ดังนั้นความคิดที่ฉลาดสำหรับมาโคร / คลาสที่แทนที่ / อะไรก็ตามที่จะลดความพยายามในการเปลี่ยนแปลงทุกอย่างโดยไม่ทำให้โค้ดคลุมเครือ? (สัญชาตญาณแรกของฉันคือแทนที่ NSDateFormatter ด้วยเวอร์ชันที่จะตั้งค่าโลแคลในเมธอด init ต้องเปลี่ยนสองบรรทัด - บรรทัด 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 ชั่วโมง - มันจะไปหาใครก็ตามที่พยายามอย่างที่สุดที่จะช่วยเหลือแม้ว่าคำตอบจะไม่เป็นประโยชน์ในกรณีของฉัน

การสังเกตอยากรู้อยากเห็น

ปรับใช้หมวดหมู่เล็กน้อย:

#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 ปรากฏว่ามีบางสิ่งที่ "แปลก" เกี่ยวกับวิธีการเริ่มต้นในหมวดหมู่

คำตอบสุดท้าย

ดูคำตอบที่ยอมรับด้านล่าง


5
Moshe ฉันไม่รู้ว่าทำไมคุณเลือกที่จะแก้ไขชื่อ "Feechur" เป็นคำที่ถูกต้องตามกฎหมายในงานศิลปะ (และเป็นเวลา 30 ปีหรือมากกว่านั้น) หมายถึงลักษณะหรือคุณสมบัติของซอฟต์แวร์บางอย่างที่ไม่ดีพอที่จะถือว่าเป็นข้อผิดพลาดแม้ว่าผู้เขียนจะปฏิเสธที่จะยอมรับมัน
Hot Licks

1
เมื่อแปลงสตริงเป็นวันที่สตริงต้องตรงกับคำอธิบายของตัวจัดรูปแบบ - นี่เป็นปัญหาวงในของพื้นที่ของคุณ
bshirley

สตริงวันที่ต่างๆจะมีเพื่อทดสอบการกำหนดค่าต่าง ๆ ที่เป็นไปได้ถูกต้องและผิดพลาด ฉันรู้ว่าบางส่วนของพวกเขาไม่ถูกต้องกำหนดสตริงการจัดรูปแบบ
Hot Licks

คุณเคยทดลองกับค่าที่แตกต่างกัน- (NSDateFormatterBehavior)formatterBehaviorหรือไม่?
bshirley

ยังไม่ได้ทดลองกับมัน ข้อมูลจำเพาะนั้นขัดแย้งกับว่าสามารถเปลี่ยนแปลงได้ใน iOS หรือไม่ คำอธิบายหลักบอกว่า "iOS หมายเหตุ: iOS รองรับเฉพาะพฤติกรรม 10.4+" ในขณะที่ส่วน NSDateFormatterBehavior กล่าวว่าทั้งสองโหมดพร้อมใช้งาน (แต่อาจพูดถึงค่าคงที่เท่านั้น)
Hot Licks

คำตอบ:


67

ดุจ !!

บางครั้งคุณมี "Aha !!" ขณะนี้บางครั้งมันเป็น "Duh !!" นี่คือหลัง ในหมวดหมู่สำหรับinitWithSafeLocale"ซูเปอร์" ถูกกำหนดเป็นinit self = [super init];นี่เป็นการเริ่มต้นของ SUPERCLASS NSDateFormatterแต่ไม่ได้initเป็นNSDateFormatterวัตถุ

เห็นได้ชัดว่าเมื่อการเริ่มต้นนี้ถูกข้าม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.000ZZ ";" ?
ตัวแทน Chocks

@Agent - เงยหน้าขึ้นมอง: unicode.org/reports/tr35/tr35-31/…
Hot Licks

@tbag - คำถามของคุณไม่ควรเกี่ยวกับ NSDateFormatter ใช่ไหม
Hot Licks

@ HotLicks ใช่ฉันไม่ดี ฉันกิน NSDateFormatter
tbag

@tbag - สเป็คพูดอะไร?
Hot Licks

41

แทนที่จะเป็นคลาสย่อยคุณสามารถสร้างNSDateFormatterหมวดหมู่ด้วย initializer เพิ่มเติมที่ดูแลการกำหนดโลแคลและอาจเป็นสตริงรูปแบบดังนั้นคุณจึงมีฟอร์แมตพร้อมใช้งานหลังจากเริ่มต้นแล้ว

@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 แบบคงที่ (เหมือนในคำแนะนำของฉัน) จะทำงานในหมวดหมู่หรือไม่
Hot Licks

ใช่ว่าควรทำงานในหมวดหมู่ ฉันทิ้งมันไว้เพื่อทำให้ตัวอย่างง่ายขึ้น
omz

อยากรู้อยากเห็นวิธีการหมวดหมู่ไม่ทำงาน วิธีการหมวดหมู่จะถูกดำเนินการและมันจะได้รับสถานที่เดียวกันกับรุ่นอื่น ๆ (ฉันดำเนินการกลับไปด้านหลังรุ่นหมวดหมู่แรก) เห็นได้ชัดว่า setLocale ไม่ได้ "รับ"
Hot Licks

มันจะน่าสนใจที่จะหาว่าทำไมวิธีนี้ใช้ไม่ได้ หากไม่มีใครมาพร้อมกับสิ่งที่ดีกว่าฉันจะมอบรางวัลให้กับคำอธิบายที่ดีที่สุดของข้อผิดพลาดที่เห็นได้ชัดนี้
Hot Licks

ฉันกำลังมอบรางวัลให้ OMZ เพราะเขาเป็นคนเดียวที่พยายามทำสิ่งนี้อย่างชัดเจน
Hot Licks

7

ฉันขอแนะนำบางสิ่งที่แตกต่างอย่างสิ้นเชิงเพราะโดยความจริงแล้วทั้งหมดนี้ค่อนข้างจะวิ่งลงไปในโพรงกระต่าย

คุณควรจะใช้อย่างใดอย่างหนึ่งNSDateFormatterกับdateFormatชุดและlocaleบังคับให้en_US_POSIXรับวัน (จากเซิร์ฟเวอร์ / APIs)

จากนั้นคุณควรจะใช้NSDateFormatterUI ที่แตกต่างกันซึ่งคุณจะตั้งค่าtimeStyle/ dateStyleคุณสมบัติ - วิธีนี้คุณจะไม่ได้dateFormatตั้งค่าอย่างชัดเจนด้วยตัวเองดังนั้นจึงสมมติว่ารูปแบบนั้นจะถูกใช้อย่างไม่เหมาะสม

ซึ่งหมายความว่า UI คือการขับเคลื่อนด้วยการตั้งค่าของผู้ใช้ (AM / PM VS 24 ชั่วโมงและสตริงวันรูปแบบถูกต้องในการเลือกของผู้ใช้ - จากการตั้งค่า iOS) ในขณะที่วันที่ "เข้ามาใน" แอปของคุณมักจะเป็น "แยกวิเคราะห์" อย่างถูกต้องไปยังNSDateสำหรับ ให้คุณใช้


บางครั้งรูปแบบนี้ใช้งานได้บางครั้งไม่ สิ่งหนึ่งที่อันตรายคือวิธีการของคุณอาจจำเป็นต้องปรับเปลี่ยนรูปแบบวันที่ของตัวจัดรูปแบบและในการทำเช่นนั้นให้เปลี่ยนรูปแบบที่กำหนดโดยรหัสที่เรียกว่าคุณเมื่ออยู่ระหว่างการดำเนินการจัดรูปแบบวันที่ มีสถานการณ์อื่น ๆ ที่ต้องเปลี่ยนเขตเวลาซ้ำ ๆ
Licks ร้อน

ฉันไม่รู้ว่าทำไมการเปลี่ยนtimeZoneค่าของตัวจัดรูปแบบจึงเป็นไปในรูปแบบของโครงการนี้คุณอธิบายได้ไหม? นอกจากนี้ยังชัดเจนว่าคุณจะละเว้นจากการเปลี่ยนรูปแบบ หากคุณต้องการทำเช่นนี้สิ่งนี้จะเกิดขึ้นในตัวจัดรูปแบบ "นำเข้า" ดังนั้นตัวจัดรูปแบบแยกต่างหาก
Daniel

เมื่อใดก็ตามที่คุณกำลังเปลี่ยนสถานะของวัตถุทั่วโลกมันอันตราย ง่ายต่อการลืมว่าคนอื่นก็ใช้งานเช่นกัน
เลียน่าสนใจ

3

นี่คือวิธีแก้ปัญหาสำหรับปัญหาในรุ่นที่รวดเร็ว อย่างรวดเร็วเราสามารถใช้ส่วนขยายแทนหมวดหมู่ ดังนั้นที่นี่ฉันได้สร้างส่วนขยายสำหรับ DateFormatter และภายใน initWithSafeLocale ส่งคืน DateFormatter ด้วย Locale ที่เกี่ยวข้องที่นี่ในกรณีของเราคือ 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))")
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.