Кажется, у NSDateFormatter
него есть «особенность», которая неожиданно кусает вас: если вы выполняете простую операцию «фиксированного» формата, такую как:
NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:@"yyyyMMddHHmmss"];
NSString* dateStr = [fmt stringFromDate:someDate];
[fmt release];
Тогда он отлично работает в США и большинстве регионов до тех пор, пока ... кто-то с его телефоном, настроенным на 24-часовой регион, устанавливает переключатель 12/24 часа в настройках на 12. Затем вышеупомянутое начинает привязывать «AM» или «PM» к конец результирующей строки.
(Смотрите, например, NSDateFormatter, я делаю что-то не так или это ошибка? )
(И см. Https://developer.apple.com/library/content/qa/qa1480/_index.html )
Очевидно, Apple объявила, что это «ПЛОХО» - Broken As Designed, и они не собираются это исправить.
Обходное решение, по-видимому, заключается в установке языкового стандарта форматирования даты для определенного региона, как правило, США, но это немного грязно:
NSLocale *loc = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
[df setLocale: loc];
[loc release];
Не так уж и плохо в первый раз, но я имею дело примерно с десятью различными приложениями, и первое, на которое я смотрю, имеет 43 экземпляра этого сценария.
Итак, есть ли какие-нибудь умные идеи для макро / переопределенного класса / чего бы то ни было, чтобы минимизировать усилия по изменению всего, не делая код неясным? (Мой первый инстинкт - переопределить NSDateFormatter версией, которая установит языковой стандарт в методе init. Требуется изменить две строки - строку alloc / init и добавленный импорт.)
добавленной
Это то, что я придумал до сих пор - кажется, работает во всех сценариях:
@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
Bounty!
Я награжу награду лучшим (законным) предложением / критикой, которое я вижу к полудню вторника. [См. Ниже - срок продлен.]
Обновить
Относительно предложения ОМЗ, вот что я нахожу -
Вот версия категории - h file:
#import <Foundation/Foundation.h>
@interface NSDateFormatter (Locale)
- (id)initWithSafeLocale;
@end
Файл категории 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
Код:
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];
Результат:
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)
На телефоне [сделайте так, чтобы iPod Touch] был установлен в Великобритании, с переключателем 12/24, установленным в 12. В двух результатах есть явная разница, и я считаю версию категории неправильной. Обратите внимание, что журнал в версии категории выполняется (и в него попадают остановки, помещенные в код), так что это не просто случай, когда код как-то не используется.
Обновление Баунти:
Поскольку я еще не получил никаких применимых ответов, я продляю срок выплаты вознаграждения еще на день или два.
Баунти заканчивается через 21 час - она отправится тому, кто приложит максимум усилий, чтобы помочь, даже если ответ не очень полезен в моем случае.
Любопытное наблюдение
Немного изменил реализацию категории:
#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
В основном просто изменили имя статической переменной локали (в случае, если был какой-то конфликт со статической переменной, объявленной в подклассе) и добавили дополнительный NSLog. Но посмотрите, что печатает 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
Как видите, setLocale просто нет. Язык форматирования по-прежнему en_GB. Похоже, что в методе init в категории есть что-то «странное».
Окончательный ответ
Смотрите принятый ответ ниже.
источник
- (NSDateFormatterBehavior)formatterBehavior
?Ответы:
Duh !!
Иногда у вас есть "Ага !!" момент, иногда это больше "Дух !!" Это последнее. В категории для
initWithSafeLocale
«супер»init
было закодировано какself = [super init];
. Это inits суперкласс ,NSDateFormatter
но неinit
наNSDateFormatter
сам объект.Видимо, когда эта инициализация пропускается,
setLocale
«отскакивает», вероятно, из-за некоторой отсутствующей структуры данных в объекте. Изменениеinit
кself = [self init];
ЗаставляетNSDateFormatter
инициализации произойти, иsetLocale
снова счастлива.Вот «окончательный» источник для категории .m:
источник
Вместо создания подклассов вы можете создать
NSDateFormatter
категорию с дополнительным инициализатором, который позаботится о назначении языкового стандарта и, возможно, также строки форматирования, чтобы у вас был готовый к использованию форматер сразу после его инициализации.Тогда вы можете использовать
NSDateFormatter
где угодно в вашем коде просто:Возможно, вы захотите добавить префикс к вашему методу категории, чтобы избежать конфликтов имен, на тот случай, если Apple решит добавить такой метод в будущую версию ОС.
Если вы всегда используете один и тот же формат (ы) даты, вы также можете добавить методы категорий, которые возвращают единичные экземпляры с определенными конфигурациями (что-то вроде
+sharedRFC3339DateFormatter
). Имейте в виду, однако, чтоNSDateFormatter
это не поточно- ориентированный, и вы должны использовать блокировки или@synchronized
блоки, когда вы используете один и тот же экземпляр из нескольких потоков.источник
Позвольте мне предложить что-то совершенно другое, потому что, честно говоря, все это в некотором роде бежит по кроличьей норе.
Вы должны использовать один
NSDateFormatter
сdateFormat
установленным иlocale
принудительнымen_US_POSIX
для получения дат (с серверов / API).Тогда вы должны использовать другой
NSDateFormatter
для пользовательского интерфейса, который вы будете устанавливатьtimeStyle
/dateStyle
свойства - таким образом, вы не имеете явногоdateFormat
набора самостоятельно, таким образом ложно предполагая, что будет использоваться этот формат.Это означает, что пользовательский интерфейс определяется пользовательскими настройками (am / pm против 24 часов и строки даты, правильно отформатированные по выбору пользователя - из настроек iOS), в то время как даты, которые «попадают» в ваше приложение, всегда «анализируются» правильно
NSDate
для вам использовать.источник
timeZone
значения форматера мешало бы этой схеме, не могли бы вы уточнить? Также, чтобы быть ясно, вы бы воздержались от изменения формата. Если вам нужно сделать это, то это будет происходить на «импортном» форматере, то есть на отдельном форматере.Вот решение этой проблемы в быстрой версии. В Swift мы можем использовать расширение вместо категории. Итак, здесь я создал расширение для DateFormatter, и внутри этого initWithSafeLocale возвращает DateFormatter с соответствующим языковым стандартом, здесь в нашем случае это en_US_POSIX, кроме этого также предусмотрено несколько методов формирования даты.
Swift 4
описание использования:
источник