Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can you properly support the iOS 12 hour / 24 hour time override in Date & Time settings for DateFormatters?

If your iOS device is set to use a region that defaults to 12 hour time (e.g. United States) and you have a DateFormatter set up like this:

var dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateFormat = "h:mm a"
    
    return formatter
}()

Then it will output the time in a 24 hour format if the "24-Hour Time" switch is turned ON in Date & Time in Settings in iOS.

If your custom date format is "H:mm", i.e. 24 hour, and you're still in a default-12h region, then it will output 24 hour time regardless of the on/off state of the 24 hour switch.

Ok, so fine, simple, I'll just use "h:mm a" and then users will see whatever time displayed according to the format they prefer. BUT if you change your region to a region that uses 24 hour time by default, then the behaviour is reversed! If you have "H:mm" and the 24-hour time switch is OFF it will change it to 12 hour time ("h:mm a").

Yes, you can usually avoid this by using the time style (formatter.timeStyle = .short, for example) instead of using custom date formats, but what are you supposed to do if you need a custom date format and you want to respect a user's settings? (For example, if you need to insert some custom characters in the string or just want to have the components in a non-standard order)

I suppose the only way to have your cake and eat it too here is to check in advance what the user's preference is (which you can't really do directly, as far as I can tell) or maybe what the locale's default is (I don't see any way to do that though) and then adjust the format accordingly…

I made this Xcode project to demonstrate the issue more clearly (obviously it must be run on a device as there is no 24 hour time switch on the simulator).

I am aware there are various workarounds, but I don't understand what Apple expects you to do if you're making an app that uses custom date/time formats for your UI and has users from different regions in the world? Am I missing something or is this a flaw in iOS?

Note: This question is specifically about converting dates to strings for display purposes, so I want to respect the user's locale and other settings whenever possible. I am aware you can set the locale to en_POSIX etc. but that doesn't solve the problem. I also would prefer to make this as seamless as possible and take advantage of whatever is built in, but that doesn't seem to be possible here unless you only target users in regions that have the same default 24 hour time settings…

like image 649
shim Avatar asked Apr 19 '18 18:04

shim


People also ask

How do I change time from 24 hour to 12 hour settings on iPhone?

Open Settings. Choose General. Select Date & Time. Turn off 24-Hour Time.

How do you convert 12 hours to 24 hours in Swift?

import Foundation let afternoonHour = try Date("2021-10-23T02:37:12Z", strategy: . iso8601) // this format to 24 hour let dateFormatter = DateFormatter() dateFormatter. dateFormat = "HH:mm:ss" let twentyFourHour = dateFormatter. string(from: afternoonHour) print(twentyFourHour) // with the new Date.


1 Answers

From the documentation for dateFormat on DateFormatter:

You should only set this property when working with fixed format representations, as discussed in Working With Fixed Format Date Representations. For user-visible representations, you should use the dateStyle and timeStyle properties, or the setLocalizedDateFormatFromTemplate(_:) method if your desired format cannot be achieved using the predefined styles; both of these properties and this method provide a localized date representation appropriate for display to the user.

I still don't really understand why Foundation bothers to modify the date format if you're not using a fixed locale on your date formatter, but it seems the documentation does not recommend using the custom date format for non-fixed format representations.

setLocalizedDateFormatFromTemplate appears to be what you need if you can't just rely on dateStyle and timeStyle, although it doesn't appear to automatically change with a user's 24 hour time setting, so one could check to see if 24 hour time is on before specifying a template…

This method is supposed to allow you to specify a format without losing out on the formatting that is specific to each locale.

EDIT response from Apple Developer Relations (same idea, but more detail):

The correct way to handle this is to avoid specifying "hh" or "HH" based on locale and instead using the "jj" skeleton symbol in conjunction with -[NSDateFormatter setLocalizedDateFormatFromTemplate:].

From the "hour" section of the Date Field Symbol Table (https://www.unicode.org/reports/tr35/tr35-dates.html#dfst-hour):

Input skeleton symbol In such a context, it requests the preferred hour format for the locale (h, H, K, or K), as determined by the preferred attribute of the hours element in the supplemental data.

By using "jj" you can get a format which best matches the user's current settings:

NSDateFormatter *formatter = [NSDateFormatter new];
[formatter setLocalizedDateFormatFromTemplate:@"jj:mm"];
NSLog(@"%@: 'jj:mm' => '%@' ('%@')", formatter.locale.localeIdentifier, formatter.dateFormat, [formatter stringFromDate:[NSDate date]]);

results in

  • en_US (12-hour): "en_US: 'jj:mm' => 'h:mm a' ('1:44 PM')
  • en_US (24-hour): "en_US: 'jj:mm' => 'HH:mm' ('13:44')
  • en_GB (12-hour): "en_GB: 'jj:mm' => 'h:mm a' ('1:44 pm')
  • en_GB (24-hour): "en_GB: 'jj:mm' => 'HH:mm' ('13:44')

This allows you to match your preferred format while keeping with the user's preferred time settings.

like image 152
shim Avatar answered Sep 18 '22 05:09

shim