ios - style - objective c dateformatter
¿Cuál es la mejor manera de lidiar con la configuración regional de NSDateFormatter "feechur"? (5)
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 <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 [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: <__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
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
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))")
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 bloqueos o bloques @synchronized
cuando usa la misma instancia de varios subprocesos.
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;
}
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