NSDateFormatter yerel ayarı “feechur” ile başa çıkmanın en iyi yolu nedir?


168

NSDateFormatterSana beklenmedik bir şekilde ısırır bir "özelliği" var gibi görünüyor : Eğer gibi basit bir "sabit" format işlemi yaparsanız:

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

Daha sonra ABD'de iyi çalışır ve çoğu yerel KADAR kadar ... telefonlarını 24 saatlik bir bölgeye ayarlayan biri 12/24 saat anahtarını ayarlarda 12'ye ayarlar. Sonra yukarıdaki "AM" veya "PM" sonuçta elde edilen dizenin sonu.

(Bkz. Örneğin NSDateFormatter, yanlış bir şey mi yapıyorum yoksa bu bir hata mı? )

(Ve bkz. Https://developer.apple.com/library/content/qa/qa1480/_index.html )

Görünüşe göre Apple bunu "KÖTÜ" olarak ilan etti - Tasarlandığı Gibi Kırıldı ve düzeltmeyecekler.

Atlatma, belirli bir bölge, genellikle ABD için tarih biçimlendiricisinin yerel ayarını yapmaktır, ancak bu biraz dağınıktır:

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

Onsies-twosies çok kötü değil, ama yaklaşık on farklı uygulama ile ilgileniyorum, ve ilk baktığım bu senaryo 43 örneği vardır.

Yani bir makro / geçersiz kılınan sınıf / her şeyi değiştirme çabasını en aza indirecek herhangi bir zekice fikir, kodu belirsiz hale getirmeden? (İlk içgüdüm, init yönteminde yerel ayarı ayarlayacak bir sürümle NSDateFormatter'ı geçersiz kılmaktır. İki satırın (ayırma / init satırı ve eklenen içe aktarma) değiştirilmesini gerektirir.)

Katma

Şimdiye kadar bulduğum şey bu - tüm senaryolarda çalışıyor gibi görünüyor:

@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

Ödül!

Ödülünü Salı gün ortası itibariyle gördüğüm en iyi (meşru) öneri / eleştiriye vereceğim. [Aşağıya bakın - süre uzatıldı.]

Güncelleme

Re OMZ'nin teklifi, işte bulduğum şey -

Kategori sürümü - h dosyası:

#import <Foundation/Foundation.h>


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

Kategori m dosyası:

#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

Kod:

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];

Sonuç:

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)

Telefon, bir iPod Touch olmasını sağlayın, 12/24 anahtarı 12'ye ayarlanmış olarak İngiltere'ye ayarlandı. İki sonuçta net bir fark var ve kategori versiyonunun yanlış olduğuna karar veriyorum. Kategori sürümündeki günlüğün yürütüldüğünü (ve koda yerleştirilen durakların vurulduğunu) unutmayın, bu nedenle kodun bir şekilde kullanılmadığı bir durum değildir.

Ödül güncellemesi:

Henüz geçerli bir yanıt alamadığım için ödül tarihini bir iki gün daha uzatacağım.

Ödül 21 saat içinde sona erer - cevabım benim durumumda gerçekten yararlı olmasa bile, yardım etmek için en fazla çaba harcayan kişiye gider.

Meraklı bir gözlem

Kategori uygulamasında biraz değişiklik yapıldı:

#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

Temel olarak, statik yerel ayar değişkeninin adını değiştirdi (alt sınıfta bildirilen statikle bazı çakışma olması durumunda) ve fazladan NSLog ekledi. Ancak NSLog'un ne yazdırdığına bakın:

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

Gördüğünüz gibi, setLocale basitçe yapmadı. Formatlayıcının yerel ayarı hala en_GB. Bir kategorideki init yöntemi hakkında "garip" bir şey olduğu anlaşılıyor.

Son cevap

Bkz kabul cevabı aşağıda.


5
Moshe, başlığı neden değiştirmeyi seçtiğini bilmiyorum. "Feechur" teknikte meşru bir terimdir (ve 30 yıldan beri olmuştu), bu da yazarların kabul etmeyi reddetmesine rağmen, yeterince hata olarak algılanan bazı yazılımların bir yönü veya özelliği anlamına gelir.
Hot Licks

1
bir dizeyi tarihe dönüştürürken, dizenin biçimlendirici açıklamasıyla tam olarak eşleşmesi gerekir - bu, bulunduğunuz bölgeye teğet bir sorundur.
bshirley

Çeşitli tarih dizeleri, doğru ve hatalı farklı olası yapılandırmaları test etmek için vardır. Biçimlendirme dizesi verildi, bazıları geçersiz olduğunu biliyorum.
Hot Licks

farklı değerleri denediniz - (NSDateFormatterBehavior)formatterBehaviormi?
bshirley

Bunu denemedim. Spesifikasyon, iOS'ta değiştirilip değiştirilemeyeceği konusunda çelişkilidir. Ana açıklamada "iOS Not: iOS yalnızca 10.4+ davranışı destekliyor" yazarken, NSDateFormatterBehavior bölümü her iki modun da kullanılabilir olduğunu söylüyor (ancak yalnızca sabitler hakkında konuşuyor olabilir).
Hot Licks

Yanıtlar:


67

Duh !!

Bazen bir "Aha !!" an, bazen daha çok "Duh !!" Bu sonuncusu. Kategorisinde initWithSafeLocale"süper" initolarak kodlandı self = [super init];. Bu üst sınıf inits NSDateFormatterama değil nesne kendisi.initNSDateFormatter

Görünüşe göre bu başlatma atlandığında, setLocalemuhtemelen nesnede bazı eksik veri yapısı nedeniyle "sıçrar". Değişen initiçin self = [self init];nedenler NSDateFormatteroluşmaya başlatma ve setLocaletekrar mutlu.

İşte kategorinin .m için "son" kaynağı:

#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 "için tarih biçimlendiricisi ne olacak;" ?
Ajan Takozları.


@tbag - Sorunuz NSDateFormatter ile ilgili olmamalı mı?
Sıcak Yalıyor

@HotLicks evet benim hatam. Ben NSDateFormatter et.
tbag

@tbag - Spesifikasyon ne diyor?
Hot Licks

41

Alt sınıflandırma yerine NSDateFormatter, yerel ayar ve muhtemelen bir biçim dizesi atamaya özen gösteren ek bir başlatıcı ile bir kategori oluşturabilirsiniz , böylece başlattıktan hemen sonra kullanıma hazır bir biçimlendiriciniz olur.

@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

Daha sonra NSDateFormatterkodunuzda herhangi bir yerde kullanabilirsiniz :

NSDateFormatter* fmt = [[NSDateFormatter alloc] initWithPOSIXLocaleAndFormat:@"yyyyMMddHHmmss"];

Apple'ın işletim sisteminin gelecekteki bir sürümüne böyle bir yöntem eklemeye karar vermesi durumunda, ad çakışmalarından kaçınmak için bir şekilde kategori yönteminize önek eklemek isteyebilirsiniz.

Her zaman aynı tarih biçimlerini kullanıyorsanız, belirli yapılandırmalara sahip tekton örneklerini (benzer bir şey +sharedRFC3339DateFormatter) döndüren kategori yöntemleri de ekleyebilirsiniz . Ancak bunun NSDateFormatteriş parçacığı için güvenli olmadığını @synchronizedve aynı örneği birden çok iş parçacığından kullanırken kilitleri veya blokları kullanmanız gerektiğini unutmayın.


Statik bir NSLocale (önerimde olduğu gibi) olması bir kategoride çalışır mı?
Hot Licks

Evet, bu bir kategoride de çalışmalı. Örneği kolaylaştırmak için bunu bıraktım.
omz

İlginçtir ki, kategori yaklaşımı işe yaramıyor. Kategori yöntemi yürütülür ve diğer sürümle aynı Yerel Ayarı alır (bunları arka arkaya, önce kategori sürümü yürütürüm). Sadece bir şekilde setLocale görünüşte "almaz".
Hot Licks

Bu yaklaşımın neden işe yaramadığını bulmak ilginç olurdu. Kimse daha iyi bir şey bulamazsa, bu görünür hatanın en iyi açıklamasına ödül vereceğim.
Hot Licks

OMZ'ye ödül veriyorum, çünkü bu konuda gayret gösteren tek kişi o.
Hot Licks

7

Tamamen farklı bir şey önerebilir miyim, çünkü dürüst olmak gerekirse tüm bunlar bir tavşan deliğinden aşağı akıyor.

Sen kullanarak biri olmalıdır NSDateFormatterile dateFormatseti ve localezorla en_US_POSIX(sunucular / API'lardan) tarihleri almak için.

Daha sonra NSDateFormatter, timeStyle/ dateStyleözelliklerini ayarlayacağınız kullanıcı arayüzü için farklı bir tane kullanmalısınız - bu şekilde dateFormatkendiniz açık bir setiniz olmaz , bu nedenle yanlış bir şekilde bu formatın kullanılacağını varsayarsınız.

Bir doğru "Ayrıştırılmış" uygulama hep ediliyor "gelen" tarihlerde ise - Bu araçlar UI (iOS ayarlarından kullanıcı seçimine doğru biçimlendirilmiş 24 saat vs am / pm ve tarih dizeleri) kullanıcı tercihleri ile tahrik edilmektedir NSDateiçin kullanın.


Bazen bu şema işe yarar, bazen işe yaramaz. Bir tehlike, yönteminizin biçimlendiricinin tarih biçimini değiştirmesi ve bunu yaparken, tarih biçimlendirme işlemlerinin ortasındayken, sizi çağıran kod tarafından ayarlanan biçimi değiştirmeniz gerekebilir. Saat diliminin tekrar tekrar değiştirilmesi gereken başka senaryolar da vardır.
Hot Licks

timeZoneBiçimlendiricinin değerinin değiştirilmesinin neden bu şemaya gireceğini bilmiyorum , biraz ayrıntı verebilir misiniz? Ayrıca netleştirmek için biçimi değiştirmekten kaçınırsınız. Bunu yapmanız gerekiyorsa, bu bir "içe aktarma" biçimlendiricisinde, yani ayrı bir biçimlendiricide gerçekleşir.
Daniel

Küresel bir nesnenin durumunu her değiştirdiğinizde bu tehlikelidir. Başkalarının da kullandığını unutması kolay.
Hot Licks

3

İşte hızlı sürümde bu sorunun çözümü. Hızlı bir şekilde kategori yerine uzantı kullanabiliriz. Yani, burada DateFormatter için uzantı oluşturduk ve içinde initWithSafeLocale DateFormatter ilgili yerel ile döndürür, Burada bizim durumumuzda en_US_POSIX olan, bunun dışında da birkaç tarih oluşturma yöntemleri sağladı.

  • Hızlı 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
    }   }
  • kullanım açıklaması:

    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))")
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.