What changes by locale beyond strings: sorting and searching, the first day of the week, calendars, names and addresses?

24.4.2 · L3 · Interview

Predict first

The same words are sorted for German and Swedish, then plain. The first weekday for US and French calendars, and the year in four calendars. What prints?

import Foundation

let words = ["zebra", "ärger", "apple"]
func sorted(in id: String) -> String {
    words.sorted { $0.compare($1, options: [], range: nil, locale: Locale(identifier: id)) == .orderedAscending }.joined(separator: " ")
}
print("sv:", sorted(in: "sv"))
print("de:", sorted(in: "de"))
print("plain:", words.sorted().joined(separator: " "))

var us = Calendar(identifier: .gregorian); us.locale = Locale(identifier: "en_US")
var fr = Calendar(identifier: .gregorian); fr.locale = Locale(identifier: "fr_FR")
print("first weekday", us.firstWeekday, fr.firstWeekday)

let date = Date(timeIntervalSince1970: 1_798_000_000)
let years = [Calendar.Identifier.gregorian, .buddhist, .islamicUmmAlQura, .japanese].map { Calendar(identifier: $0).component(.year, from: date) }
print("years", years)
  • sv: apple zebra ärger; de: apple ärger zebra; first weekday 1 2; years [2026, 2569, 1448, 8]
  • All sorts are identical and weekday is 1 for both
  • years are all 2026

Mental model. Alphabetical order is a convention, not a law of nature. Each language has its own, so asking “which comes first” always needs the question “in whose alphabet?”.

Sorting and searching depend on the language. Where “ä” sorts differs: in German it sorts with “a”, in Swedish it is a separate letter after “z”. The check below sorts the same three words under two locales and gets apple ärger zebra for German and apple zebra ärger for Swedish, while a plain code-point sort (sorted()) puts accented letters last for everyone. So sort and compare user-visible text with the locale: localizedStandardCompare (Finder-style, with numbers in natural order), localizedCompare, or compare(_:options:range:locale:) with an explicit locale; search with localizedCaseInsensitiveContains and diacritic-insensitive options so “resume” finds “résumé”. Use plain comparison only for identifiers and protocol data. The same is true of case conversion (uppercased(with:) for the Turkish dotted and dotless i) and of character counting (Q52).

Dates and calendars are cultural: the first day of the week is Sunday in en_US and Monday in fr_FR (the check shows 1 and 2) and Saturday in some regions, which changes calendar grids and “this week” logic; read it from the calendar for the user’s locale instead of assuming. Not everyone uses the Gregorian calendar: the same instant is year 2026 in Gregorian, 2569 in the Buddhist calendar used in Thailand, 1448 in the Islamic Umm al-Qura calendar and year 8 of the Reiwa era in the Japanese calendar (the check prints those four). Store and exchange dates in a calendar-neutral form (UTC Date or ISO 8601) and convert for display with the user’s calendar and time zone (Q78, Q79). Names and addresses vary too: not everyone has a “first” and “last” name, name order differs, and address and phone formats differ by country, so use PersonNameComponents with its formatter (Q122) and Contacts’ postal address formatter, avoid splitting names yourself, and do not make a field required that some cultures do not have. Test with real-world data from several regions, not invented English-shaped examples.

Ordering, week start and year numbering all depend on locale and calendar, so use the locale-aware APIs.

Common traps

  • Sorting user-visible text with a plain sorted(). Accents and language rules are ignored, so the order looks wrong to native readers.
  • Assuming Sunday is the first day of the week, or that year is the Gregorian year. Calendar grids and date maths break in other regions.

They might follow up with

  • Which comparison for a list shown to users? A locale-aware one such as localizedStandardCompare.
  • How do you store a date for users in several calendars? As an absolute instant (or ISO 8601) and convert for display.

Related questions

  • 24.3.2 How is bidirectional text handled, why should you isolate user-generated strings, and how do different…
  • 24.4.1 How do you test internationalisation before translations exist: pseudolanguages, launch arguments, text…
  • 1.9.2 Character vs Unicode.Scalar vs UTF-8 code unit?
  • 2.2.2 How do you handle time zones: what do you store, send and show?
  • 2.2.3 How do Calendar and DateComponents work, and why can YYYY give the wrong year?
  • 2.8.6 Which Foundation formatter replaces hand-written string code for sizes, lists, names and units?

← How do you test internationalisation before translations exist…