Take the 2-minute tour ×

I thought I saw something answering this on SO recently but now I can't find it. Here is the code I am using now to determine if settings are for 24 hour time display. It works for me in the US, but I don't know if it will work in all locales. Is this sufficient or is there a better way to find out the current setting for this?

+(BOOL) use24HourClock
    BOOL using24HourClock = NO;

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];    
    [dateFormatter setLocale: [NSLocale currentLocale]];    
    [dateFormatter setDateStyle:kCFDateFormatterNoStyle];
    [dateFormatter setTimeStyle:kCFDateFormatterShortStyle];    
    // get date/time (1Jan2001 0000UTC)
    NSDate* midnight = [[NSDate alloc] initWithTimeIntervalSinceReferenceDate:0];   
    NSString* dateString = [dateFormatter stringFromDate: midnight];
    // dateString will either be "15:00" or "16:00" (depending on DST) or
    // it will be "4:00 PM" or "3:00 PM" (depending on DST)
    using24HourClock = ([dateString length] == 5);
    [midnight release];
    [dateFormatter release];    

    return using24HourClock;
share|improve this question
add comment

3 Answers

up vote 32 down vote accepted

Here's the best way to do it:

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

NSRange containsA = [formatStringForHours rangeOfString:@"a"];
BOOL hasAMPM = containsA.location != NSNotFound;

This uses a special date template string called "j". According to the ICU Spec, "j"...

requests the preferred hour format for the locale (h, H, K, or k), as determined by whether h, H, K, or k is used in the standard short time format for the locale. In the implementation of such an API, 'j' must be replaced by h, H, K, or k before beginning a match against availableFormats data. Note that use of 'j' in a skeleton passed to an API is the only way to have a skeleton request a locale's preferred time cycle type (12-hour or 24-hour).

That last sentence is important. It "is the only way to have a skeleton request a locale's preferred time cycle type". Since NSDateFormatter and NSCalendar are built on the ICU library, the same holds true here.

share|improve this answer
Nice 3-liner. I’m wondering, what exactly does the “a” stand for? Is it possible that some occult setting could change the time-cycle symbol to “AM”? Should the range line be changed to ` NSRange containsA = [formatStringForHours rangeOfString:@"a" options:NSCaseInsensitiveSearch];`? –  Wienke Sep 30 '12 at 16:42
@Wienke "a" is the ICU symbol for the AM/PM designator. It can't ever be "A", because ICU uses "A" to mean something totally different (milliseconds in the day). –  Dave DeLong Sep 30 '12 at 20:52
+1, I feel way more comfortable searching for a character that has a defined fixed meaning and will not change depending on the locale. –  DarkDust Feb 11 '13 at 9:09
I don't think the snippet will always work as expected. See my answer to the question: stackoverflow.com/a/23586580/3617012 –  iOSX May 11 at 21:38
add comment

I've just asked a similar question on here and I've managed to figure out a decent way of determining this with a little function I've added to a category of NSLocale. It appears to be pretty accurate and haven't found any problems with it while testing with several regions.

@implementation NSLocale (Misc)
- (BOOL)timeIs24HourFormat {
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setLocale:self];
    [formatter setDateStyle:NSDateFormatterNoStyle];
    [formatter setTimeStyle:NSDateFormatterShortStyle];
    NSRange amRange = [dateString rangeOfString:[formatter AMSymbol]];
    NSRange pmRange = [dateString rangeOfString:[formatter PMSymbol]];
    BOOL is24Hour = (amRange.location == NSNotFound && pmRange.location == NSNotFound);
    [formatter release];
    return is24Hour;

Hope this helps!

share|improve this answer
There is a small issue with this category method. You should add a line [formatter setLocale:self]; just after the creation of the formatter, otherwise it only uses the current locale. –  Scott Little Apr 11 '11 at 11:02
Oh yes, good spot! –  Michael Waterfall Apr 11 '11 at 12:23
No Problem. BTW, great solution, I'm happy to use it. –  Scott Little Apr 11 '11 at 13:38
This will work, but it's more work than you need to go through. –  Dave DeLong Jul 26 '12 at 2:20
add comment

I gave Dave's answer a shot, but when testing with German locale - a region where 24 hour time cycle is used - I realized it would be better to check for H or k in the format string instead of a, and that quoted substrings should be ignored.

For German locale I get back "HH a" when requesting the date format for template "j" in the iOS simulator, and "HH 'Uhr'" on the device. (Do not understand why there is a difference at all.)

Here is a category for NSLocale that I use to check for 24 hour time cycle:

@interface NSLocale (TwentyFourHourTimeCycleCheck)

- (BOOL)uses24HourTimeCycle;


@implementation NSLocale (TwentyFourHourTimeCycleCheck)

- (BOOL)uses24HourTimeCycle
    BOOL uses24HourTimeCycle = NO;

    NSString *formatStringForHours = [NSDateFormatter dateFormatFromTemplate:@"j" options:0 locale:self];
    NSScanner *symbolScanner = [NSScanner scannerWithString:formatStringForHours];

    NSString *singleQuoteCharacterString = @"'";

    // look for single quote characters, ignore those and the enclosed strings
    while ( ![symbolScanner isAtEnd] ) {

        NSString *scannedString = @"";
        [symbolScanner scanUpToString:singleQuoteCharacterString intoString:&scannedString];

        // if 'H' or 'k' is found the locale uses 24 hour time cycle, and we can stop scanning
        if ( [scannedString rangeOfString:@"H"].location != NSNotFound ||
             [scannedString rangeOfString:@"k"].location != NSNotFound ) {

            uses24HourTimeCycle = YES;


        // skip the single quote
        [symbolScanner scanString:singleQuoteCharacterString intoString:NULL];
        // skip everything up to and including the next single quote
        [symbolScanner scanUpToString:singleQuoteCharacterString intoString:NULL];
        [symbolScanner scanString:singleQuoteCharacterString intoString:NULL];

    return uses24HourTimeCycle;

share|improve this answer
add comment

Your Answer


