Parece que NSDateFormatter
possui um "recurso" que o morde inesperadamente: se você fizer uma operação simples de formato "fixo", como:
NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:@"yyyyMMddHHmmss"];
NSString* dateStr = [fmt stringFromDate:someDate];
[fmt release];
Funciona bem nos EUA e na maioria das localidades, ATÉ QUE ... alguém com o telefone definido para uma região de 24 horas define o interruptor de 12/24 horas nas configurações para 12. Em seguida, o item acima começa a colocar "AM" ou "PM" em o fim da sequência resultante.
(Veja, por exemplo, NSDateFormatter, estou fazendo algo errado ou isso é um bug? )
(E veja https://developer.apple.com/library/content/qa/qa1480/_index.html )
Aparentemente, a Apple declarou que isso é "MAU" - Quebrado como projetado, e eles não vão corrigi-lo.
Aparentemente, a evasão define o local do formatador de datas para uma região específica, geralmente os EUA, mas isso é um pouco confuso:
NSLocale *loc = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
[df setLocale: loc];
[loc release];
Não é tão ruim em twosies customizáveis, mas estou lidando com cerca de dez aplicativos diferentes, e o primeiro que eu olho tem 43 instâncias desse cenário.
Então, alguma idéia inteligente para uma classe macro / substituída / o que quer que seja para minimizar o esforço de mudar tudo, sem tornar o código obscuro? (Meu primeiro instinto é substituir o NSDateFormatter por uma versão que defina a localidade no método init. Requer a alteração de duas linhas - a linha de alocação / inicialização e a importação adicionada.)
Adicionado
Isto é o que eu criei até agora - parece funcionar em todos os cenários:
@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
Recompensa!
Atribuirei a recompensa à melhor sugestão / crítica (legítima) que vejo até o meio-dia de terça-feira. [Veja abaixo - prazo estendido.]
Atualizar
Re proposta da OMZ, aqui está o que eu estou encontrando -
Aqui está a versão da categoria - arquivo h:
#import <Foundation/Foundation.h>
@interface NSDateFormatter (Locale)
- (id)initWithSafeLocale;
@end
Arquivo da categoria 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
O 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];
O 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)
O telefone [faz com que o iPod Touch] esteja definido para a Grã-Bretanha, com o interruptor 12/24 definido como 12. Há uma clara diferença nos dois resultados, e considero a versão da categoria errada. Observe que o log na versão da categoria está sendo executado (e as paradas colocadas no código são atingidas); portanto, não é apenas um caso do código que de alguma forma não está sendo usado.
Atualização de recompensa:
Como ainda não recebi nenhuma resposta aplicável, estenderei o prazo para mais um ou dois dias.
A recompensa termina em 21 horas - vai para quem fizer mais esforço para ajudar, mesmo que a resposta não seja realmente útil no meu caso.
Uma observação curiosa
Modificou a implementação da categoria ligeiramente:
#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
Basicamente, apenas alterei o nome da variável de localidade estática (caso houvesse algum conflito com a estática declarada na subclasse) e adicionei o NSLog extra. Mas veja o que o 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 você pode ver, o setLocale simplesmente não. O código do idioma do formatador ainda é en_GB. Parece que há algo "estranho" em um método init em uma categoria.
Resposta final
Veja a resposta aceita abaixo.
fonte
- (NSDateFormatterBehavior)formatterBehavior
?Respostas:
Duh !!
Às vezes você tem um "Aha !!" momento, às vezes é mais um "Duh !!" Este é o último. Na categoria para
initWithSafeLocale
o "super"init
foi codificado comoself = [super init];
. Isso inicia a SUPERCLASSNSDateFormatter
mas nãoinit
oNSDateFormatter
objeto em si.Aparentemente, quando essa inicialização é ignorada,
setLocale
"retorna", presumivelmente por causa de alguma estrutura de dados ausente no objeto. Alterar oinit
paraself = [self init];
faz com que aNSDateFormatter
inicialização ocorra esetLocale
fica feliz novamente.Aqui está a fonte "final" para o .m da categoria:
fonte
Em vez de subclassificar, você pode criar uma
NSDateFormatter
categoria com um inicializador adicional que se encarrega de atribuir a localidade e, possivelmente, também uma string de formato, para que você tenha um formatador pronto para uso logo após inicializá-lo.Então você pode usar
NSDateFormatter
qualquer lugar no seu código com apenas:Você pode prefixar seu método de categoria de alguma forma para evitar conflitos de nome, caso a Apple decida adicionar esse método em uma versão futura do sistema operacional.
Caso você esteja sempre usando o (s) mesmo (s) formato (s) de data, você também pode adicionar métodos de categoria que retornam instâncias singleton com determinadas configurações (algo como
+sharedRFC3339DateFormatter
). Esteja ciente, no entanto, de queNSDateFormatter
não é seguro para threads e você precisa usar bloqueios ou@synchronized
blocos quando estiver usando a mesma instância de vários threads.fonte
Posso sugerir algo totalmente diferente, porque, para ser sincero, tudo isso está correndo pela toca de um coelho.
Você deve usar um
NSDateFormatter
comdateFormat
set elocale
forçado aen_US_POSIX
receber datas (de servidores / APIs).Então você deve usar um diferente
NSDateFormatter
para a interface do usuário que definirá as propriedadestimeStyle
/dateStyle
- dessa forma, você não terá umdateFormat
conjunto explícito , assumindo falsamente que o formato será usado.Isto significa UI é impulsionado por preferências do usuário (am / pm vs 24 horas, e cordas data formatada corretamente a escolha do usuário - a partir das configurações do iOS), enquanto datas que são "que entram em" seu aplicativo estão sempre sendo "analisado" corretamente a um
NSDate
para você usar.fonte
timeZone
valor do formatador atrapalharia esse esquema, você poderia elaborar? Também para ficar claro, você se absteria de alterar o formato. Se você precisar fazer isso, isso aconteceria em um formatador "import", portanto, em um formatador separado.Aqui está a solução para esse problema na versão rápida. No swift, podemos usar extensão em vez de categoria. Então, aqui eu criei a extensão para o DateFormatter e, dentro dele, initWithSafeLocale retorna o DateFormatter com o local relevante.
Swift 4
descrição de uso:
fonte