Il semble que cela NSDateFormatter
a une "fonctionnalité" qui vous mord de manière inattendue: Si vous effectuez une simple opération de format "fixe" telle que:
NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:@"yyyyMMddHHmmss"];
NSString* dateStr = [fmt stringFromDate:someDate];
[fmt release];
Ensuite, cela fonctionne bien aux États-Unis et dans la plupart des régions JUSQU'À ce que ... quelqu'un avec son téléphone réglé sur une région de 24 heures règle le commutateur 12/24 heures dans les paramètres sur 12. Ensuite, ce qui précède commence à clouer "AM" ou "PM" sur la fin de la chaîne résultante.
(Voir, par exemple, NSDateFormatter, est-ce que je fais quelque chose de mal ou est-ce un bogue? )
(Et voir https://developer.apple.com/library/content/qa/qa1480/_index.html )
Apparemment, Apple a déclaré que c'était "MAUVAIS" - Cassé comme conçu, et ils ne vont pas le réparer.
Le contournement consiste apparemment à définir les paramètres régionaux du formateur de date pour une région spécifique, généralement les États-Unis, mais c'est un peu compliqué:
NSLocale *loc = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
[df setLocale: loc];
[loc release];
Pas trop mal dans onsies-twosies, mais j'ai affaire à une dizaine d'applications différentes, et la première que je regarde contient 43 instances de ce scénario.
Alors, des idées intelligentes pour une macro / classe remplacée / quoi que ce soit pour minimiser l'effort de tout changer, sans rendre le code obscur? (Mon premier instinct est de remplacer NSDateFormatter par une version qui définirait les paramètres régionaux dans la méthode init. Nécessite de changer deux lignes - la ligne alloc / init et l'importation ajoutée.)
Ajoutée
C'est ce que j'ai proposé jusqu'à présent - semble fonctionner dans tous les scénarios:
@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
Prime!
Je vais attribuer la prime à la meilleure suggestion / critique (légitime) que je vois d'ici mardi midi. [Voir ci-dessous - délai prolongé.]
Mettre à jour
Concernant la proposition d'OMZ, voici ce que je trouve -
Voici la version de la catégorie - fichier h:
#import <Foundation/Foundation.h>
@interface NSDateFormatter (Locale)
- (id)initWithSafeLocale;
@end
Fichier de catégorie 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
Le 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];
Le résultat:
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)
Le téléphone [faites qu'un iPod Touch] est réglé sur la Grande-Bretagne, avec le commutateur 12/24 réglé sur 12. Il y a une nette différence entre les deux résultats, et je juge que la version de la catégorie est fausse. Notez que le journal dans la version de catégorie EST en cours d'exécution (et les arrêts placés dans le code sont frappés), donc ce n'est pas simplement un cas où le code n'est pas utilisé d'une manière ou d'une autre.
Mise à jour de la prime:
Comme je n'ai pas encore reçu de réponses applicables, je prolongerai la date limite de prime d'un jour ou deux.
La prime se termine dans 21 heures - elle ira à celui qui fait le plus d'efforts pour aider, même si la réponse n'est pas vraiment utile dans mon cas.
Une observation curieuse
Modifié légèrement l'implémentation de la catégorie:
#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
Fondamentalement, il suffit de changer le nom de la variable locale statique (au cas où il y aurait un conflit avec le statique déclaré dans la sous-classe) et d'ajouter le NSLog supplémentaire. Mais regardez ce que NSLog imprime:
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
Comme vous pouvez le voir, le setLocale ne l'a tout simplement pas. Les paramètres régionaux du formateur sont toujours en_GB. Il semble qu'il y ait quelque chose d '"étrange" à propos d'une méthode init dans une catégorie.
Réponse finale
Voir la réponse acceptée ci-dessous.
la source
- (NSDateFormatterBehavior)formatterBehavior
?Réponses:
Duh !!
Parfois, vous avez un "Aha !!" moment, parfois c'est plus un "Duh !!" C'est le dernier. Dans la catégorie pour
initWithSafeLocale
le «super» ainit
été codé commeself = [super init];
. Cela fait partie de la SUPERCLASS de l' objet lui-même,NSDateFormatter
mais pas deinit
celuiNSDateFormatter
-ci.Apparemment, lorsque cette initialisation est ignorée,
setLocale
"rebondit", probablement à cause d'une structure de données manquante dans l'objet. Changer leinit
enself = [self init];
provoque l'NSDateFormatter
initialisation etsetLocale
est à nouveau heureux.Voici la source "finale" du .m de la catégorie:
la source
Au lieu de sous-classer, vous pouvez créer une
NSDateFormatter
catégorie avec un initialiseur supplémentaire qui s'occupe d'attribuer les paramètres régionaux et éventuellement aussi une chaîne de format, de sorte que vous ayez un formateur prêt à l'emploi juste après l'initialisation.Ensuite, vous pouvez utiliser
NSDateFormatter
n'importe où dans votre code avec juste:Vous voudrez peut-être préfixer votre méthode de catégorie d'une manière ou d'une autre pour éviter les conflits de noms, au cas où Apple déciderait d'ajouter une telle méthode dans une future version du système d'exploitation.
Si vous utilisez toujours le (s) même (s) format (s) de date, vous pouvez également ajouter des méthodes de catégorie qui renvoient des instances de singleton avec certaines configurations (quelque chose comme
+sharedRFC3339DateFormatter
). Sachez cependant que ceNSDateFormatter
n'est pas thread-safe et que vous devez utiliser des verrous ou@synchronized
blocs lorsque vous utilisez la même instance à partir de plusieurs threads.la source
Puis-je suggérer quelque chose de totalement différent parce que, pour être honnête, tout cela coule un peu dans un terrier de lapin.
Vous devriez en utiliser un
NSDateFormatter
avecdateFormat
set etlocale
forcé àen_US_POSIX
pour recevoir les dates (des serveurs / API).Ensuite, vous devriez utiliser une
NSDateFormatter
interface différente pour l'interface utilisateur dont vous définirez les propriétéstimeStyle
/dateStyle
- de cette façon, vous n'avez pas dedateFormat
jeu explicite par vous-même, supposant ainsi à tort que le format sera utilisé.Cela signifie que l'interface utilisateur est régie par les préférences de l'utilisateur (am / pm vs 24 heures, et les chaînes de date formatées correctement selon le choix de l'utilisateur - à partir des paramètres iOS), alors que les dates qui "arrivent" dans votre application sont toujours "analysées" correctement en un
NSDate
pour à utiliser.la source
timeZone
valeur du formateur entraverait ce schéma, pourriez-vous élaborer? Aussi pour être clair, vous vous abstiendrez de changer le format. Si vous avez besoin de le faire, cela se produira sur un formateur «importé», donc un formateur séparé.Voici la solution à ce problème dans la version swift. Dans swift, nous pouvons utiliser l'extension au lieu de la catégorie. Donc, ici, j'ai créé l'extension pour le DateFormatter et à l'intérieur que initWithSafeLocale renvoie le DateFormatter avec le Locale pertinent, Ici, dans notre cas, c'est en_US_POSIX, en dehors de cela également fourni quelques méthodes de formation de date.
Swift 4
description de l'utilisation:
la source