Каков наилучший способ справиться с локали NSDateFormatter "feechur"?
кажется,NSDateFormatter имеет "особенность", которая неожиданно кусает вас: если вы делаете простую "фиксированную" операцию формата, такую как:
NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:@"yyyyMMddHHmmss"];
NSString* dateStr = [fmt stringFromDate:someDate];
[fmt release];
тогда он отлично работает в США и большинстве локалей до тех пор ... кто-то со своим телефоном, установленным в 24-часовой регион, устанавливает переключатель 12/24 часа в настройках на 12. Затем выше начинает лавировать "AM" или " PM " на конец результирующей строки.
(см., например, NSDateFormatter, я делаю что-то неправильно или это a ошибка?)
(и смотрите https://developer.apple.com/library/content/qa/qa1480/_index.html)
по-видимому, Apple объявила это "плохим" - сломанным, как и было задумано, и они не собираются его исправлять.
обход, по-видимому, установить локаль форматирования даты для конкретного региона, как правило, США, но это немного грязно:
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
Баунти!
Я назначу награду за лучшее (законное) предложение/критику, которую я вижу к середине дня во вторник. [См. ниже -- крайний срок продлен.]
обновление
предложение Re OMZ, вот что я нахожу--
вот версия категории -- H файл:
#import <Foundation/Foundation.h>
@interface NSDateFormatter (Locale)
- (id)initWithSafeLocale;
@end
категория М файл:
#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)
телефон [make that an iPod Touch] установлен в Великобритании, с переключателем 12/24 установлен в 12. Есть явная разница в двух результатах, и я считаю, что версия категории ошибочна. Обратите внимание, что журнал в версии категории выполняется (и остановки, помещенные в код, попадают), поэтому это не просто случай, когда код как-то не используется.
Bounty update:
так как я еще не получил никаких применимых ответов Я продлю срок получения награды еще на день или два.
Баунти заканчивается через 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
в основном просто изменил имя статической переменной locale (в случае, если был некоторый конфликт со статическим объявленным в подклассе) и добавил дополнительный 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 в категории.
окончательный ответ
см. принятый ответ ниже.
5 ответов:
ужас!!
иногда у вас есть "ага!!"момент, иногда это больше похоже на" Да!!- Это последнее. В категории для
initWithSafeLocale"супер"initбыла прописана какself = [super init];. Это inits суперклассNSDateFormatterа неinittheNSDateFormatterсам объект.видимо, когда эта инициализация пропускается,
setLocale"отскакивает", предположительно из-за некоторой отсутствующей структуры данных в объекте. Изменениеinitдоself = [self init];вызываетNSDateFormatterинициализация должна произойти, иsetLocaleснова счастлив.вот "окончательный" источник для категории .м:
#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
вместо подклассов, вы можете создать
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 решит добавить такой метод в a будущая версия ОС.
если вы всегда используете один и тот же формат(ы) даты, вы также можете добавить методы категории, которые возвращают одноэлементные экземпляры с определенными конфигурациями (что-то вроде
+sharedRFC3339DateFormatter). Однако имейте в виду, чтоNSDateFormatterне является потокобезопасным, и вы должны использовать замки или@synchronizedблоки, когда вы используете один и тот же экземпляр из нескольких потоков.
могу ли я предложить что-то совершенно другое, потому что, честно говоря, все это несколько спускается в кроличью нору.
вы должны использовать один
NSDateFormatterсdateFormatаlocaleвынужденыen_US_POSIXдля получения дат (от серверов / API).тогда вы должны использовать другой
NSDateFormatterдля пользовательского интерфейса, который вы выберитеtimeStyle/dateStyleсвойства - таким образом, у вас нет явногоdateFormatустановить самостоятельно, таким образом ложно предполагая, что формат будем использовать.это означает, что пользовательский интерфейс управляется пользовательскими настройками (am/pm vs 24 часа и строки даты, отформатированные правильно для выбора пользователя-из настроек iOS), тогда как даты, которые "входят" в ваше приложение, всегда "анализируются" правильно в
NSDateдля вас, чтобы использовать.
вот решение этой проблемы в версии swift. В swift мы можем использовать расширение вместо категории. Итак, здесь я создал расширение для 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))")
попробуйте этот....
-(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; }
Comments