NSDateFormatter and current language in iOS11
Asked Answered
M

5

26

It appears that default behavior for NSDateFormatter has been changed in iOS11. This code used to work and produced date formatter according to currently selected iPhone/iPad language prior to iOS11:

 _dateFormatterInstance = [[NSDateFormatter alloc] init];
 _dateFormatterInstance.timeZone = [NSTimeZone systemTimeZone];

Looks like in iOS11 we have to explicitly specify locale property for it:

 _dateFormatterInstance = [[NSDateFormatter alloc] init];
 _dateFormatterInstance.timeZone = [NSTimeZone systemTimeZone];
 _dateFormatterInstance.locale = [NSLocale localeWithLocaleIdentifier:[[NSLocale preferredLanguages] firstObject]];

Can somebody confirm my findings?

Malchus answered 13/9, 2017 at 19:50 Comment(6)
What behavior are you actually seeing in iOS 11? How is it different from iOS 10?Araroba
When this code is run on device configure to use French language (for example), date formatter is still using English names for week days. On iOS10 first code will use French names.Malchus
I'm curious if you want to use their preferred locale, you can set the locale as you've indicated - do you also need to set the calendar and/or timeZone?Doreendorelia
@Joey I’m not sure i understand. In my code I do set current time zone. Current calendar - I think it will get defaulted to that, but you can always set it up to be sure.Malchus
Sorry, unclear question. Since Locale.current doesn't return the locale as defined in Settings, does Calendar.current and TimeZone.current behave similarly or do they continue to work as originally expected? Wondering if we need to set timeZone or calendar or if the defaults and manually setting locale will do the trick.Doreendorelia
I think time zone behavior remains the same (you can’t have an application that doesn’t support certain time zone). And probably same logic could apply to a calendar :) Local and supported languages seem different enough from time zone and calendar.Malchus
A
69

This isn't a problem with NSDateFormatter, it's a change in how iOS 11 supports localization.

Under iOS 11, [NSLocale currentLocale] only returns languages supported by your app's localizations. If your app only supports English (as the base localization), then no matter what language the user selects on the device, currentLocale will always return English.

Under iOS 10 and earlier, currentLocale would directly represent the user's chosen language and region, regardless of what localizations your app supports.

Classes such as NSDateFormatter default to using NSLocale currentLocale. So no matter what language your app actually supported through its localization, classes like NSDateFormatter would show text in the language set on the device, even it was different from the language being used by your app.

iOS 11 fixes this inconsistency. While one could argue that this change breaks lots of apps that only support one (or just a few) language, it actually makes the app more consistent.

To make all of this clear, consider an example. You create a simple test app with a base localization in English. If you run your app with iOS 10 and the device's language is set to English, you obviously see English text and you see dates formatted for English. If you now change the device's language to French and restart the app, the user now sees English text in the app (since that is its only localization) but dates now show with French month and weekday names.

Now run the same app under iOS 11. As with iOS 10, if the device's language is English you see everything in English. If you then change the device's language to French and run the app, iOS 11 sees that your app only supports English and currentLocale returns English, not French. So now the user sees English text (due to the app's localization) and dates are now also still in English.

Araroba answered 13/9, 2017 at 20:42 Comment(9)
Thank you! This is extremely useful and thorough. In my case - I actually would prefer iOS10 behavior since we're doing localization support from our back-end for all UI text visible to a user, but obviously not for standard things like month and day names.Malchus
Update your app to include whatever localizations you actually support and then currentLocale can return one of those possible languages.Araroba
Well. It’s actually easier to specify locale for all shared dateformatters in the code :)Malchus
@Araroba You saved my life thanks, but where did you get this information? Do you have any official links?Eaglewood
@tofucodes I looked all over but I could not find any information on this change. I have this vague memory of seeing mention of this once during the beta period. My answer is based on actual tests that demonstrate the new behavior. If I do find official information on this change I'll add it to my answer.Araroba
@Araroba I'll also look for it. Thank youEaglewood
Thank you. This answer comes up as one of the most useful ones, I was googling for what changed in iOS 11, both as user and developer. For someone from a locale that iOS and most apps are not translated into, this is a step backwards and bad regression. On iOS 10, I at least had dates and times localized in many apps. (E.g the system Weather app.) It’s not cool to not have localized dates at all.Fraunhofer
@Araroba How do you include other localizations without having a strings file? I just need the language so that my back end returns the correct strings.Utley
How do I enable all those languages in app settings?Endeavor
S
11

This actually appears to be more of a bug than an intentional change in behaviour in iOS 11. If you only have one language set, this behaviour isn't present as Locale.current always returns the correct language and region even if your app isn't localized to that language.

However, if you have more than one language - such as French and English - then iOS 11 appears to always favour English or the closest supported language in your app when using Locale.current.

Locale.preferredLanguages appears to return the correct language-region information, so you might be able to use that instead.

Below is an example showing the output from Locale.current and Locale.preferredLanguages, showing the inconsistencies.

This was generated from an app that only supported English. On the device, French was set as both the primary language and region, with English (Australia) set as a secondary language in the first example.

(Incorrect) Locale.current with multiple languages - note how English is the language, when it should be French and therefore fr_FR

  - identifier : "en_FR"
  - kind : "current"

(Correct) Locale.preferredLanguages with multiple languages

  - 0 : "fr-FR"
  - 1 : "en-AU"

(Correct) Locale.current with French as the only language

  - 0 : "fr-FR"

(Correct) Locale.preferredLanguages with French as the only language

  - identifier : "fr_FR"
  - kind : "current"
Smaltite answered 8/2, 2018 at 5:1 Comment(1)
In iOS 12, if you only have one preferred language in your list like Spanish, Locale.current is English if that's the only language you support in the app.Doreendorelia
C
1

Yes, the default behaviour is changed in iOS11 exactly as @rmaddy described.

In my case, I have a project with a base development language set to English but, on iOS11, when I changed the device's language to any other language (say Swedish) the dates would still be displayed as, for instance, Monday 6 November. This happened because my app didn't support any localization.

The solution was simple: in order to have the app displaying the dates in Swedish I just had to add an empty Strings.strings file and then, in projects settings, I added the Swedish localization. Although the strings file is empty, the app then became localized in Swedish so by changing the language, in Settings, to Swedish, we could see the same date as måndag 6 november, thus achieving the desired use-case of iOS10.

Note: if you do something like this and it doesn't work for you, when adding a language in Project Settings make sure to go to "Other" and pick a language from there (instead of just choosing one from the first-level dropdown).

Canaletto answered 20/11, 2017 at 20:53 Comment(0)
D
1

iOS 15, Swift 5

Thanks to rmaddy, who perfectly explained why and how the problem has come up. Though, the only solution I found really working was changing the instance of Locale within the Formatter implementation, like so:

let formatter = DateFormatter()

// this helps
formatter.locale = Locale(identifier: Locale.preferredLanguages.first!)

// this is the problematic code that I deleted
formatter.locale = Locale.current

Unfortunately adding localizations to the app did not change the "english" words used by the DateFormatter (that uses Locale.current by default). So I ended up with this solution above, manually setting an instance of Locale with the (users) preferred languages number one.

Dresden answered 28/11, 2021 at 0:10 Comment(0)
D
0

Apple changed the behavior of the Locale API. As of iOS 11, it behaves as rmaddy's answer describes. If you want to achieve the iOS 10 and earlier behavior, I received the following guidance from Apple:

There are a few ways that it can be addressed (in “most recommended” to “least recommended” order):

  1. Add localizations for languages that your app should support.
  2. In your app’s Info.plist, replace CFBundleDevelopmentRegion with CFBundleLocalizations and populate it with one or more localizations that your app is intended to be localized into.
  3. In your app’s Info.plist, set CFBundleAllowMixedLocalizations to YES. This will cause Locale.current to always use the locale most preferred as specified in user preferences. Please note that if you have other strings in your app, this may result in mixed localizations of string content (e.g system framework strings in French but the rest of the app in Spanish).

Note that Locale.current takes into account the user's settings, such as if they have enabled 24-hour time. One option not noted above would be to set the date formatter’s locale to Locale(identifier: Locale.preferredLanguages.first!) but this will use the default settings for that locale, ignoring any settings the user has specified, which you probably don't want to do.

Doreendorelia answered 9/12, 2021 at 2:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.