How can I determine if iPhone is set for 12 hour or 24 hour time display?
Asked Answered
C

6

43

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;
}
Cheeseburger answered 18/12, 2009 at 18:36 Comment(0)
H
89

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;

in Swift:

let formatString: NSString = NSDateFormatter.dateFormatFromTemplate("j", options: 0, locale: NSLocale.currentLocale())!
let hasAMPM = formatString.containsString("a")

Swift 4:

let formatString = DateFormatter.dateFormat(fromTemplate: "j", options: 0, locale: Locale.current)!
let hasAMPM = formatString.contains("a")

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.

Hickox answered 25/7, 2012 at 23:48 Comment(7)
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];`?Suite
@Suite "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).Hickox
+1, I feel way more comfortable searching for a character that has a defined fixed meaning and will not change depending on the locale.Closeup
I don't think the snippet will always work as expected. See my answer to the question: https://mcmap.net/q/276639/-how-can-i-determine-if-iphone-is-set-for-12-hour-or-24-hour-time-displayBlowfly
Had a further look into the ICU Spec after reading @Blowfly comment - found this passage "The preference of 12 hour versus 24 hour for the locale can be derived from the Time Data. If the hour symbol is "h" or "K" then the format is 12 hour; otherwise it is 24 hour." Might be a safer check?Germane
The posted "Swift 4" solution might be fragile. I just test with one of many locales - de_DE (German is using 24-hour format). The result is HH 'Uhr'. I believe "Uhr" means clock in German. There are so many different languages, and there is chance that other language can collide with formatString.contains("a") checking.Bradberry
@Jason's answer suggesting to use hourCycle in Locale seems like a better solution if you are running iOS 16 or later. But I haven't tried it myself yet. https://mcmap.net/q/276639/-how-can-i-determine-if-iphone-is-set-for-12-hour-or-24-hour-time-displayCheeseburger
L
22

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];
    NSString *dateString = [formatter stringFromDate:[NSDate date]];
    NSRange amRange = [dateString rangeOfString:[formatter AMSymbol]];
    NSRange pmRange = [dateString rangeOfString:[formatter PMSymbol]];
    BOOL is24Hour = (amRange.location == NSNotFound && pmRange.location == NSNotFound);
    [formatter release];
    return is24Hour;
}
@end

Hope this helps!

Litalitany answered 29/12, 2009 at 1:33 Comment(3)
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.Salaam
No Problem. BTW, great solution, I'm happy to use it.Salaam
This will work, but it's more work than you need to go through.Hickox
B
9

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;

@end

@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;

            break;
        }

        // 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;
}

@end
Blowfly answered 10/5, 2014 at 21:36 Comment(2)
Thanks for sharing this, it indeed says "HH 'Uhr" when on 24 hour clock in german locale. So with this we are suggesting a possibility that some language might return something that contains "a" even in the 24 hour format right?Haematocryal
I agree with @MarkoHlebar Checking for "containing letter a" seems like a pretty fragile solution. In fact, German is returning substring 'Uhr' from data format template "j". There can be other type of substring from other locale.Bradberry
S
1

Since iOS 16 and macOS 13 you can also look at the current Locale's hourCycle value. This might avoid some of the potential issues picking the best formatting codes to query and test from DateFormatter:

if Locale.current.hourCycle == .oneToTwelve 
    || Locale.current.hourCycle == .zeroToEleven {

    // Do something for 12-hour
    ...
}

Note there are 4 different possible .hourCycle values. For instance, one minute after midnight could be written four ways: 0:01, 0:01am, 12:01am, and 24:01.

And, according to both Apple docs and a quick test (iOS 16.7.2), the .hourCycle value does also consider any specific 12/24-hour time preference the user may have set.

Steelmaker answered 1/4 at 16:3 Comment(0)
N
0

In Swift 4:

let formatString: String = DateFormatter.dateFormat(fromTemplate: "j", options: 0, locale: Locale.current)!
let hasAMPM = formatString.contains("a")
Nimiety answered 13/10, 2017 at 21:17 Comment(0)
P
-1

I my case I simply set timeStyle = .short for dateFormatter. (Swift)

let yourCustomDate = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "hh:mm a"
dateFormatter.amSymbol = "am"
dateFormatter.pmSymbol = "pm"
dateFormatter.timeStyle = .short
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
let dateString = dateFormatter.string(from: yourCustomDate)

As result, dateString will be automatically converted to 12 or 24 time format. For example: 6:30 pm in 12-hours format and 18:30 in 24-hours format.

Pompei answered 6/7, 2020 at 14:36 Comment(1)
This does not answer the question of how to determine programatically what the device is set for. This answers how to print time in local format, a different question.Cheeseburger

© 2022 - 2024 — McMap. All rights reserved.