Каков наилучший способ справиться с локализацией NSDateFormatter "feechur"?
Кажется, что NSDateFormatter
имеет "функцию", которая неожиданно кусает вас: если вы выполняете простую операцию "фиксированного" формата, например:
NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:@"yyyyMMddHHmmss"];
NSString* dateStr = [fmt stringFromDate:someDate];
[fmt release];
Затем он отлично работает в США и большинстве локаций. UNTIL... кто-то со своим телефоном, настроенным на 24-часовую область, устанавливает переключатель 12/24 часа в настройках на 12. Затем вышеприведенное начинается с "AM" или "AM", PM "на конец результирующей строки.
(см., например, NSDateFormatter, я делаю что-то неправильно или это ошибка?)
(И см. https://developer.apple.com/library/content/qa/qa1480/_index.html)
Очевидно, Apple заявила, что это "BAD" - Broken As Designed, и они не собираются ее исправлять.
Похоже, что обход, чтобы установить локаль формата даты для определенного региона, как правило, США, но это немного грязно:
NSLocale *loc = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
[df setLocale: loc];
[loc release];
Не так уж плохо в onsies-twosies, но я имею дело с десятью различными приложениями, и первый, на который я смотрю, имеет 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!
Я награду премией за лучшее (законное) предложение/критику, которое я вижу к середине дня во вторник. [См. Ниже - расширенный срок.]
Update
Предложение Re OMZ, вот что я нахожу -
Вот версия категории - h файл:
#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 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 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 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. Там есть явная разница в двух результатах, и я считаю, что версия категории неверна. Обратите внимание, что журнал в версии категории IS, выполняемый (и останавливается в коде, попадает), так что это не просто случай кода, который каким-то образом не используется.
Обновление Bounty:
Так как я еще не получил ответов, я продюлю грандиозный срок на другой день или два.
Bounty заканчивается через 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 locale: %@ %@", en_US_POSIX2.description, [en_US_POSIX2 localeIdentifier]);
[self setLocale:en_US_POSIX2];
NSLog(@"Category object: %@ and object locale: %@ %@", self.description, self.locale.description, [self.locale localeIdentifier]);
return self;
}
@end
В основном просто изменилось имя переменной static locale (в случае конфликта с статикой, объявленной в подклассе) и добавлен дополнительный NSLog. Но посмотрите, что печатает NSLog:
2011-07-15 16:35:24.322 DemoApp[214:307] Category locale: <__NSCFLocale: 0x160550> en_US_POSIX
2011-07-15 16:35:24.338 DemoApp[214:307] Category object: <NSDateFormatter: 0x160d90> and object 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 в категории есть что-то "странное".
Окончательный ответ
См. принятый ответ ниже.
Ответы
Ответ 1
Duh!!
Иногда у вас есть "Ага!" момент, иногда это больше "Ду!" Это последнее. В категории для initWithSafeLocale
"супер" init
был закодирован как self = [super init];
. Это соответствует SUPERCLASS NSDateFormatter
, но не init
сам объект NSDateFormatter
.
По-видимому, когда эта инициализация пропущена, setLocale
"отскакивает", предположительно из-за некоторой недостающей структуры данных в объекте. Изменение init
на self = [self init];
вызывает инициализацию NSDateFormatter
, и setLocale
снова счастлив.
Вот "окончательный" источник для категории .m:
#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
Ответ 2
Вместо подкласса вы можете создать категорию NSDateFormatter
с дополнительным инициализатором, который позаботится о назначении языкового стандарта и, возможно, также в строке формата, чтобы у вас был готовый к использованию форматер сразу после его инициализации.
@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
Затем вы можете использовать NSDateFormatter
в любом месте своего кода:
NSDateFormatter* fmt = [[NSDateFormatter alloc] initWithPOSIXLocaleAndFormat:@"yyyyMMddHHmmss"];
Возможно, вы захотите префикс своего метода категории, чтобы избежать конфликтов имен, на случай, если Apple решит добавить такой метод в будущую версию ОС.
Если вы всегда используете один и тот же формат даты, вы также можете добавить методы категорий, которые возвращают экземпляры singleton с определенными конфигурациями (что-то вроде +sharedRFC3339DateFormatter
). Однако имейте в виду, что NSDateFormatter
не является потокобезопасным, и вы должны использовать блокировки или блокировки @synchronized
, когда используете один и тот же экземпляр из нескольких потоков.
Ответ 3
Могу ли я предложить нечто совершенно другое, потому что, честно говоря, все это несколько бежит по кроличьей дыре.
Вы должны использовать один NSDateFormatter
с dateFormat
set и locale
принудительно для en_US_POSIX
для получения дат (с серверов /API ).
Затем вы должны использовать другой NSDateFormatter
для пользовательского интерфейса, который вы установите для свойств timeStyle
/dateStyle
- таким образом, у вас не будет установлен явный dateFormat
, поэтому ложно предполагается, что формат будет использоваться.
Это означает, что пользовательский интерфейс управляется пользовательскими настройками (am/pm vs 24 hour и строками даты, отформатированными правильно для пользователя, - с настройками iOS), тогда как даты, входящие в ваше приложение, всегда "разбираются" правильно к NSDate
для использования.
Ответ 4
Вот решение этой проблемы в быстрой версии. В быстром мы можем использовать расширение вместо категории.
Итак, здесь я создал расширение для DateFormatter и внутри этого initWithSafeLocale
возвращает DateFormatter с соответствующей локалью, здесь, в нашем случае, которая является en_US_POSIX, кроме того, она также предоставляет пару методов формирования даты.
-
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
} }
-
описание использования:
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))")
Ответ 5
Попробуйте этот...
-(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;
}