Es scheint, dass NSDateFormatter
es eine "Funktion" gibt, die Sie unerwartet beißt: Wenn Sie eine einfache "feste" Formatoperation ausführen, wie z.
NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:@"yyyyMMddHHmmss"];
NSString* dateStr = [fmt stringFromDate:someDate];
[fmt release];
Dann funktioniert es in den USA und den meisten Regionen gut, BIS ... jemand, dessen Telefon auf eine 24-Stunden-Region eingestellt ist, den 12/24-Stunden-Schalter in den Einstellungen auf 12 stellt. Dann beginnt das oben Gesagte, "AM" oder "PM" anzuheften das Ende der resultierenden Zeichenfolge.
(Siehe z. B. NSDateFormatter, mache ich etwas falsch oder ist das ein Fehler? )
(Siehe https://developer.apple.com/library/content/qa/qa1480/_index.html )
Anscheinend hat Apple dies als "SCHLECHT" deklariert - Broken As Designed, und sie werden es nicht beheben.
Die Umgehung besteht anscheinend darin, das Gebietsschema des Datumsformatierers für eine bestimmte Region, im Allgemeinen die USA, festzulegen, aber dies ist etwas chaotisch:
NSLocale *loc = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
[df setLocale: loc];
[loc release];
Nicht schlecht in Onsies-Twosies, aber ich habe es mit ungefähr zehn verschiedenen Apps zu tun, und die erste, die ich mir ansehe, enthält 43 Instanzen dieses Szenarios.
Also irgendwelche cleveren Ideen für ein Makro / eine überschriebene Klasse / was auch immer, um den Aufwand zu minimieren, alles zu ändern, ohne den Code zu verschleiern? (Mein erster Instinkt ist, NSDateFormatter mit einer Version zu überschreiben, die das Gebietsschema in der init-Methode festlegt. Erfordert das Ändern von zwei Zeilen - der Alloc / Init-Zeile und dem hinzugefügten Import.)
Hinzugefügt
Das habe ich mir bisher ausgedacht - scheint in allen Szenarien zu funktionieren:
@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
Kopfgeld!
Ich werde das Kopfgeld an den besten (legitimen) Vorschlag / die beste Kritik vergeben, die ich bis Dienstagmittag sehe. [Siehe unten - Frist verlängert.]
Aktualisieren
Zu OMZs Vorschlag, hier ist was ich finde -
Hier ist die Kategorie Version - h Datei:
#import <Foundation/Foundation.h>
@interface NSDateFormatter (Locale)
- (id)initWithSafeLocale;
@end
Datei der Kategorie 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
Der Code:
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];
Das Ergebnis:
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)
Das Telefon [machen Sie einen iPod Touch] ist auf Großbritannien eingestellt, der 12/24-Schalter auf 12. Es gibt einen deutlichen Unterschied zwischen den beiden Ergebnissen, und ich beurteile die Kategorieversion als falsch. Beachten Sie, dass das Protokoll in der Kategorieversion ausgeführt wird (und Stopps im Code getroffen werden), sodass der Code nicht einfach nicht verwendet wird.
Kopfgeld-Update:
Da ich noch keine zutreffenden Antworten erhalten habe, werde ich die Kopfgeldfrist um ein oder zwei Tage verlängern.
Das Kopfgeld endet in 21 Stunden - es geht an jeden, der sich am meisten bemüht, zu helfen, auch wenn die Antwort in meinem Fall nicht wirklich nützlich ist.
Eine merkwürdige Beobachtung
Die Implementierung der Kategorie wurde geringfügig geändert:
#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
Grundsätzlich wurde nur der Name der statischen Gebietsschemavariablen geändert (falls es einen Konflikt mit der in der Unterklasse deklarierten statischen Variable gab) und das zusätzliche NSLog hinzugefügt. Aber schauen Sie, was dieser NSLog druckt:
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
Wie Sie sehen können, hat die setLocale dies einfach nicht getan. Das Gebietsschema des Formatierers ist immer noch en_GB. Es scheint, dass eine Init-Methode in einer Kategorie etwas "Seltsames" hat.
Endgültige Antwort
Siehe die akzeptierte Antwort unten.
quelle
- (NSDateFormatterBehavior)formatterBehavior
?Antworten:
Duh !!
Manchmal hast du ein "Aha !!" Moment, manchmal ist es eher ein "Duh !!" Dies ist das letztere. In der Kategorie für
initWithSafeLocale
das "Super"init
wurde als codiertself = [super init];
. Dies ist die SUPERCLASS vonNSDateFormatter
, aber nichtinit
dasNSDateFormatter
Objekt selbst.Wenn diese Initialisierung übersprungen wird,
setLocale
"prallt" sie offenbar ab, vermutlich aufgrund einer fehlenden Datenstruktur im Objekt. Durch Ändern voninit
toself = [self init];
wird dieNSDateFormatter
Initialisierung ausgeführt undsetLocale
ist wieder zufrieden.Hier ist die "endgültige" Quelle für die .m der Kategorie:
quelle
Anstelle einer Unterklasse können Sie eine
NSDateFormatter
Kategorie mit einem zusätzlichen Initialisierer erstellen , der sich um die Zuweisung des Gebietsschemas und möglicherweise auch einer Formatzeichenfolge kümmert, sodass Sie direkt nach der Initialisierung einen gebrauchsfertigen Formatierer haben.Dann könnten Sie
NSDateFormatter
überall in Ihrem Code mit nur:Möglicherweise möchten Sie Ihrer Kategoriemethode ein Präfix voranstellen, um Namenskonflikte zu vermeiden, falls Apple beschließt, eine solche Methode in einer zukünftigen Version des Betriebssystems hinzuzufügen.
Wenn Sie immer dieselben Datumsformate verwenden, können Sie auch Kategoriemethoden hinzufügen, die Singleton-Instanzen mit bestimmten Konfigurationen zurückgeben (z. B.
+sharedRFC3339DateFormatter
). Beachten Sie jedoch, dass diesNSDateFormatter
nicht threadsicher ist und Sie Sperren oder@synchronized
Blöcke verwenden müssen, wenn Sie dieselbe Instanz aus mehreren Threads verwenden.quelle
Darf ich etwas völlig anderes vorschlagen, denn um ehrlich zu sein, läuft all dies etwas durch ein Kaninchenloch.
Sie sollten eine
NSDateFormatter
mitdateFormat
set verwenden undlocale
gezwungen sein,en_US_POSIX
Daten zu empfangen (von Servern / APIs).Dann sollten Sie
NSDateFormatter
für die Benutzeroberfläche eine andere verwenden, für die Sie dietimeStyle
/dateStyle
-Eigenschaften festlegen. Auf diese Weise haben Sie keine explizitedateFormat
Festlegung selbst und gehen fälschlicherweise davon aus, dass das Format verwendet wird.Dies bedeutet, dass die Benutzeroberfläche von den Benutzereinstellungen abhängt (morgens / abends gegenüber 24 Stunden und Datumszeichenfolgen, die gemäß den iOS-Einstellungen korrekt nach Benutzerauswahl formatiert wurden), während Datumsangaben, die in Ihre App "eingehen", immer korrekt auf ein
NSDate
für "analysiert" werden Sie zu verwenden.quelle
timeZone
Wertes des Formatierers diesem Schema im Wege stehen würde. Könnten Sie das näher erläutern? Um ganz klar zu sein, würden Sie auch auf eine Änderung des Formats verzichten. Wenn Sie dies tun müssen, geschieht dies bei einem "Import" -Formatierer, also einem separaten Formatierer.Hier ist die Lösung für dieses Problem in der schnellen Version. In Swift können wir Erweiterung anstelle von Kategorie verwenden. Also, hier habe ich die Erweiterung für den DateFormatter erstellt und darin gibt initWithSafeLocale den DateFormatter mit dem entsprechenden Gebietsschema zurück. Hier in unserem Fall en_US_POSIX. Abgesehen davon wurden auch einige Methoden zur Datumsbildung bereitgestellt.
Swift 4
Verwendungsbeschreibung:
quelle