Jaki jest najlepszy sposób radzenia sobie z nsdateformatter locale "feechur"?

Wydaje się, że NSDateFormatter ma" funkcję", która niespodziewanie cię gryzie: jeśli wykonasz prostą" stałą " operację formatu, taką jak:

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

Następnie działa dobrze w Stanach Zjednoczonych i większości lokalizacji do ... ktoś z telefonem ustawionym na Region 24-godzinny ustawia przełącznik 12/24 hour w ustawieniach na 12. Wtedy powyższe rozpoczyna zaczepianie "AM" lub "PM"na koniec wynikowego łańcucha.

(Patrz, np, NSDateFormatter, czy robię coś złego, czy jest to błąd?)

(oraz zobacz https://developer.apple.com/library/content/qa/qa1480/_index.html )

Najwyraźniej Apple uznało to za " złe " - zepsute zgodnie z przeznaczeniem i nie zamierzają tego naprawić.

W przeciwieństwie do innych formatów daty, które nie są dostępne w USA, nie są dostępne w USA.]}
NSLocale *loc = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
[df setLocale: loc];
[loc release];

Nie jest tak źle w onsies-twoosies, ale mam do czynienia z około 10 różnymi aplikacjami, a pierwsza z nich, na którą patrzę, ma 43 instancje ten scenariusz.

Więc jakieś sprytne pomysły na Makro/nadpisaną klasę / cokolwiek, aby zminimalizować wysiłek, aby wszystko zmienić, bez powodowania, że kod zaciemnia? (Moim pierwszym instynktem jest obejście NSDateFormatter z wersją, która ustawiłaby locale w metodzie init. Wymaga zmiany dwóch linii -- linii alloc/INIT i dodanego importu.)

Dodano

To jest to, co wymyśliłem do tej pory -- wydaje się działać we wszystkich scenariusze:

@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
Bounty!

Przyznam nagrodę za najlepszą (uzasadnioną) sugestię/krytykę, jaką widzę do wtorku w połowie dnia. [Patrz poniżej -- termin przedłużony.]

Update

Re OMZ 's proposal, here is what I' m finding --

Oto plik category version -- h:

#import <Foundation/Foundation.h>


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

Plik kategorii 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

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

Wynik:

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)
[[10]}Telefon [make that an iPod Touch] jest ustawiony do Wielkiej Brytanii, z przełącznik 12/24 ustawiony na 12. Jest wyraźna różnica w tych dwóch wynikach i oceniam wersję kategorii jako błędną. Zauważ, że log w wersji kategorii jest wykonywany (i zatrzymuje się w kodzie), więc nie jest to po prostu przypadek, że kod jakoś nie jest używany.

Aktualizacja nagród:

Ponieważ nie dostałem jeszcze żadnych odpowiednich odpowiedzi, przedłużę termin nagrody o kolejny dzień lub dwa.

Bounty kończy się za 21 godzin -- trafi do każdego dokłada wszelkich starań, aby pomóc, nawet jeśli odpowiedź nie jest naprawdę przydatna w moim przypadku.

Ciekawa obserwacja

Nieznacznie zmodyfikowano implementację kategorii:

#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

Po prostu zmieniłem nazwę statycznej zmiennej locale (w przypadku konfliktu ze statyczną zadeklarowaną w podklasie) i dodałem dodatkowy NSLog. Ale zobacz co ten NSLog drukuje:

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

Jak widać, setLocale po prostu nie. locale formatera jest nadal pl_gb. Wydaje się, że jest coś "dziwnego" w metodzie init w danej kategorii.

Ostatnia odpowiedź

Zobacz zaakceptowaną odpowiedź poniżej.

Author: Cœur, 2011-07-07

5 answers

Duh!!

Czasami masz " Aha!!"moment, czasami jest bardziej jak" Duh!!"To jest to drugie. W kategorii dla initWithSafeLocale "super" init został zakodowany jako self = [super init];. To initsuje SUPERKLASĘ NSDateFormatter, ale nie init samego obiektu NSDateFormatter.

Widocznie, gdy ta inicjalizacja zostanie pominięta, setLocale "odbija się", prawdopodobnie z powodu braku struktury danych w obiekcie. Zmiana init na self = [self init]; powoduje wystąpienie inicjalizacji NSDateFormatter, a setLocale jest znowu szczęśliwy.

Oto "ostateczne" źródło dla kategorii .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
 60
Author: Hot Licks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-09 12:09:45

Zamiast podklasowania, możesz utworzyć kategorię NSDateFormatter z dodatkowym inicjalizatorem, który zadba o przypisanie ustawień regionalnych i ewentualnie również ciągu formatującego, więc będziesz miał gotowy do użycia program formatujący zaraz po inicjalizacji.

@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

Wtedy możesz użyć NSDateFormatter gdziekolwiek w Twoim kodzie z po prostu:

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

Możesz w jakiś sposób przedrostek swojej metody kategorii, aby uniknąć konfliktów nazw, na wypadek, gdyby Apple zdecydowało się dodać taką metodę w przyszłej wersji OS.

W przypadku, gdy używasz zawsze tego samego formatu daty, Możesz również dodać metody kategorii, które zwracają instancje singleton z określonymi konfiguracjami(coś w rodzaju +sharedRFC3339DateFormatter). Pamiętaj jednak, że NSDateFormatter nie jest bezpieczny dla wątków i musisz używać blokad lub @synchronized bloków, gdy używasz tej samej instancji z wielu wątków.

 38
Author: omz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-09 17:15:52

Mogę zasugerować coś zupełnie innego, bo szczerze mówiąc, to wszystko jest trochę biegające po króliczej norze.

Powinieneś używać jednego NSDateFormatter z dateFormat ustawionym i locale zmuszonym do en_US_POSIX do odbierania dat (z serwerów/API).

Następnie powinieneś użyć innego NSDateFormatter dla interfejsu użytkownika, który ustawisz timeStyle/dateStyle properties-w ten sposób nie masz jawnego dateFormat ustawionego przez siebie, więc fałszywie zakładając, że format zostanie użyty.

Oznacza to UI jest napędzany przez Preferencje użytkownika (am / pm vs 24 godziny i ciągi daty sformatowane poprawnie do wyboru użytkownika-z ustawień iOS), podczas gdy daty, które "wchodzą "w aplikacji są zawsze" parsed " poprawnie do NSDate dla Ciebie do użycia.

 4
Author: Daniel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-10 11:26:34

Oto rozwiązanie tego problemu w wersji swift. W swift możemy użyć extension zamiast category. Tak, tutaj stworzyłem rozszerzenie dla DateFormatter i wewnątrz tego initWithSafeLocale zwraca DateFormatter z odpowiednim Locale, tutaj w naszym przypadku, który jest en_US_POSIX, oprócz tego również pod warunkiem kilka metod formowania daty.

  • Swift 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
    }   }
    
  • Opis użycia:

    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))")
    
 0
Author: Tech,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-25 14:37:30

Spróbuj tego....

-(NSDate *)getDateInCurrentSystemTimeZone
{
    NSDate* sourceDate = [NSDate date];
    NSTimeZone* sourceTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
    NSTimeZone* destinationTimeZone = [NSTimeZone systemTimeZone];

    NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:sourceDate];
    NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:sourceDate];
    NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset;

    NSDate* destinationDate = [[NSDate alloc] initWithTimeInterval:interval sinceDate:sourceDate];
    return destinationDate;
}
 -1
Author: Khurshid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-15 11:52:42