¿Cuál es la mejor forma de lidiar con la configuration local de NSDateFormatter "feechur"?

Parece que NSDateFormatter tiene una "característica" que lo muerde inesperadamente: si realiza una operación de formatting simple "fija", como:

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

Luego funciona bien en EE. UU. Y en la mayoría de los locales HASTA … alguien con su teléfono configurado en una región de 24 horas configura el conmutador de 12/24 horas en 12. Luego lo anterior comienza a "AM" o "PM" en el final de la cadena resultante.

(Véase, por ejemplo, NSDateFormatter, ¿estoy haciendo algo mal o es un error? )

(Y ver https://developer.apple.com/library/content/qa/qa1480/_index.html )

Al parecer, Apple ha declarado que es "MALO" – Broken As Designed, y no lo van a arreglar.

La elusión es, aparentemente, establecer la configuration regional del formatting de date para una región específica, generalmente los Estados Unidos, pero esto es un poco desorderado:

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

No está mal en onsies-twosies, pero estoy lidiando con alnetworkingedor de diez aplicaciones diferentes, y la primera que observo tiene 43 casos de este escenario.

Entonces, ¿cualquier idea inteligente para una macro / class anulada / lo que sea para minimizar el esfuerzo de cambiar todo, sin hacer que el código oculte? (Mi primer instinto es anular NSDateFormatter con una versión que establecería la configuration regional en el método init. Requiere cambiar dos líneas: la línea alloc / init y la import agregada).

Adicional

Esto es lo que he propuesto hasta el momento: parece funcionar en todos los escenarios:

 @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 

¡Generosidad!

Le otorgaré la recompensa a la mejor sugerencia / crítica (legítima) que veo a mediodía del martes. [Vea a continuación: ploop más extendido.]

Actualizar

La propuesta de Re OMZ, aquí es lo que estoy encontrando:

Aquí está la versión de la categoría – file h:

 #import <Foundation/Foundation.h> @interface NSDateFormatter (Locale) - (id)initWithSafeLocale; @end 

Archivo de categoría 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 

El código:

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

El resultado:

 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) 

El teléfono [haga que un iPod Touch] esté configurado en Gran Bretaña, con el interruptor 12/24 configurado en 12. Hay una clara diferencia en los dos resultados, y juzgo que la versión de la categoría es incorrecta. Tenga en count que el inicio de session de la versión de la categoría se está ejecutando (y las finalidades colocadas en el código se activan), por lo que no se trata simplemente de que el código no se use de alguna manera.

Actualización de recompensas:

Como no he recibido ninguna respuesta aplicable, extenderé la date límite de recompensa por uno o dos días más.

La recompensa termina en 21 horas, se destinará a quien hace el mayor esfuerzo para ayudar, incluso si la respuesta no es realmente útil en mi caso.

Una curiosa observación

Modificó ligeramente la implementación de categoría:

 #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 

Básicamente, solo cambió el nombre de la variable de configuration local estática (en caso de que hubiera algún conflicto con la estática declarada en la subclass) y agregó el NSLog adicional. Pero mira lo que imprime NSLog:

 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 

Como puede ver, el setLocale simplemente no lo hizo. La configuration regional del formateador sigue siendo en_GB. Parece que hay algo "extraño" sobre un método init en una categoría.

Respuesta final

Vea la respuesta aceptada a continuación.

Duh!

A veces tienes un "¡Ajá!" momento, a veces es más de un "Duh!" Este es el ultimo En la categoría para initWithSafeLocale el "super" init se codificó como self = [super init]; . Esto ingresa a SUPERCLASS de NSDateFormatter pero no NSDateFormatter object NSDateFormatter .

Al parecer, cuando se omite esta initialization, setLocale "rebota", presumiblemente debido a la falta de estructura de datos en el object. Cambiar el init a self = [self init]; provoca la initialization de setLocale y setLocale se alegra nuevamente.

Aquí está la fuente "final" para la categoría .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 

En lugar de subclasss, puede crear una categoría NSDateFormatter con un inicializador adicional que se encargue de asignar la configuration regional y posiblemente también una cadena de formatting, por lo que tendría un formateador listo para usar inmediatamente después de inicializarlo.

 @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 

Entonces puedes usar NSDateFormatter en cualquier parte de tu código con solo:

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

Es posible que desee prefijar su método de categoría de alguna manera para evitar conflictos de nombres, por si Apple decide agregar dicho método en una futura versión del sistema operativo.

En caso de que siempre esté usando los mismos formattings de date, también podría agregar methods de categoría que devuelvan instancias singleton con ciertas configuraciones (algo así como +shanetworkingRFC3339DateFormatter ). Sin embargo, tenga en count que NSDateFormatter no es seguro para subprocesss y tiene que usar lockings o bloques @synchronized cuando usa la misma instancia de varios subprocesss.

¿Puedo sugerir algo totalmente diferente, porque para ser honesto, todo esto está corriendo por un hoyo de conejo?

Debería estar utilizando un NSDateFormatter con dateFormat set y locale forzado a en_US_POSIX para recibir dates (desde serveres / API).

Entonces debería utilizar un NSDateFormatter diferente para la interfaz de usuario, que establecerá las properties timeStyle / dateStyle , de esta forma no tendrá un set dateFormat explícito, asumiendo falsamente que se usará ese formatting.

Esto significa que la interfaz de usuario está controlada por las preferences del usuario (am / pm vs 24 horas, y las cadenas de date están formateadas correctamente según la elección del usuario, desde la configuration de iOS), mientras que las dates que " NSDate " a su aplicación siempre se "analizan" correctamente en un NSDate para que lo uses.

Prueba esta …

 -(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; }