NSDateFormatter लोकेल "फीसचूर" से निपटने का सबसे अच्छा तरीका क्या है?


168

ऐसा लगता है कि NSDateFormatterएक "सुविधा" है जो आपको अप्रत्याशित रूप से काटती है: यदि आप एक सरल "निश्चित" प्रारूप संचालन करते हैं जैसे:

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

फिर यह यूएस और अधिकांश स्थानों में ठीक काम करता है UNTIL ... किसी ने अपने फोन को 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];

ऑनसाइट-ट्वॉइस में बहुत बुरा नहीं है, लेकिन मैं लगभग दस अलग-अलग ऐप के साथ काम कर रहा हूं, और मैं इस परिदृश्य के 43 उदाहरणों को देखता हूं।

मैक्रो / ओवरराइड क्लास के लिए कोई भी चतुर विचार / जो कुछ भी करने के लिए कोड को अस्पष्ट बनाने के बिना, सब कुछ बदलने के प्रयास को कम करने के लिए? (मेरी पहली वृत्ति NSDateFormatter को एक ऐसे संस्करण के साथ ओवरराइड करने के लिए है जो init विधि में स्थान निर्धारित करेगा। दो लाइनों को बदलने की आवश्यकता है - आवंटन / 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

बाउंटी!

मैं सबसे अच्छे (वैध) सुझाव / समालोचना को पुरस्कार देता हूँ जिसे मैं मंगलवार के मध्य तक देखता हूँ। [नीचे देखें - समय सीमा बढ़ाई गई।]

अपडेट करें

ओएमजेड का प्रस्ताव फिर, यहां वही है जो मैं पा रहा हूं -

यहाँ श्रेणी संस्करण है - h फ़ाइल:

#import <Foundation/Foundation.h>


@interface NSDateFormatter (Locale)
- (id)initWithSafeLocale;
@end

श्रेणी एम फ़ाइल:

#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 टच] 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 है। ऐसा प्रतीत होता है कि एक श्रेणी में एक init विधि के बारे में कुछ "अजीब" है।

अंतिम जवाब

देखें स्वीकार किए जाते हैं जवाब नीचे।


5
मोशे, मुझे नहीं पता कि आपने शीर्षक का संपादन क्यों चुना। "फिकुर" कला में एक वैध शब्द है (और 30 साल या तो के लिए किया गया था), जिसका अर्थ है कि कुछ सॉफ़्टवेयर का एक पहलू या विशेषता जो बग को माना जाने के लिए पर्याप्त रूप से बीमार माना जाता है, भले ही लेखक इसे स्वीकार करने से इनकार कर दें।
हॉट लीक्स

1
स्ट्रिंग को तिथि में परिवर्तित करते समय, स्ट्रिंग को फ़ॉर्मेटर विवरण से बिल्कुल मेल खाना चाहिए - यह आपके इलाके के लिए एक स्पर्शरेखा मुद्दा है।
बिशर्ले

विभिन्न तारीखों के तार अलग-अलग संभव कॉन्फ़िगरेशन, सही और गलत का परीक्षण करने के लिए हैं। मुझे पता है कि उनमें से कुछ अमान्य हैं, स्वरूपण स्ट्रिंग को देखते हुए।
हॉट लिक्स

क्या आपने विभिन्न मूल्यों के साथ प्रयोग किया है - (NSDateFormatterBehavior)formatterBehavior?
बिशर्ले जूल 12'11

इसके साथ प्रयोग नहीं किया गया। युक्ति इस पर विरोधाभासी है कि क्या इसे iOS में भी बदला जा सकता है। मुख्य विवरण "iOS नोट: iOS केवल 10.4+ व्यवहार" का समर्थन करता है, जबकि NSDateFormatterBehavior अनुभाग कहता है कि दोनों मोड उपलब्ध हैं (लेकिन यह केवल स्थिरांक के बारे में बात कर सकता है)।
हॉट लिक्स

जवाबों:


67

ओह !!

कभी-कभी आपके पास "अहा !!" पल, कभी-कभी यह एक "डुह !!" यह बाद की बात है। initWithSafeLocale"सुपर" के लिए श्रेणी initमें कोडित किया गया था self = [super init];। इस के सुपर क्लास inits NSDateFormatterलेकिन ऐसा नहीं करता वस्तु ही।initNSDateFormatter

जाहिरा तौर पर जब इस इनिशियलाइज़ेशन को छोड़ दिया जाता है, तो setLocale"बाउंस ऑफ" होता है, संभवतः ऑब्जेक्ट में कुछ गायब डेटा संरचना के कारण। बदलने initके लिए self = [self init];कारण बनता है NSDateFormatterप्रारंभ होने के लिये, और setLocaleफिर से खुश है।

यहाँ श्रेणी के लिए "अंतिम" स्रोत है।

#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 "के लिए दिनांक फ़ॉर्मेटर क्या होगा?" ?
एजेंट चक।

@Agent - इसे देखें: unicode.org/reports/tr35/tr35-31/…
हॉट

@tbag - क्या आपका प्रश्न NSDateFormatter के बारे में नहीं होना चाहिए?
हॉट लीक्स

@HotLicks हाँ मेरा बुरा। मैं NSDateFormatter मांस।
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 ओएस के भविष्य के संस्करण में इस तरह की विधि को जोड़ने का फैसला करता है।

यदि आप हमेशा एक ही तिथि प्रारूप (एस) का उपयोग कर रहे हैं, तो आप श्रेणी के तरीकों को भी जोड़ सकते हैं जो कुछ कॉन्फ़िगरेशन (कुछ इस तरह +sharedRFC3339DateFormatter) के साथ सिंगलटन इंस्टेंस लौटाते हैं । हालांकि इस बात NSDateFormatterसे अवगत रहें कि यह थ्रेड-सुरक्षित नहीं है और आपको @synchronizedकई थ्रेड्स से एक ही उदाहरण का उपयोग करते समय ताले या ब्लॉक का उपयोग करना होगा।


क्या एक श्रेणी में स्थिर NSLocale (मेरे सुझाव की तरह) काम करेगा?
हॉट लिप्स

हाँ, यह भी एक श्रेणी में काम करना चाहिए। मैंने इसे सरल बनाने के लिए छोड़ दिया।
ओम

उत्सुकता से, श्रेणी दृष्टिकोण काम नहीं करता है। श्रेणी विधि निष्पादित की जाती है, और यह अन्य संस्करण के समान सटीक लोकेल प्राप्त कर रहा है (मैं उन्हें बैक टू बैक, श्रेणी संस्करण पहले निष्पादित करता हूं)। बस किसी भी तरह setLocale जाहिरा तौर पर "ले" नहीं करता है।
हॉट लिप्स

यह पता लगाना दिलचस्प होगा कि यह दृष्टिकोण क्यों काम नहीं करता है। अगर कोई भी कुछ बेहतर नहीं करता है, तो मैं इस स्पष्ट बग के सर्वोत्तम विवरण के लिए इनाम दूंगा।
हॉट लिक्स

ठीक है, मैं इनाम को OMZ को दे रहा हूं, क्योंकि वह एकमात्र ऐसा है जिसने इस पर स्पष्ट प्रयास किया है।
हॉट लक्स

7

हो सकता है कि मैं पूरी तरह से कुछ अलग करने का सुझाव दूं क्योंकि ईमानदार होना यह सब कुछ खरगोश के छेद के नीचे चल रहा है।

आपको सेट्स (सर्वर / एपीआई से) प्राप्त करने के लिए सेट के NSDateFormatterसाथ एक का उपयोग करना चाहिए ।dateFormatlocaleen_US_POSIX

तब आपको NSDateFormatterUI के लिए एक भिन्न का उपयोग करना चाहिए जिसे आप timeStyle/ dateStyleगुण सेट करेंगे - इस तरह से आप dateFormatअपने आप से एक स्पष्ट सेट नहीं कर रहे हैं , इस प्रकार गलत तरीके से उस प्रारूप का उपयोग किया जाएगा।

इसका मतलब है कि यूआई उपयोगकर्ता की प्राथमिकताओं से प्रेरित है (am / pm बनाम 24 घंटे, और तारीख स्ट्रिंग को उपयोगकर्ता की पसंद - iOS सेटिंग से सही रूप में स्वरूपित किया गया है), जबकि तारीखें जो "आपके ऐप में" हमेशा "पार्स" हो रही हैं, उनके NSDateलिए सही तरीके से आप का उपयोग करने के लिए।


कभी-कभी यह योजना काम करती है, कभी-कभी नहीं। एक खतरा यह है कि आपकी विधि को फ़ॉर्मेटर के दिनांक प्रारूप को संशोधित करने की आवश्यकता हो सकती है और ऐसा करने पर, आपके द्वारा कहे जाने वाले कोड द्वारा निर्धारित प्रारूप को बदल दें, जब यह दिनांक स्वरूपण कार्य के बीच में था। ऐसे अन्य परिदृश्य हैं जहां समय-सीमा को बार-बार बदलना चाहिए।
हॉट लिप्स

मुझे नहीं पता कि timeZoneइस योजना के रूप में फॉर्मेटर के बदलते मूल्य को क्यों मिलेगा, क्या आप विस्तृत कर सकते हैं? यह भी स्पष्ट है कि आप प्रारूप को बदलने से परहेज करेंगे। यदि आपको ऐसा करने की आवश्यकता है, तो यह "आयात" फ़ॉर्मेटर पर होगा, इसलिए एक अलग फॉर्मेटर।
डैनियल

किसी भी समय आप एक वैश्विक वस्तु की स्थिति को बदल रहे हैं यह खतरनाक है। यह भूलना आसान है कि दूसरे भी इसका उपयोग कर रहे हैं।
हॉट लिक्स

3

यहां स्विफ्ट संस्करण में उस समस्या का समाधान है। स्विफ्ट में हम श्रेणी के बजाय एक्सटेंशन का उपयोग कर सकते हैं। इसलिए, यहाँ मैंने DateFormatter के लिए एक्सटेंशन बनाया है और उस initWithSafeLocale में संबंधित लोकेल के साथ DateFormatter को लौटाया है, यहाँ हमारे मामले में जो 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))")
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.