web-dev-qa-db-fra.com

Quel est le meilleur moyen de gérer les paramètres régionaux "feechur" de NSDateFormatter?

Il semble que NSDateFormatter possède une "fonctionnalité" qui vous mord inopinément: si vous effectuez une opération de formatage "fixe" simple, telle que:

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

Ensuite, cela fonctionne très bien aux États-Unis et dans la plupart des pays jusqu’à UNTIL ... une personne dont le téléphone est réglé sur une région de 24 heures règle le commutateur 12/24 heures sur 12. Ensuite, le message ci-dessus commence à coller sur "AM" ou "PM". 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 cela était "MAUVAIS" - Broken As Designed, 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 ceci est un peu compliqué:

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

Pas si mal en onsies-twosies, mais je traite avec une dizaine d'applications différentes, et la première que je regarde comporte 43 instances de ce scénario.

Donc, toutes les idées intelligentes pour une macro/classe surchargée/quoi que ce soit afin de minimiser l'effort de tout changer, sans pour autant rendre le code obscur? (Mon premier réflexe consiste à remplacer NSDateFormatter par une version qui définirait les paramètres régionaux dans la méthode init. Nécessite de modifier deux lignes - la ligne alloc/init et l'importation ajoutée.)

Ajoutée

C’est ce que j’ai trouvé 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!

J'attribuerai cette prime à la meilleure suggestion/critique (légitime) que je verrai d'ici mardi à midi. [Voir ci-dessous - délai prolongé.]

Mise à jour

Concernant la proposition d'OMZ, voici ce que je découvre -

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 [assurez-vous qu'un iPod Touch] est réglé sur la Grande-Bretagne, avec le commutateur 12/24 sur 12. Il existe une nette différence entre les deux résultats et j'estime que la version de la catégorie est fausse. Notez que le journal dans la version de catégorie IS étant exécuté (et les arrêts placés dans le code sont atteints), il ne s’agit donc pas simplement que le code ne soit pas utilisé.

Bounty update:

Comme je n'ai pas encore reçu de réponse applicable, je vais prolonger le délai de remise des primes d'un ou deux jours.

La prime prend fin 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

En gros, je viens de changer le nom de la variable locale locale statique (au cas où il y aurait un conflit avec la statique déclarée 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 constater, le setLocale n'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 le réponse acceptée ci-dessous.

163
Hot Licks

Duh !!

Parfois, vous avez un "Aha !!" moment, il est parfois plus d'un "Duh !!" C'est le dernier. Dans la catégorie pour initWithSafeLocale, le "super" init était codé ainsi: self = [super init];. Cela insère la SUPERCLASS de NSDateFormatter mais pas init l’objet NSDateFormatter lui-même.

Apparemment, lorsque cette initialisation est ignorée, setLocale "rebondit", probablement à cause d'une structure de données manquante dans l'objet. Changer le init en self = [self init]; provoque l'initialisation de NSDateFormatter et setLocale redevient heureux.

Voici la source "finale" du fichier .m de la catégorie:

#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
63
Hot Licks

Au lieu de sous-classer, vous pouvez créer une catégorie NSDateFormatter avec un initialiseur supplémentaire qui se charge d'attribuer les paramètres régionaux et éventuellement une chaîne de formatage. Vous disposez ainsi d'un formateur prêt à l'emploi après son initialisation.

@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

Ensuite, vous pouvez utiliser NSDateFormatter n'importe où dans votre code avec simplement:

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

Vous voudrez peut-être préfixer votre méthode category d'une manière ou d'une autre pour éviter les conflits de noms, juste au cas où Apple décide d'ajouter une telle méthode dans une future version du système d'exploitation.

Si vous utilisez toujours le même format de date, vous pouvez également ajouter des méthodes de catégorie qui renvoient des instances singleton avec certaines configurations (quelque chose comme +sharedRFC3339DateFormatter). Sachez cependant que NSDateFormatter n’est pas thread-safe et que vous devez utiliser des verrous ou @synchronized bloque lorsque vous utilisez la même instance à partir de plusieurs threads.

38
omz

Permettez-moi de suggérer quelque chose de totalement différent, car pour être honnête, tout cela se répercute quelque peu sur un terrier de lapin.

Vous devriez utiliser un NSDateFormatter avec dateFormat set et locale forcé à en_US_POSIX pour recevoir les dates (des serveurs/API).

Ensuite, vous devriez utiliser un NSDateFormatter différent pour l'interface utilisateur, ce qui vous permettra de définir les propriétés timeStyle/dateStyle. Ainsi, vous n'avez pas de dateFormat défini par vous-même, supposant à tort que ce format sera utilisé.

Cela signifie que l'interface utilisateur dépend des préférences de l'utilisateur (am/pm vs 24 hour, et que les chaînes de date sont correctement formatées en fonction des choix de l'utilisateur, à partir des paramètres iOS), alors que les dates "entrant" dans votre application sont toujours "analysées" correctement en NSDate à utiliser.

6
Daniel

Voici la solution à ce problème dans la version Swift. Dans Swift, nous pouvons utiliser l'extension au lieu de la catégorie. Donc, j'ai créé l'extension pour le DateFormatter et dedans que initWithSafeLocale renvoie le DateFormatter avec la locale appropriée, Ici dans notre cas, il s'agit de en_US_POSIX. En plus de cela, a également fourni quelques méthodes de formation de date.

  • 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
    }   }
    
  • description d'utilisation:

    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))")
    
3
Tech