提问者:小点点

正确的NSCalendar初始化


我们的应用程序使用日期相当多,但此时我们只支持公历,并且一个应用程序范围内的NSCalendar实例初始化如下:

NSCalendar *appCalendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];

上述方法的文档说明“返回的日历默认为当前区域设置和默认时区。” 但是,当在区域设置为“联合王国”的设备上运行应用程序时,调用[appCalendar firstWeekday]返回值1(星期日),而不是预期的2(星期一)。 如果运行[[NSCalendar CurrentCalendal]firstWeekday],则返回正确的值2。 起初,我认为可能没有在“AppCalendar”上设置区域设置,但是日志显示它有一个区域设置,尽管它缺少countrycode等,而“CurrentCalendar”实例确实有这个区域设置,并允许它返回正确的FirstWeekday。

是否应该在从CalendarWithIdentifier返回的对象上显式地设置区域设置,如果是,这样做时有什么考虑因素吗?


共1个答案

匿名用户

文档错误:

返回的日历默认为当前区域设置和默认时区。

应该是:

返回的日历默认为系统区域设置和默认时区。

CFCalendar.c:

CFCalendarRef CFCalendarCreateWithIdentifier(CFAllocatorRef allocator, CFStringRef identifier) {
    if (allocator == NULL) allocator = __CFGetDefaultAllocator();
    __CFGenericValidateType(allocator, CFAllocatorGetTypeID());
    __CFGenericValidateType(identifier, CFStringGetTypeID());
    // return NULL until Chinese calendar is available
    if (identifier != kCFGregorianCalendar && identifier != kCFBuddhistCalendar && identifier != kCFJapaneseCalendar && identifier != kCFIslamicCalendar && identifier != kCFIslamicCivilCalendar && identifier != kCFHebrewCalendar) {
//    if (identifier != kCFGregorianCalendar && identifier != kCFBuddhistCalendar && identifier != kCFJapaneseCalendar && identifier != kCFIslamicCalendar && identifier != kCFIslamicCivilCalendar && identifier != kCFHebrewCalendar && identifier != kCFChineseCalendar) {
    if (CFEqual(kCFGregorianCalendar, identifier)) identifier = kCFGregorianCalendar;
    else if (CFEqual(kCFBuddhistCalendar, identifier)) identifier = kCFBuddhistCalendar;
    else if (CFEqual(kCFJapaneseCalendar, identifier)) identifier = kCFJapaneseCalendar;
    else if (CFEqual(kCFIslamicCalendar, identifier)) identifier = kCFIslamicCalendar;
    else if (CFEqual(kCFIslamicCivilCalendar, identifier)) identifier = kCFIslamicCivilCalendar;
    else if (CFEqual(kCFHebrewCalendar, identifier)) identifier = kCFHebrewCalendar;
//  else if (CFEqual(kCFChineseCalendar, identifier)) identifier = kCFChineseCalendar;
    else return NULL;
    }
    struct __CFCalendar *calendar = NULL;
    uint32_t size = sizeof(struct __CFCalendar) - sizeof(CFRuntimeBase);
    calendar = (struct __CFCalendar *)_CFRuntimeCreateInstance(allocator, CFCalendarGetTypeID(), size, NULL);
    if (NULL == calendar) {
    return NULL;
    }
    calendar->_identifier = (CFStringRef)CFRetain(identifier);
    calendar->_locale = NULL;
    calendar->_localeID = CFLocaleGetIdentifier(CFLocaleGetSystem());
    calendar->_tz = CFTimeZoneCopyDefault();
    calendar->_cal = NULL;
    return (CFCalendarRef)calendar;
}

_localenull初始化,_localeid用系统区域设置的区域设置标识符初始化(在iPhone和simulator上是空字符串)。 _cal设置为NULL

CFIndex CFCalendarGetFirstWeekday(CFCalendarRef calendar) {
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFIndex, calendar, firstWeekday);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (calendar->_cal) {
    return ucal_getAttribute(calendar->_cal, UCAL_FIRST_DAY_OF_WEEK);
    }
    return -1;
}

因此,因为_calNULL,所以调用__CFCalendarSetupCal

static void __CFCalendarSetupCal(CFCalendarRef calendar) {
    calendar->_cal = __CFCalendarCreateUCalendar(calendar->_identifier, calendar->_localeID, calendar->_tz);
}

它使用空字符串_localeID调用__CFCalendarCreateUcalendar

我可以在iOS 11,12&; 13.源代码是一个叫做CF-Lite的东西,但是我更进一步,拆解了实际的CoreFoundation框架,它做了同样的事情。。。

call       _CFLocaleGetSystem             ; _CFLocaleGetSystem
mov        rdi, rax                       ; argument "cf" for method _CFRetain
call       _CFRetain                      ; _CFRetain
mov        qword [r15+0x18], rax
call       _CFTimeZoneCopyDefault         ; _CFTimeZoneCopyDefault
mov        qword [r15+0x20], rax
mov        rbx, qword [r15+0x10]
mov        rdi, qword [r15+0x18]          ; argument "locale" for method _CFLocaleGetIdentifier
call       _CFLocaleGetIdentifier         ; _CFLocaleGetIdentifier
mov        rdx, qword [r15+0x20]          ; argument #3 for method ___CFCalendarCreateUCalendar
mov        rdi, rbx                       ; argument #1 for method ___CFCalendarCreateUCalendar
mov        rsi, rax                       ; argument #2 for method ___CFCalendarCreateUCalendar
call       ___CFCalendarCreateUCalendar   ; ___CFCalendarCreateUCalendar

。。。使用CFLocaleGetSystemCFLocaleGetIdentifier中的空标识符。

当您检查CFCalendarCreateWithIdentifier文档时,没有任何有关当前区域设置,时区,。。。

更有趣的是这两种方法的区别(部分讨论):

  • +CalendarWithIdentifier:
    • 它包含有关当前区域设置的信息,...
    • 对当前区域设置只字不提,...

    但没有区别,CalendarWithIdentifier:只是调用alloc&; InitWithCalendarIdentifier:

    push       rbp
    mov        rbp, rsp
    push       r14
    push       rbx
    mov        rbx, rdx
    mov        rsi, qword [0x3cb478]                       ; argument "selector" for method _objc_msgSend, @selector(alloc)
    mov        r14, qword [_objc_msgSend_390220]           ; _objc_msgSend_390220
    call       r14                                         ; Jumps to 0x553ae0 (_objc_msgSend), _objc_msgSend
    mov        rsi, qword [0x3cc768]                       ; argument "selector" for method _objc_msgSend, @selector(initWithCalendarIdentifier:)
    ...
    

    我认为这是一个文档问题,应该向苹果公司报告。