¿Cuál es la mejor manera de lidiar con la configuración regional de NSDateFormatter “feechur”?

Parece que NSDateFormatter tiene una “característica” que lo muerde inesperadamente: si realiza una operación de formato “fijo” simple como:

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

Luego funciona bien en los EE. UU. Y en la mayoría de los lugares hasta que … alguien con su teléfono configurado en una región de 24 horas ajuste el interruptor de 12/24 horas en la configuración a 12. Luego, lo anterior comienza a marcar “AM” o “PM” en el final de la cadena resultante.

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

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

Aparentemente Apple ha declarado que esto es “MALO” – Roto como está diseñado, y no lo van a arreglar.

La elusión aparentemente establece la configuración regional del formateador de fecha para una región específica, generalmente EE. UU., Pero esto es un poco desordenado:

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

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

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

Adicional

Esto es lo que se me ocurrió hasta ahora: 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!

Otorgaré la recompensa a la mejor sugerencia / crítica (legítima) que veo para el mediodía del martes. [Ver a continuación – fecha límite extendida.]

Actualizar

La propuesta de Re OMZ, esto es lo que estoy buscando:

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

 #import  @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:  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:  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 [hacer que un iPod Touch] esté configurado en Gran Bretaña, con el interruptor 12/24 en 12. Hay una clara diferencia en los dos resultados, y juzgo que la versión de la categoría es incorrecta. Tenga en cuenta que el registro en la versión de categoría IS se ejecuta (y se detienen las paradas ubicadas en el código), por lo que no se trata simplemente de que el código no se use de alguna manera.

Bounty update:

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

Bounty termina en 21 horas; irá a quien haga 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 la 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, simplemente cambió el nombre de la variable de configuración regional estática (en caso de que hubiera algún conflicto con la estática declarada en la subclase) y agregó el NSLog adicional. Pero mira lo que ese NSLog imprime:

 2011-07-15 16:35:24.322 DemoApp[214:307] Category's locale:  en_US_POSIX 2011-07-15 16:35:24.338 DemoApp[214:307] Category's object:  and object's locale:  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 configuración regional del formateador sigue siendo en_GB. Parece que hay algo “extraño” sobre un método init en una categoría.

Respuesta final

Ver la respuesta aceptada a continuación.

Duh !!

A veces tienes un “¡Ajá!” momento, a veces es más un “¡¡¡Duh !!” Este es el último En la categoría para initWithSafeLocale el “super” init se codificó como self = [super init]; . Esto entra en la SUPERCLASE de NSDateFormatter pero no NSDateFormatter objeto NSDateFormatter .

Aparentemente, cuando se salta esta inicialización, setLocale “rebota”, presumiblemente debido a la falta de una estructura de datos en el objeto. Cambiar el init a self = [self init]; hace que se NSDateFormatter inicialización de NSDateFormatter , y setLocale vuelve a ser feliz.

Aquí está la fuente “final” para el .m de la categoría:

 #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 crear subclases, puede crear una categoría NSDateFormatter con un inicializador adicional que se encargue de asignar la configuración regional y posiblemente también una cadena de formato, para que tenga un formateador listo para usar justo 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 podría usar NSDateFormatter en cualquier parte de su código con solo:

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

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

En caso de que siempre use el mismo formato de fecha, también puede agregar métodos de categoría que devuelvan instancias +sharedRFC3339DateFormatter con ciertas configuraciones (algo así como +sharedRFC3339DateFormatter ). Tenga en cuenta, sin embargo, que NSDateFormatter no es seguro para subprocesos y que debe usar lockings o bloques @synchronized cuando usa la misma instancia de varios subprocesos.

Puedo sugerir algo totalmente diferente porque, para ser sincero, todo esto está corriendo por un agujero de conejo.

Debería utilizar un NSDateFormatter con el conjunto de dateFormat y el locale forzado a en_US_POSIX para recibir fechas (desde servidores / API).

Entonces deberías estar usando un NSDateFormatter diferente para la UI, en la que establecerás las propiedades timeStyle / dateStyle ; de esta forma, no tienes dateFormat explícito establecido por ti mismo, asumiendo falsamente que se usará ese formato.

Esto significa que la IU depende de las preferencias del usuario (am / pm frente a 24 horas y cadenas de fechas formateadas correctamente para la elección del usuario, desde la configuración de iOS), mientras que las fechas que ” NSDate ” a su aplicación siempre se “analizan” correctamente en un NSDate para que lo uses

Aquí está la solución para ese problema en la versión rápida. En swift podemos usar la extensión en lugar de la categoría. Por lo tanto, aquí he creado la extensión para DateFormatter y dentro de initWithSafeLocale se devuelve el DateFormatter con la configuración regional pertinente, aquí en nuestro caso que es en_US_POSIX, además de que también proporcionó un par de métodos de formación de fecha.

  • 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 } } 
  • descripción del uso:

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

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