First, thanks for re-posting.
Second, I’m going to drop a link to Unicode Technical Standard #35 Unicode Locale Data Markup Language (LDML) Part 4: Dates, just for the benefit of Future Quinn™.
Third, I apologise in advance for harping on about the old API, but you’ll understand why in a second.
You wrote:
I could use
formatter.setLocalizedDateFormatFromTemplate(isTwentyFourHourClock ? "HHmm" : "hhmm")
Unfortunately that doesn’t work, even for ‘simple’ cases like US English. Consider the code at the end of this post. Put that code into a test app and run it on an device. Now configure your device as follows:
Now run the code. It prints this:
locale: en_US (current)
format: X .standard X .preferred X .force12Hour X .force24Hour X
AM: X 09:42 X 09:42 X 09:42 X 09:42 X
PM: X 21:42 X 21:42 X 21:42 X 21:42 X
locale: en_US (fixed)
format: X .standard X .preferred X .force12Hour X .force24Hour X
AM: X 9:42 AM X 9:42 AM X 9:42 AM X 09:42 X
PM: X 9:42 PM X 9:42 PM X 9:42 PM X 21:42 X
Note I’m testing with Xcode 13.2.1 on iOS 15.3.1.
The second group (en_US (fixed)
) is as you expect but what about the first group? It’s using 24-hour time even in the .force12Hour
case. This is a consequence of how the system deals with the 12-/24-hour override.
Honestly, I’m not sure how best to deal with this. I think it’s clear that I need to research it in more depth, and I don’t have time for that in the context of DevForums. If you’d like to get a definitive answer here, please open a DTS tech support incident. That’ll let me allocate the time to research this properly.
And this is all before we even get to the new API (-:
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
enum Format: CaseIterable {
case standard
case preferred
case force12Hour
case force24Hour
func formatter(for locale: Locale) -> DateFormatter {
// Creating and discarding formatters like this is /super/
// inefficient but I don’t care in this test project.
let df = DateFormatter()
df.locale = locale
df.timeZone = TimeZone(identifier: "GMT")!
switch self {
case .standard:
df.timeStyle = .short
case .preferred:
df.setLocalizedDateFormatFromTemplate("jjmm")
case .force12Hour:
df.setLocalizedDateFormatFromTemplate("hhmm")
case .force24Hour:
df.setLocalizedDateFormatFromTemplate("HHmm")
}
return df
}
}
func test(locale: Locale) {
let dAM = Date(timeIntervalSinceReferenceDate: 668511720.0)
let dPM = Date(timeIntervalSinceReferenceDate: 668554920.0)
let results = Format.allCases.map { f in
(
label: ".\(f)",
am: f.formatter(for: locale).string(from: dAM),
pm: f.formatter(for: locale).string(from: dPM)
)
}
func pad(_ s: String) -> String {
"\(s)\(String(repeating: " ", count: max(0, 14 - s.count)))"
}
// I’m using `X` as a field separator because it‘s strongly
// left-to-right. Some of my testing involve Arabic, a right-to-left
// language, and Xcode’s console does weird things if I use a separator,
// like `|`, that’s not strongly left-to-right.
print("locale: \(locale)")
print("format: X \(results.map { pad($0.label) }.joined(separator: " X ")) X")
print(" AM: X \(results.map { pad($0.am) }.joined(separator: " X ")) X")
print(" PM: X \(results.map { pad($0.pm) }.joined(separator: " X ")) X")
print()
}
func test() {
test(locale: .current)
test(locale: Locale(identifier: "en_US"))
}