Parece que NSDateFormatter
tiene una "característica" que te muerde inesperadamente: si haces una operación de formato "fijo" simple como:
NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:@"yyyyMMddHHmmss"];
NSString* dateStr = [fmt stringFromDate:someDate];
[fmt release];
Entonces funciona bien en los EE. UU. Y en la mayoría de los lugares HASTA ... alguien con su teléfono configurado en una región de 24 horas establece el interruptor de 12/24 horas en la configuración a 12. Luego lo anterior comienza a agregar "AM" o "PM" en El final de la cadena resultante.
(Ver, por ejemplo, NSDateFormatter, ¿estoy haciendo algo mal o es un error? )
(Y ver 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.
Aparentemente, la elusión es establecer la configuración regional del formateador de fecha para una región específica, generalmente en los Estados Unidos, 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 miro tiene 43 instancias de este escenario.
Entonces, ¿alguna idea inteligente para una macro / clase anulada / 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 establezca 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 vea hasta el mediodía del martes. [Ver abajo - plazo extendido]
Actualizar
Re propuesta de OMZ, esto es lo que estoy encontrando:
Aquí está la versión de categoría - archivo 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 [hace 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 considero que la versión de categoría es incorrecta. Tenga en cuenta que el registro en la versión de categoría SE está ejecutando (y las paradas colocadas en el código se golpean), por lo que no se trata simplemente de que el código de alguna manera no se esté utilizando.
Actualización de recompensa:
Como aún no he recibido ninguna respuesta aplicable, extenderé el plazo de recompensa por otro día o dos.
Bounty finaliza 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
Se 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, solo cambié el nombre de la variable de entorno local estático (en caso de que hubiera algún conflicto con el estático declarado en la subclase) 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 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
Vea la respuesta aceptada a continuación.
fuente
- (NSDateFormatterBehavior)formatterBehavior
?Respuestas:
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ó comoself = [super init];
. Esto inicia la SUPERCLASE deNSDateFormatter
pero noinit
elNSDateFormatter
objeto en sí.Aparentemente, cuando se omite esta inicialización,
setLocale
"rebota", presumiblemente debido a la falta de una estructura de datos en el objeto. Al cambiarinit
a, seself = [self init];
produce laNSDateFormatter
inicialización ysetLocale
es feliz nuevamente.Aquí está la fuente "final" para el .m de la categoría:
fuente
En lugar de subclasificar, podría crear una
NSDateFormatter
categoría 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.Entonces podría usar
NSDateFormatter
cualquier parte de su código con solo:Es posible que desee prefijar su método de 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 los mismos formatos de fecha, también puede agregar métodos de categoría que devuelvan instancias de singleton con ciertas configuraciones (algo así como
+sharedRFC3339DateFormatter
). Sin embargo, tenga en cuenta queNSDateFormatter
no es seguro para subprocesos y que debe usar bloqueos o@synchronized
bloqueos cuando usa la misma instancia de múltiples subprocesos.fuente
¿Puedo sugerir algo totalmente diferente porque para ser honesto, todo esto está corriendo por un agujero de conejo?
Debería usar uno
NSDateFormatter
condateFormat
set ylocale
forzado aen_US_POSIX
recibir fechas (de servidores / API).Entonces, debería usar una
NSDateFormatter
interfaz de usuario diferente para la que establecerá las propiedadestimeStyle
/dateStyle
, de esta manera no tendrá undateFormat
conjunto explícito usted mismo, por lo tanto, suponiendo falsamente que se usará ese formato.Esto significa que la interfaz de usuario depende de las preferencias del usuario (am / pm frente a 24 horas, y cadenas de fecha formateadas correctamente a elección del usuario, desde la configuración de iOS), mientras que las fechas que están "entrando" en su aplicación siempre se están "analizando" correctamente
NSDate
para usted para usarfuente
timeZone
valor del formateador se interpondría en este esquema, ¿podría explicarlo? También para ser claro, se abstendrá de cambiar el formato. Si necesita hacerlo, esto sucedería en un formateador "importado", por lo tanto, en un formateador separado.Aquí está la solución para ese problema en la versión rápida. En swift podemos usar extensión en lugar de categoría. Entonces, aquí he creado la extensión para DateFormatter y dentro de ese initWithSafeLocale devuelve DateFormatter con la configuración regional relevante, aquí en nuestro caso es en_US_POSIX, aparte de eso también proporcionó un par de métodos de formación de fecha.
Swift 4
descripción de uso:
fuente