Locale is a Foundation class in iOS and macOS that encapsulates the user’s linguistic and cultural conventions: date format, number format, currency, and units of measurement. According to Apple Developer Documentation, 2024, Locale determines how DateFormatter displays a month (January or January), the decimal separator in a number (comma or dot), and the currency symbol (ruble, dollar, or euro). Each Locale instance is tied to an identifier like ru_RU or en_US, where the first part is the language code (ISO 639-1) and the second is the region code (ISO 3166-1). Unlike TimeZone, Locale does not affect the absolute time value, only its string representation.
Key Takeaways
Locale is a value type in Swift (NSLocale in Objective-C) representing a set of formatting rules specific to a particular language and region. Unlike TimeZone, which determines the absolute time offset, Locale determines how time, numbers, and currency appear in string representation. The same date 2024-07-21 will be displayed as “21 July 2024” for ru_RU and “July 21, 2024” for en_US.
Each Locale instance consists of two components: Language (determines month names, day names, word order) and Region (determines number format, currency, calendar). The combination of these components is encoded in an identifier according to the BCP 47 standard: ru_RU (Russian language, Russia), en_US (English, USA), de_DE (German, Germany).
According to Unicode CLDR (2024), the number of supported locales in iOS exceeds 700 language-region combinations. Foundation uses data from CLDR (Common Locale Data Repository) — the most comprehensive repository of localization data maintained by the Unicode Consortium. This ensures consistent formatting across all Apple devices.
DateFormatter uses Locale to select the correct month and day names, determine the order of date components (day/month/year or month/day/year), and separators. Without explicitly specifying Locale, DateFormatter uses the device locale — this is correct for UI but dangerous for server data where the format should be fixed.
| Component | ru_RU | en_US | de_DE |
|---|---|---|---|
| Date (medium) | 21 July 2024 | Jul 21, 2024 | 21.07.2024 |
| Number (1000.5) | 1 000,5 | 1,000.5 | 1.000,5 |
| Currency (100) | 100.00 ₽ | $100.00 | 100.00 € |
| Calendar | Gregorian | Gregorian | Gregorian |
| List separator | ; | , | ; |
NumberFormatter uses Locale to determine the decimal separator (comma or dot), grouping separator (space, comma, dot), and currency symbol. Ignoring Locale when parsing numbers is one of the common causes of bugs in international applications: the number “1,5” means “one and a half” for ru_RU, but for en_US the digital parser will read it as “five” after the comma.
Important: Calendar created via Calendar.current inherits the device locale. Calendar(identifier: .gregorian) with an explicitly set locale is the recommended approach for predictable formatting. When working with ISO 8601 dates, always use Locale(identifier: “en_US_POSIX”) — a special locale for technical formatting that is not affected by regional settings.
Locale identifier consists of a language code (ISO 639-1, two characters) and a region code (ISO 3166-1, two characters), separated by an underscore. Examples: ru_RU, en_US, fr_FR, zh_Hans_CN (Chinese, simplified script, China). Foundation also supports identifiers in BCP 47 format: ru-RU, en-US, used in web standards.
Besides full identifiers, Locale can be created by language only: Locale(identifier: “ru”) returns a locale with the Russian language and the default region for that language (usually Russia). Similarly for English: Locale(identifier: “en”) uses the US region. This approach is useful for setting the formatting language without tying to a specific region.
Special locales include en_US_POSIX — a technical locale for machine formatting of dates and numbers, ensuring a stable format regardless of user settings. This locale is mandatory for parsing dates from server APIs, especially for ISO 8601 format. It uses the Gregorian calendar, 24-hour time format, and dot as the decimal separator.
import Foundation
// Available locale identifiers
let available: [String] = Locale.availableIdentifiers
print("Total locales: \(available.count)")
// Filter Russian locales
let russianLocales = available.filter { $0.hasPrefix("ru") }
print("Russian locales: \(russianLocales)")
// Locale components
let locale = Locale(identifier: "de_DE")
print("Language: \(locale.languageCode ?? "nil")")
print("Region: \(locale.regionCode ?? "nil")")
print("Currency: \(locale.currencyCode ?? "nil")")
print("Calendar: \(locale.calendar.identifier)")
Checking available locales via Locale.availableIdentifiers returns an array of all identifiers supported by the current iOS version. To filter by region, use Locale.availableIdentifiers.filter with regionCode check. This is useful for building a region selection UI without a hardcoded list.
Locale.current is the primary way to get the device’s current locale set by the user in iOS settings (Settings > General > Language & Region). This property automatically updates when the language or region is changed in settings without restarting the application. However, it may not match the locale preferred for content display: a user may set the interface language to English but view dates in Russian format.
For a more precise determination of user preferences, use Locale.preferredLanguages — an array of languages ordered by user priority. The first element is the primary interface language. This list corresponds to the settings in Language & Region, including dragging languages in order of preference. Communication apps (messengers, email clients) should consider this order when selecting the content display language.
import Foundation
// Current system locale
let current = Locale.current
print("Current locale: \(current.identifier)")
print("Language: \(current.language?.disjointName ?? "nil")")
// User preferred languages
let preferred = Locale.preferredLanguages
print("Preferred languages: \(preferred)")
// Get region from current locale
if let region = current.regionCode {
let regionLocale = Locale(identifier: "en_\(region)")
let countryName = regionLocale.localizedString(
forRegionCode: region
)
print("Country: \(countryName ?? region)")
}
// Check 24h format
let uses24h = current.uses24hClock(
for: .dateAndTime
)
print("Uses 24h: \(uses24h)")
Localization in UI: to display month and day names in the interface language, use Calendar with a set locale. Calendar.current.symbols(for: .month) returns month names in the current locale’s language. To display country names in the user’s language, use Locale.current.localizedString(forRegionCode:).
Date formatting with locale awareness is a key task when displaying dates to the user. DateFormatter with a set locale automatically selects the correct date and time format for the user’s region. For dateStyle and timeStyle values of .short, .medium, .long, .full, the formatter uses locale rules to compose date components.
import Foundation
let date = Date()
// Format with different locales
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.locale = Locale(identifier: "ru_RU")
print("Russian: \(formatter.string(from: date))")
formatter.locale = Locale(identifier: "en_US")
print("English: \(formatter.string(from: date))")
formatter.locale = Locale(identifier: "ja_JP")
print("Japanese: \(formatter.string(from: date))")
// Currency formatting with locale
let numFormatter = NumberFormatter()
numFormatter.numberStyle = .currency
numFormatter.locale = Locale(identifier: "de_DE")
print("German currency: \(numFormatter.string(from: 1234.56) ?? "nil")")
numFormatter.locale = Locale(identifier: "en_US")
print("US currency: \(numFormatter.string(from: 1234.56) ?? "nil")")
Parsing dates from server APIs should always use Locale(identifier: “en_US_POSIX”) for a fixed format. Servers typically send dates in ISO 8601 format with English month names, and using the current device locale may cause parsing errors if the user is in a non-English region. en_US_POSIX ensures parsing does not depend on device settings.
import Foundation
// Correct server date parsing
let isoFormatter = DateFormatter()
isoFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
isoFormatter.locale = Locale(identifier: "en_US_POSIX")
isoFormatter.timeZone = TimeZone(secondsFromGMT: 0)
let serverDate = "2024-07-21T14:30:00+0000"
if let parsed = isoFormatter.date(from: serverDate) {
print("Parsed date: \(parsed)")
}
// Localized currency name
let usLocale = Locale(identifier: "en_US")
let currencyName = usLocale.localizedString(
forCurrencyCode: "RUB"
)
print("Russian ruble in US locale: \(currencyName ?? "nil")")
Additional capabilities: Locale provides localized descriptions of its components through methods localizedString(forRegionCode:), localizedString(forLanguageCode:), localizedString(forCurrencyCode:), and localizedString(forCalendarIdentifier:). These methods return names in the language of the locale on which they are called. For example, Locale(identifier: “ru_RU”).localizedString(forCountryCode: “DE”) returns “Germany”.
Ignoring Locale when parsing numbers is a critical error in international applications. NumberFormatter without an explicit locale uses the current device locale. If a user in Russia enters “1,5”, NumberFormatter.number(from: “1,5”) correctly returns 1.5. But if the same code runs on a device with en_US locale, parsing returns nil because for en_US the decimal separator is a dot.
Missing en_US_POSIX for server dates leads to subtle bugs. DateFormatter with dateFormat and locale = Locale.current may break for users from regions where the date format differs from the American one. For example, in Germany DateFormatter may expect “21.07.2024” while the server sends “07/21/2024”. en_US_POSIX ensures a fixed format for machine parsing regardless of the user’s region.
Comparing date strings instead of using Date is another common mistake. Developers sometimes compare string representations of dates from different locales, getting incorrect results. Locale only changes the display, not the absolute date value. Always compare Date objects, not their string representations. For comparing date components, use Calendar with an explicitly set locale.
According to WWDC 2023, about 30% of internationalization problems in applications are related to incorrect Locale configuration. Apple recommends always explicitly setting the locale for DateFormatter and NumberFormatter when working with server data and using Locale.current only for UI display. This simple practice eliminates most bugs related to regional settings.
Frequently Asked Questions
Locale is a Foundation class representing cultural and linguistic formatting rules: date format, number format, currency, and units of measurement. It is used together with DateFormatter, NumberFormatter, and Calendar for localized data display.
Locale determines the display format (language, regional conventions), while TimeZone determines the absolute time offset relative to UTC. Locale affects the string representation, TimeZone affects the numerical time value. Both are used together for complete date formatting.
en_US_POSIX is a special locale for technical formatting that ensures a stable format regardless of user settings. It is mandatory for parsing server dates (ISO 8601) and working with APIs where the format must be predictable.
Locale.availableIdentifiers returns an array of strings with identifiers of all supported locales. To filter by language, use filter with hasPrefix; to get the region, use Locale(identifier:).regionCode.
NumberFormatter uses Locale to determine the decimal separator (comma or dot), currency symbol, and grouping separator. For a fixed format, set the locale to en_US_POSIX or explicitly set the formatter properties.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also