Know when to use short or full month names

1.7k views Asked by At

Under iOS 7 or 8, the stock Calendar app does something that I have been unable to figure out.

Under some locales, such as en_US, the Calendar app shows the short (3-letter) month names.

Under other locales, such as de_DE, the Calendar app shows the full month names. Interestingly, the locale en_DE shows the short month names so it seems to be tied to the language more than the region format.

What I can't figure out is how to know which month format to use.

Regardless of my device's locale, NSDateFormatter standaloneShortMonthSymbols gives me the 3-letter month names and NSDateFormatter standaloneMonthSymbols gives me the full month names.

Is also tried:

NSString *monthformat = [NSDateFormatter dateFormatFromTemplate:@"LLL" options:0 locale:[NSLocale currentLocale]];

and that gives back the same LLL for both en_US and de_DE.

Looking at NSLocale there doesn't appear to be any value that determines whether to use short or full month names.

There doesn't appear to be anything in NSCalendar, NSDateFormatter, or NSLocale to help determine which month format to use.

Does anyone have any idea how to make this determination?

Update:

I thought I found a solution but it doesn't work for all locales that I tried. I ran the following code with various locales to see if I could find anything in common between locales that show the short and long months names in the Calendar app:

NSLocale *locale = [NSLocale currentLocale];
NSString *locid = [locale localeIdentifier];
NSLog(@"Locale = %@", locid);

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
NSLog(@"monthSymbols = %@", [formatter monthSymbols]);
NSLog(@"shortMonthSymbols = %@", [formatter shortMonthSymbols]);
NSLog(@"veryShortMonthSymbols = %@", [formatter veryShortMonthSymbols]);
NSLog(@"monthStandaloneSymbols = %@", [formatter standaloneMonthSymbols]);
NSLog(@"shortStandaloneMonthSymbols = %@", [formatter shortStandaloneMonthSymbols]);
NSLog(@"veryShortStandaloneMonthSymbols = %@", [formatter veryShortStandaloneMonthSymbols]);

NSDate *date = [NSDate date];
[formatter setDateStyle:NSDateFormatterShortStyle];
NSLog(@"short date style: %@", [formatter stringFromDate:date]);
[formatter setDateStyle:NSDateFormatterMediumStyle];
NSLog(@"medium date style: %@", [formatter stringFromDate:date]);
[formatter setDateStyle:NSDateFormatterLongStyle];
NSLog(@"long date style: %@", [formatter stringFromDate:date]);
[formatter setDateStyle:NSDateFormatterFullStyle];
NSLog(@"full date style: %@", [formatter stringFromDate:date]);

[formatter setDateStyle:NSDateFormatterNoStyle];
[formatter setDateFormat:@"M"];
NSLog(@"M date format: %@", [formatter stringFromDate:date]);
[formatter setDateFormat:@"MM"];
NSLog(@"MM date format: %@", [formatter stringFromDate:date]);
[formatter setDateFormat:@"MMM"];
NSLog(@"MMM date format: %@", [formatter stringFromDate:date]);
[formatter setDateFormat:@"MMMM"];
NSLog(@"MMMM date format: %@", [formatter stringFromDate:date]);
[formatter setDateFormat:@"MMMMM"];
NSLog(@"MMMMM date format: %@", [formatter stringFromDate:date]);
[formatter setDateFormat:@"L"];
NSLog(@"L date format: %@", [formatter stringFromDate:date]);
[formatter setDateFormat:@"LL"];
NSLog(@"LL date format: %@", [formatter stringFromDate:date]);
[formatter setDateFormat:@"LLL"];
NSLog(@"LLL date format: %@", [formatter stringFromDate:date]);
[formatter setDateFormat:@"LLLL"];
NSLog(@"LLLL date format: %@", [formatter stringFromDate:date]);
[formatter setDateFormat:@"LLLLL"];
NSLog(@"LLLLL date format: %@", [formatter stringFromDate:date]);

I had tested with en_US, en_GB, es_ES, de_DE, fr_FR, and it_IT. The French and German locales show the full month name in the Calendar app while the rest show the short name.

The one thing that looked promising with the test code is that only the French and German locales have a period at the end of the shortMonthSymbols.

So then I ran the following code to find all locales that use punctuation in the short month symbols and those that don't:

NSMutableArray *hasDot = [[NSMutableArray alloc] init];
NSMutableArray *noDot = [[NSMutableArray alloc] init];
NSCharacterSet *letters = [NSCharacterSet letterCharacterSet];
NSArray *locales = [NSLocale availableLocaleIdentifiers];
for (NSString *locid in locales) {
    NSLocale *locale = [NSLocale localeWithLocaleIdentifier:locid];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setLocale:locale];
    NSArray *shortNames = [formatter shortMonthSymbols];
    //NSLog(@"locale: %@, short names: %@", locid, shortNames[10]);
    NSString *nov = shortNames[10];
    unichar char1 = [nov characterAtIndex:0];
    unichar charN = [nov characterAtIndex:nov.length - 1];
    if ([letters characterIsMember:char1] && [letters characterIsMember:charN]) {
        [noDot addObject:locid];
    } else {
        [hasDot addObject:locid];
    }
}

NSLog(@"no dot: %@", [noDot sortedArrayUsingSelector:@selector(compare:)]);
NSLog(@"has dot: %@", [hasDot sortedArrayUsingSelector:@selector(compare:)]);

Scanning through the results I saw that the Dutch locales used a period in the short month symbols. But a quick test of the Calendar app revealed that the Calendar app showed short month names when the device was set to Dutch (nl_NL). Ugh.

Update 2:

I've tested a few more locales. The following show long month names:

fr_FR, de_DE, ru_RU, sv_SE (actually all locales for each of these languages)

the following (and I'm sure many more) show the short month:

en_US, en_GB, es_ES, it_IT, nl_NL, ca_ES, uk_UA, ro_RO (actually all locales for each of these languages)

1

There are 1 answers

7
Léo Natan On BEST ANSWER

Once every so often, there comes a question worth looking into. Rick, for you I debugged the Calendar app (can be done using attaching to MobileCal process). It all comes down to EventKitUI`CurrentLocaleRequiresUnabbrevatedMonthNames which answers the desired question.

Let's look at its disassembly:

EventKitUI`CurrentLocaleRequiresUnabbrevatedMonthNames:
0x102c6bec7:  pushq  %rbp
0x102c6bec8:  movq   %rsp, %rbp
0x102c6becb:  pushq  %r15
0x102c6becd:  pushq  %r14
0x102c6becf:  pushq  %rbx
0x102c6bed0:  subq   $0xb8, %rsp
0x102c6bed7:  movq   0x14a3aa(%rip), %r15      ; (void *)0x0000000104e93070: __stack_chk_guard
0x102c6bede:  movq   (%r15), %rax
0x102c6bee1:  movq   %rax, -0x20(%rbp)
0x102c6bee5:  cmpq   $0x0, 0x1c01fb(%rip)      ; CurrentLocaleRequiresUnabbrevatedMonthNames.usesFullLengthMonthNames + 6
0x102c6beed:  je     0x102c6beff               ; CurrentLocaleRequiresUnabbrevatedMonthNames + 56
0x102c6beef:  movb   0x1c01eb(%rip), %al       ; CurrentLocaleRequiresUnabbrevatedMonthNames.hasChecked
0x102c6bef5:  xorb   $0x1, %al
0x102c6bef7:  testb  $0x1, %al
0x102c6bef9:  je     0x102c6c0d6               ; CurrentLocaleRequiresUnabbrevatedMonthNames + 527
0x102c6beff:  movq   0x1b583a(%rip), %rdi      ; (void *)0x00000001025dae58: NSLocale
0x102c6bf06:  movq   0x1aef23(%rip), %rsi      ; "currentLocale"
0x102c6bf0d:  movq   0x14a524(%rip), %r14      ; (void *)0x0000000104945000: objc_msgSend
0x102c6bf14:  callq  *%r14
0x102c6bf17:  movq   %rax, %rdi
0x102c6bf1a:  callq  0x102d29920               ; symbol stub for: objc_retainAutoreleasedReturnValue
0x102c6bf1f:  movq   %rax, %rbx
0x102c6bf22:  movq   0x14a227(%rip), %rax      ; (void *)0x00000001025a3cd8: NSLocaleLanguageCode
0x102c6bf29:  movq   (%rax), %rdx
0x102c6bf2c:  movq   0x1ae12d(%rip), %rsi      ; "objectForKey:"
0x102c6bf33:  movq   %rbx, %rdi
0x102c6bf36:  callq  *%r14
0x102c6bf39:  movq   %rax, %rdi
0x102c6bf3c:  callq  0x102d29920               ; symbol stub for: objc_retainAutoreleasedReturnValue
0x102c6bf41:  movq   %rax, %r14
0x102c6bf44:  movq   %rbx, %rdi
0x102c6bf47:  callq  *0x14a4f3(%rip)           ; (void *)0x00000001049429b0: objc_release
0x102c6bf4d:  movq   0x1c0194(%rip), %rdi      ; __languagesRequiringUnabbreviatedMonthNames
0x102c6bf54:  testq  %rdi, %rdi
0x102c6bf57:  jne    0x102c6c0b0               ; CurrentLocaleRequiresUnabbrevatedMonthNames + 489
0x102c6bf5d:  leaq   0x15425c(%rip), %rax      ; @"ru"
0x102c6bf64:  movq   %rax, -0xd0(%rbp)
0x102c6bf6b:  leaq   0x1524ce(%rip), %rax      ; @"de"
0x102c6bf72:  movq   %rax, -0xc8(%rbp)
0x102c6bf79:  leaq   0x154260(%rip), %rax      ; @"fr"
0x102c6bf80:  movq   %rax, -0xc0(%rbp)
0x102c6bf87:  leaq   0x154272(%rip), %rax      ; @"fi"
0x102c6bf8e:  movq   %rax, -0xb8(%rbp)
0x102c6bf95:  leaq   0x154284(%rip), %rax      ; @"pt"
0x102c6bf9c:  movq   %rax, -0xb0(%rbp)
0x102c6bfa3:  leaq   0x154296(%rip), %rax      ; @"no"
0x102c6bfaa:  movq   %rax, -0xa8(%rbp)
0x102c6bfb1:  leaq   0x1542a8(%rip), %rax      ; @"nb"
0x102c6bfb8:  movq   %rax, -0xa0(%rbp)
0x102c6bfbf:  leaq   0x1542ba(%rip), %rax      ; @"nn"
0x102c6bfc6:  movq   %rax, -0x98(%rbp)
0x102c6bfcd:  leaq   0x1542cc(%rip), %rax      ; @"sv"
0x102c6bfd4:  movq   %rax, -0x90(%rbp)
0x102c6bfdb:  leaq   0x1542de(%rip), %rax      ; @"he"
0x102c6bfe2:  movq   %rax, -0x88(%rbp)
0x102c6bfe9:  leaq   0x1542f0(%rip), %rax      ; @"th"
0x102c6bff0:  movq   %rax, -0x80(%rbp)
0x102c6bff4:  leaq   0x154305(%rip), %rax      ; @"hi"
0x102c6bffb:  movq   %rax, -0x78(%rbp)
0x102c6bfff:  leaq   0x15431a(%rip), %rax      ; @"bn"
0x102c6c006:  movq   %rax, -0x70(%rbp)
0x102c6c00a:  leaq   0x15432f(%rip), %rax      ; @"mr"
0x102c6c011:  movq   %rax, -0x68(%rbp)
0x102c6c015:  leaq   0x154344(%rip), %rax      ; @"ur"
0x102c6c01c:  movq   %rax, -0x60(%rbp)
0x102c6c020:  leaq   0x154359(%rip), %rax      ; @"te"
0x102c6c027:  movq   %rax, -0x58(%rbp)
0x102c6c02b:  leaq   0x15436e(%rip), %rax      ; @"ta"
0x102c6c032:  movq   %rax, -0x50(%rbp)
0x102c6c036:  leaq   0x154383(%rip), %rax      ; @"gu"
0x102c6c03d:  movq   %rax, -0x48(%rbp)
0x102c6c041:  leaq   0x154398(%rip), %rax      ; @"kn"
0x102c6c048:  movq   %rax, -0x40(%rbp)
0x102c6c04c:  leaq   0x1543ad(%rip), %rax      ; @"ml"
0x102c6c053:  movq   %rax, -0x38(%rbp)
0x102c6c057:  leaq   0x1543c2(%rip), %rax      ; @"ne"
0x102c6c05e:  movq   %rax, -0x30(%rbp)
0x102c6c062:  leaq   0x1543d7(%rip), %rax      ; @"pa"
0x102c6c069:  movq   %rax, -0x28(%rbp)
0x102c6c06d:  movq   0x1b55ec(%rip), %rdi      ; (void *)0x00000001025d9cd8: NSArray
0x102c6c074:  movq   0x1ae5cd(%rip), %rsi      ; "arrayWithObjects:count:"
0x102c6c07b:  leaq   -0xd0(%rbp), %rdx
0x102c6c082:  movl   $0x16, %ecx
0x102c6c087:  callq  *0x14a3ab(%rip)           ; (void *)0x0000000104945000: objc_msgSend
0x102c6c08d:  movq   %rax, %rdi
0x102c6c090:  callq  0x102d29920               ; symbol stub for: objc_retainAutoreleasedReturnValue
0x102c6c095:  movq   0x1c004c(%rip), %rdi      ; __languagesRequiringUnabbreviatedMonthNames
0x102c6c09c:  movq   %rax, 0x1c0045(%rip)      ; __languagesRequiringUnabbreviatedMonthNames
0x102c6c0a3:  callq  *0x14a397(%rip)           ; (void *)0x00000001049429b0: objc_release
0x102c6c0a9:  movq   0x1c0038(%rip), %rdi      ; __languagesRequiringUnabbreviatedMonthNames
0x102c6c0b0:  movq   0x1ae6c1(%rip), %rsi      ; "containsObject:"
0x102c6c0b7:  movq   %r14, %rdx
0x102c6c0ba:  callq  *0x14a378(%rip)           ; (void *)0x0000000104945000: objc_msgSend
0x102c6c0c0:  movb   %al, 0x1c001b(%rip)       ; CurrentLocaleRequiresUnabbrevatedMonthNames.usesFullLengthMonthNames
0x102c6c0c6:  movb   $0x1, 0x1c0013(%rip)      ; __overlayCalendarGeneration + 7
0x102c6c0cd:  movq   %r14, %rdi
0x102c6c0d0:  callq  *0x14a36a(%rip)           ; (void *)0x00000001049429b0: objc_release
0x102c6c0d6:  movb   0x1c0005(%rip), %al       ; CurrentLocaleRequiresUnabbrevatedMonthNames.usesFullLengthMonthNames
0x102c6c0dc:  movq   (%r15), %rcx
0x102c6c0df:  cmpq   -0x20(%rbp), %rcx
0x102c6c0e3:  jne    0x102c6c0f3               ; CurrentLocaleRequiresUnabbrevatedMonthNames + 556
0x102c6c0e5:  addq   $0xb8, %rsp
0x102c6c0ec:  popq   %rbx
0x102c6c0ed:  popq   %r14
0x102c6c0ef:  popq   %r15
0x102c6c0f1:  popq   %rbp
0x102c6c0f2:  retq   
0x102c6c0f3:  callq  0x102d29a1c               ; symbol stub for: __stack_chk_fail

As you can see, it creates an array of locales that require unabbreviated month names. It then compares if the current locale is one of these locales.

Hardcoded in the code.

For abbreviated months, it uses the LLL format (as seen in EventKitUI`CalStringForMonth), and for unabbreviated months, it uses the LLLL format (as seen in EventKitUI`CalLongStringForMonth).

Cheers