[ios] 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, 내가 잘못하고 있거나 버그입니까? )

(그리고 https://developer.apple.com/library/content/qa/qa1480/_index.html 참조 )

분명히 애플은 이것을 “BAD”라고 선언했다-Broken As Designed, 그들은 그것을 고치지 않을 것이다.

우회는 분명히 특정 지역, 일반적으로 미국에 대한 날짜 형식 기의 로캘을 설정하는 것이지만 약간 지저분합니다.

NSLocale *loc = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
[df setLocale: loc];
[loc release];

onsies-twosies에서 나쁘지는 않지만 약 10 개의 다른 앱을 다루고 있으며, 내가 본 첫 번째 앱에는이 시나리오의 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

카테고리 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)

12/24 스위치가 12로 설정된 전화기 [iPod Touch로 확인]가 영국으로 설정되어 있습니다. 두 결과에 분명한 차이가 있으며 범주 버전이 잘못되었다고 판단합니다. 범주 버전의 로그가 실행되고 있으며 코드에 배치 된 중지가 발생 했으므로 코드가 사용되지 않는 경우가 아닙니다.

현상금 업데이트 :

해당 답변을받지 못했지만 바운티 마감일을 하루나 이틀 더 연장 할 것입니다.

바운티는 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 메소드에 대해 “이상한”것이있는 것 같습니다.

최종 답변

아래에서 허용되는 답변을 참조하십시오 .



답변

어이 !!

때때로 당신은 “Aha !!” 순간, 때로는 “Duh !!”에 가깝습니다. 이것은 후자입니다. initWithSafeLocale“슈퍼” 의 범주에서 init로 코딩되었습니다 self = [super init];. 이것은의 슈퍼 클래스를 inits NSDateFormatter하지만하지 않습니다 initNSDateFormatter 개체 자체를.

이 초기화를 건너 뛰면 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


답변

서브 클래 싱 대신 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이 향후 버전의 OS에 이러한 방법을 추가하기로 결정한 경우를 대비하여 이름 충돌을 피하기 위해 카테고리 방법을 접두사로 사용할 수 있습니다.

항상 같은 날짜 형식을 사용하는 경우 특정 구성 (예 :와 같은 +sharedRFC3339DateFormatter)으로 단일 인스턴스를 반환하는 범주 메서드를 추가 할 수도 있습니다 . 그러나 NSDateFormatter스레드 안전하지 않으므로 @synchronized여러 스레드에서 동일한 인스턴스를 사용하는 경우 잠금 또는 블록 을 사용해야 합니다.


답변

솔직히 말해서 토끼 구멍이 다소 떨어지기 때문에 완전히 다른 것을 제안 할 수 있습니다.

당신은 하나를 사용한다 NSDateFormatterdateFormat설정하고 locale강제로en_US_POSIX (서버 / API를에서) 날짜를 수신.

그런 다음 / 속성 NSDateFormatter을 설정할 UI에 다른 것을 사용해야합니다. 이 방법으로 명시 적으로하지 않습니다.timeStyledateStyledateFormat 설정 해당 형식이 사용된다고 잘못 가정합니다.

즉, UI는 사용자 환경 설정 (am / pm vs 24 시간 및 날짜 문자열이 iOS 설정에서 사용자가 선택한 형식으로)에 의해 구동되는 반면, 앱에 “오고있는”날짜는 항상 NSDate 을 위해 당신이 사용합니다.


답변

신속한 버전의 해당 문제에 대한 해결책은 다음과 같습니다. 신속하게 카테고리 대신 확장을 사용할 수 있습니다. 그래서 여기에 DateFormatter의 확장을 만들었고 initWithSafeLocale은 관련 로케일과 함께 DateFormatter를 반환합니다. 여기서는 en_US_POSIX입니다.이 외에도 몇 가지 날짜 형성 방법이 있습니다.

  • 스위프트 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))")

답변