young
(rtSwift)
1
I cannot find anyway to format hour in 24 hour with the new Date.FormatStyle
import Foundation
let afternoonHour = try Date("2021-10-23T02:37:12Z", strategy: .iso8601)
// this format to 24 hour
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm:ss"
let twentyFourHour = dateFormatter.string(from: afternoonHour)
print(twentyFourHour)
// with the new Date.FormatStyle
// how to display hour in 24hr format?
afternoonHour.formatted(.dateTime.month().day().hour(.conversationalTwoDigits(amPM: .wide)).minute().timeZone(.specificName(.short))) // "Oct 22, 07:37 PM PDT"
afternoonHour.formatted(.dateTime.hour(.conversationalDefaultDigits(amPM: .omitted))) // "07"
// how to get "19"?
tera
2
try this:
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
young
(rtSwift)
3
Doesn't work:
var formatStyle = Date.FormatStyle.dateTime
formatStyle.locale = Locale(identifier: "en_US_POSIX")
afternoonHour.formatted(formatStyle.hour()) // "07 PM"
Is 24 hour format not possible, intentionally? No way this is due to mistaken oversight.
eskimo
(Quinn “The Eskimo!”)
4
Let’s start by digging into your DateFormatter example. Consider this code:
// this format to 24 hour
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm:ss"
let twentyFourHour = dateFormatter.string(from: afternoonHour)
print(twentyFourHour)
This doesn’t work the way you think it does. Try this:
-
Set your Mac to a 24-hour by default locale (like the UK).
-
Put that code into a Xcode playground. It’ll print a 24-hour time:
20:52:54
-
Go to System Preferences > Language & Region > General and uncheck 24-Hour Time.
-
Run your playground again; this time you get a 12 hour time:
8:52:54 pm
That’s because you haven’t pinned the locale on the date formatter to en_US_POSIX. Without that, the date formatter uses the user’s settings, and that includes this 12-/24-hour override.
You’ll see worse things when it comes to dates )-: QA1480 NSDateFormatter and Internet Dates has a good example of this.
The issue here is that DateFormatter serves two roles:
Right now you’re crossing those streams, which doesn’t end well.
AFAIK Foundation’s new date formatting support is intended to support the first role, not the second. If you want to work with fixed-format dates, you have various options:
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
6 Likes
young
(rtSwift)
5
I run this on device and it's still in AM/PM format, even though it's in 24 hour setting. I verify with DatePicker which is in 24 hour format.
Text(when, format: .dateTime.month().day().hour().minute().timeZone(.specificName(.short)))
Test
import SwiftUI
struct DateFormatting: View {
let when = Date(timeIntervalSinceReferenceDate: 656904575.211251)
var body: some View {
VStack {
Text(when, format: .dateTime.month().day().hour().minute().timeZone(.specificName(.short)))
.font(.system(size: 50))
Text(when, format: .dateTime.month().day().hour(.conversationalTwoDigits(amPM: .omitted)).minute().timeZone(.specificName(.short)))
.font(.system(size: 50))
}
}
}
struct DateFormatting_Previews: PreviewProvider {
static var previews: some View {
DateFormatting()
}
}
tera
6
try a fixed locale:
Text(when, format: .dateTime.locale(.init(identifier: "en_US_POSIX")).month().day().hour(...
1 Like
young
(rtSwift)
7
No diff, still show "6:29 PM" :
Text(when, format: .dateTime.locale(.current).month().day().hour().minute().timeZone(.specificName(.short)))
Text(when, format: .dateTime.locale(.init(identifier: "en_US_POSIX")).month().day().hour().minute().timeZone(.specificName(.short)))
Maybe it's a bug, looks like it's not respecting user preferences? @eskimo ?
tera
8
looks buggy. i'd stick to DateFormatter for now which is less buggy.
btw, this gave me 24h result (tested on simulator):
Text(when, format: .dateTime)
.environment(\.locale, .init(identifier: "en_UK"))
young
(rtSwift)
9
Ah, I forgot. This is not a bug, it's how Text works: it ignores the locale set in the FormatStyle and use the environment. However, it doesn't seem to do the same for Calendar and TimeZone. It should, though, like the DatePicker:
import SwiftUI
struct ContentView: View {
static let calendar = Calendar(identifier: .chinese)
static let locale = Locale(identifier: "zh_Hans_CN")
static let timeZone = TimeZone(identifier: "Asia/Shanghai")!
static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss.SSSz"
return formatter
}()
@State private var when = Date(timeIntervalSinceReferenceDate: 656904575.211251)
var body: some View {
VStack {
// default
Text(when, format: .dateTime.month().day().hour().minute().timeZone(.specificName(.long)))
.padding()
// New Date.FormatStyle: only locale in environment work
Text(when, format: .dateTime.month().day().hour().minute().timeZone(.specificName(.long)))
.padding()
.environment(\.locale, Self.locale) // works
// New Date.FormatStyle: timezone and calendar in environment don't work
Text(when, format: .dateTime.month().day().hour().minute().timeZone(.specificName(.long)))
.padding()
.environment(\.locale, Self.locale) // works
.environment(\.timeZone, Self.timeZone) // not work, still at system
.environment(\.calendar, Self.calendar) // not work, still at system
// old DateFormatter: only timeZone in environment work
Text(when, formatter: Self.dateFormatter)
.environment(\.locale, Self.locale) // not work
.environment(\.timeZone, Self.timeZone) // works
.environment(\.calendar, Self.calendar) // not work
// DatePicker respect all the env values
DatePicker("When", selection: $when)
// all work:
.environment(\.locale, Self.locale)
.environment(\.timeZone, Self.timeZone)
.environment(\.calendar, Self.calendar)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
tera
10
that's quite non obvious... have a formatter/format parameter which is ... ignored?
struct ContentView: View {
static let when = Date(timeIntervalSince1970: 15*3600)
static let locale = Locale(identifier: "en_US_POSIX")
static let timeZone = TimeZone(identifier: "UTC")!
var body: some View {
Text(Self.when, format: Date.FormatStyle.dateTime.locale(Self.locale))
.environment(\.locale, Self.locale)
.environment(\.timeZone, Self.timeZone)
}
// this surely has to show "15:00", right?
// nope... "1/1/1970, 4:00 PM"
// same with en_US. slightly better but still wrong with en_UK (16:00)
}
2 Likes
young
(rtSwift)
11
That's how SwiftUI view that deal with Date value work: DatePicker and Text<Subject>(Subject, formatter: Formatter).
The problem is the new Text<F>(F.FormatInput, format: F) only use Locale from environment, but not for TimeZone and Calendar. I think it's a bug I reported FB9217356.
Look at this example, the second Text use the Locale, Timezone and Calendar from the environment, the first Text only use Locale from environment:
static let when = Date(timeIntervalSince1970: 15*3600)
static let locale = Locale(identifier: "en_US_POSIX")
static let timeZone = TimeZone(identifier: "UTC")!
static let localeChina = Locale(identifier: "zh_Hans_CN")
static let calendar = Calendar(identifier: .chinese)
static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .long
return formatter
}()
var body: some View {
// this one only use locale from environment, not timezone and calendar. it's a bug
Text(Self.when, format: Date.FormatStyle.dateTime.locale(Self.locale))
.environment(\.locale, Self.localeChina) // it's using this locale, not the one set above
.environment(\.timeZone, Self.timeZone) // but it's not using this, still at system default
.environment(\.calendar, Self.calendar) // and not this
// this surely has to show "15:00", right?
// nope... "1/1/1970, 4:00 PM"
// same with en_US. slightly better but still wrong with en_UK (16:00)
// this one use locale, timezone and calendar from environment:
Text(Self.when, formatter: Self.dateFormatter)
.environment(\.locale, Self.localeChina) // it's using this
.environment(\.timeZone, Self.timeZone) // and this
.environment(\.calendar, Self.calendar) // and this
}
tera
12
my example also shows that en_US_POSIX locale set on environment level is ignored.
young
(rtSwift)
13
That not what I see: my example above set a china locale in environment and it's showing in Chinese. But timezone and calendar are not used from environment.
This new Text should work like the DateFormatter version: use locale, timezone and calendar from environment. I reported this many months ago but no fix so far. This prevents me from using the new Date.FormatStyle with Text to display Date value in specific timezone and calendar, different from user's system preference.
tera
14
i'm not contradicting you here, i can see "non fixed" locales like "en_UK" or "en_US" working... just not the fixed locale "en_US_POSIX".
it would be logical for the format/formatter parameter of Text initialiser to take precedence over environment settings, don't you think?
view initialiser param >
view environment setting >
"outer view" environment setting >
...
default (current OS settings)
1 Like
young
(rtSwift)
15
Yes, I think.
What I wish is Apple make it 1) follow user's 24-hour or AM/PM preference, 2) allow setting of locale, timezone and calendar work as you say, or be consisted with the current state: take them all from the environment.
@luca_bernardi ?
tera
16
until it is fixed, in my experience DateFormatter's "setLocalizedDateFormatFromTemplate" works ok. although i do not know how to ask it for "user preferred 24 or 12 hour symbol", in other words while i can pass it "h" / "hh" / "H" or "HH" i don't know which one to use to match the OS setting.
eskimo
(Quinn “The Eskimo!”)
17
in my experience DateFormatter's
setLocalizedDateFormatFromTemplate(_:) works ok.
This is definitely your friend.
i do not know how to ask it for "user preferred 24 or 12 hour symbol"
The go-to reference for this stuff is Unicode Technical Standard #35. It describes the j and J elements, that do exactly this.
IMPORTANT This only works in the context of date format templates. That is, do this:
df.setLocalizedDateFormatFromTemplate("j")
not this:
df.dateFormat = "j"
Then again, if you’re working with localised dates then the dateFormat property is nonsense anyway.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
2 Likes
young
(rtSwift)
18
Not sure need fix or not.
After watch What's new in Foundation again, he uses @Environment(\.locale) and pass the value into the Date.FormatStyle with .locale(locale). But .environment(\.locale, ...) seems to have precedent over what you specify:
Text(when, format: Date.FormatStyle(locale: locale1, calendar: calendar1, timeZone: timeZone1)) // <= this locale1 is not used, calendar1 and timeZone1 are used
.environment(\.locale, locale2) // <== this locale2 is used
.environment(\.calendar, calendar2) // <== this calendar2 is not used
.environment(\.timeZone, timeZone2) // <== this timeZone2 is not used
But the Date.FormatStyle.inint(...) lets you specify calendar/timezone
init(date:time:locale:calendar:timeZone:capitalizationContext:)
Not sure why locale is from the environment and calendar and timezone are not.
I'm trying to get this to work in raw Swift in a watchOS complication without SwiftUI and has been giving me issues too:
let text = Date.now.formatted(dateStyle.locale(.init(identifier: "en_US_POSIX")).hour(.twoDigits(amPM: .omitted)).minute())
CLKSimpleTextProvider(text: text)
It's displaying 11:38.. this seems like a bug because en_UK does display 23:38. That isn't ideal because what if the local isn't English to begin with, it would force English numbers.
Really what I'm looking for is a new static Date.FormatStyle.Symbol.Hour so I can do something like this:
Date.now.formatted(dateStyle.hour(.hrs24).minute())
Could I create a custom hour style to achieve this or does this have to be done internally?
eskimo
(Quinn “The Eskimo!”)
20
I'm trying to get this to work
I’d like to clarify what you mean by “this”. There’s a bunch of backstory on this thread and it’s not clear about your specific goals here.
I think that you want:
-
A time that you display to users (that is, not a fixed format)
-
Containing hours and minutes
-
But overriding the hours to have them always be 24 hour time regardless of the locale
Is that right?
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple