NSDateFormatter 로케일 "feechur"를 처리하는 가장 좋은 방법은 무엇입니까?


168

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

최종 답변

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


5
모쉐, 왜 제목을 편집하기로 선택했는지 모르겠습니다. "Feechur"는 해당 기술 분야에서 합법적 인 용어로 30 년 정도 사용되어 왔으며, 저자가 인정하기를 거부하더라도 버그로 간주하기에는 충분하지 않은 일부 소프트웨어의 측면 또는 기능을 의미합니다.
핫 릭

1
문자열을 날짜로 변환 할 때 문자열은 포맷터 설명과 정확히 일치해야합니다. 이는 해당 지역의 접선 문제입니다.
bshirley

다양한 날짜 문자열은 정확하고 잘못된 다른 가능한 구성을 테스트하기 위해 존재합니다. 서식 문자열이 주어지면 일부가 유효하지 않다는 것을 알고 있습니다.
핫 릭

다른 값으로 실험 - (NSDateFormatterBehavior)formatterBehavior했습니까?
bshirley

실험하지 않았습니다. 사양은 iOS에서 변경할 수 있는지 여부와 모순됩니다. 주요 설명은 "iOS 참고 : iOS는 10.4+ 동작 만 지원합니다."라고 말하고 NSDateFormatterBehavior 섹션에는 두 가지 모드를 모두 사용할 수 있지만 (상수에 대해서만 이야기하는 것일 수 있습니다).
Hot Licks

답변:


67

어이 !!

때때로 당신은 "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

"NSString * dateStr = @"2014-04-05T04 : 00 : 00.000Z ";의 날짜 포맷터는 무엇입니까? ?
요크 요원.


@tbag-NSDateFormatter에 대한 질문이 아니십니까?
핫 릭

@HotLicks 그래 내 나쁜. NSDateFormatter를 고기로 만듭니다.
tbag

@tbag-사양은 무엇을 말합니까?
핫 릭

41

서브 클래 싱 대신 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여러 스레드에서 동일한 인스턴스를 사용하는 경우 잠금 또는 블록 을 사용해야 합니다.


정적 NSLocale (내 제안과 같이)이 범주에서 작동합니까?
Hot Licks

예, 카테고리에서도 작동합니다. 예제를 더 단순하게 만들기 위해 생략했습니다.
omz

흥미롭게도 카테고리 접근 방식이 작동하지 않습니다. 카테고리 메소드가 실행되고 다른 버전과 정확히 동일한 로케일을 얻습니다 (카테고리 버전을 먼저 다시 실행합니다). 어떻게 든 setLocale이 "취득"하지 않는 것 같습니다.
Hot Licks

이 방법이 왜 효과가 없는지 알아 보는 것이 흥미로울 것입니다. 아무도 더 나은 것을 만들지 않으면이 명백한 버그에 대한 최고의 설명으로 현상금을 수여합니다.
Hot Licks

글쎄, 나는 이것에 명백한 노력을 한 유일한 사람이기 때문에 OMZ에게 현상금을 수여합니다.
핫릭

7

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

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

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

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


때로는이 체계가 작동하지만 때로는 그렇지 않습니다. 한 가지 위험은 메소드가 포맷터의 날짜 형식을 수정해야 할 수 있으며, 그렇게하면 날짜 형식화 작업 중일 때 호출 한 코드에 의해 설정된 형식을 변경해야합니다. 시간대를 반복해서 변경해야하는 다른 시나리오가 있습니다.
핫 릭

timeZone포맷터의 값을 변경 하는 것이이 체계를 방해 하는 이유를 모르겠습니다. 정교 할 수 있습니까? 또한 형식을 변경하지 않아도됩니다. 그렇게하려면 "가져 오기"포맷터에서 발생하므로 별도의 포맷터가 필요합니다.
Daniel

전역 객체의 상태를 변경할 때마다 위험합니다. 다른 사람들도 사용하고 있다는 것을 잊기 쉽습니다.
핫 릭

3

신속한 버전의 해당 문제에 대한 해결책은 다음과 같습니다. 신속하게 카테고리 대신 확장을 사용할 수 있습니다. 그래서 여기에 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))")
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.