swift-foundation icon indicating copy to clipboard operation
swift-foundation copied to clipboard

Date.FormatStyle not behaving as expected when init with nils and a suggestion to improve it

Open malhal opened this issue 5 months ago • 7 comments

When Date.FormatStyle is init with nils for date and time styles, an empty string is not returned like the note in the code comment below states:

https://github.com/swiftlang/swift-foundation/blob/94a9456634323b3435c5eb6d83e9206359a7b2d0/Sources/FoundationInternationalization/Formatting/Date/DateFormatStyle.swift#L262-L263

Example:

let defaultStyle = Date.FormatStyle()
defaultStyle.format(Date())

Expected output: "" Actual output: "15/07/2025, 13:34"

let nilStyle = Date.FormatStyle(date: nil, time: nil)
nilStyle.format(Date())

Expected output: "" Actual output: "15/07/2025, 13:34"

I discovered this when I was trying to format a date without the time and I failed with:

let onlyDate = Date.FormatStyle(time: .omitted)
onlyDate.format(Date())

Expected output: "15/07/2025" Actual output: "15/07/2025, 13:34"

It seems .omitted only works if non-nil values for date and time are provided. It appears that date is defaulting to .numeric when it is not specified how about formalising this in the init? e.g.

public init(..., time: TimeStyle = .numeric, ...

(if that is not coming from my system preferences!)

And then rely on .omitted as the nil case.

Environment Swift Playground Xcode 16.4 macOS 15.5

malhal avatar Jul 15 '25 12:07 malhal

The comment is very likely incorrect and needs to be updated. Is that your concern, or do you actually want a way to get back an empty string?

itingliu avatar Jul 22 '25 00:07 itingliu

I'm not bothered about the empty string, as I said above what I actually want is the time omitted which is not working when a date format is not included:

let onlyDate = Date.FormatStyle(time: .omitted)
onlyDate.format(Date())

Expected output: "15/07/2025" Actual output: "15/07/2025, 13:34"

malhal avatar Jul 22 '25 06:07 malhal

That's fair. Though as you probably know there's an easy way around this, which is to specify a date: alongside the time: .omitted

itingliu avatar Jul 22 '25 16:07 itingliu

do you know which date is the default?

malhal avatar Jul 22 '25 16:07 malhal

https://github.com/swiftlang/swift-foundation/blob/22d54679a83a66e2318d14c06f16c1d4c1df0ba2/Sources/FoundationInternationalization/Formatting/Date/DateFormatStyle.swift#L226

Looks like .numeric is the default

itingliu avatar Jul 22 '25 23:07 itingliu

Wow nice find thanks! So as a workaround I'll go with Date.FormatStyle(date: .numeric, time: .omitted) then hopefully public init(date: DateStyle? = nil can be fixed to public init(date: DateStyle? = .numeric)

malhal avatar Jul 23 '25 07:07 malhal

I don't think we'd want to fix it by changing the default argument from nil to .numeric. Their current behaviours are different:

(I'm using en_GB so it's day/month/year)

let f1 = Date.FormatStyle().month(.twoDigits).day(.twoDigits) // "24/07"
let f2 = Date.FormatStyle(date: .numeric).month(.twoDigits).day(.twoDigits) // "24/07/2025"

It was intentionally left as nil to accommodate for the cases when you further refine the format style with date components templates, like f1. We use nil to indicate it's up to the framework to decide how to interpret the template when func format() is called.

On the other hand, specifying ".numeric" as the date style makes it explicit that you want to use exactly that style, which has all year, month, and day components. Further modification .month().day() would be used to modify the numeric format, instead of building it from the ground like f1 does.

(Granted this design to delay resolving template until format time is a little unconventional.)

I think the way to fix this is to change the implementation in init so that .omitted is respected.

itingliu avatar Jul 24 '25 15:07 itingliu