13

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…

shim
  • 9,289
  • 12
  • 69
  • 108
  • You should never use a fixed date format to display date/time to the user. You should use DateFormatter dateStyle and timeStyle. – Leo Dabus Apr 19 '18 at 19:57
  • As I said in the question, one cannot always use `dateStyle` and `timeStyle`. – shim Apr 19 '18 at 19:58
  • https://stackoverflow.com/questions/28332946/how-do-i-get-the-current-date-in-short-format-in-swift/28347285?s=1|24.3585#28347285 – Leo Dabus Apr 19 '18 at 20:02
  • Check the link above. You should only set the time or date style to short, medium, long or full and it will displayed according to the user locale and device settings – Leo Dabus Apr 19 '18 at 20:02
  • Yes, as I said I am already aware of the time styles. As I stated already in the question that isn't always an option. – shim Apr 19 '18 at 20:03
  • I didn't know the setting can rewrite dateFormat but according to the Apple's documentation, it seems it rewrites. `In iOS, the user can override the default AM/PM versus 24-hour time setting. This may cause NSDateFormatter to rewrite the format string you set.` – Ryan Apr 19 '18 at 20:49
  • Check [this](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/DataFormatting/Articles/dfDateFormatting10_4.html#//apple_ref/doc/uid/TP40002369-SW1) on `Fixed Formats` section. – Ryan Apr 19 '18 at 20:49
  • And [this](https://developer.apple.com/library/content/qa/qa1480/_index.html) can help you. – Ryan Apr 19 '18 at 20:51
  • Yes, I am aware that this is a documented "feature" of iOS. Took it for granted that that would be obvious. I also don't see how either of those links help. The documentation says the system may change your date formats, but it is vague about how it works and doesn't give any suggestions besides don't use date formats. Then I'm not really sure what the point of the system overriding your custom date formats is, if the behaviour is inconsistent such that even if it works for one region, it will not work for another region. – shim Apr 19 '18 at 20:55
  • @shim I think you didn't understand how it works. If you would like to use a fixed date format as you have in your example that doesn't get affected by the device's locale and or the device settings you need to set your date formatter locale to "en_US_POSIX", If you would like to respect the user settings and current locale you need to use date formatter date and time style to display the date localized and according to the user settings – Leo Dabus Apr 19 '18 at 21:47
  • I want to use a fixed date format that **is** affected by the device's locale such that I can present times to a user while ensuring that the formats respect their preferences regarding 12 vs 24 hour time. Clearly the custom date format is at least intended to be able to be used for displaying times in a UI, or else why would they bother to make the time format change depending on the user's 24 hour time setting? – shim Apr 19 '18 at 21:48

1 Answers1

24

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.

shim
  • 9,289
  • 12
  • 69
  • 108