So I like a lot about how the new Date formatting options work, but sometimes I find the "format" part super verbose as I already have a whole cheat sheet of the styles I use and a String is a lot easier to store and pass around than a Date.FormatString..
So I'd love an overload like Date.ParseStrategy(formatLiteral:"YYYY-MM-dd' 'HH:mm:ss" , etc.)
Looking at:
- swift-foundation/Sources/FoundationInternationalization/Formatting/Date/DateParseStrategy.swift at 71cf0809cc713ea25dfe569ea530dbb8d1b963e1 · apple/swift-foundation · GitHub
- swift-foundation/Sources/FoundationInternationalization/Formatting/Date/DateFormatString.swift at 71cf0809cc713ea25dfe569ea530dbb8d1b963e1 · apple/swift-foundation · GitHub
- swift-foundation/Sources/FoundationInternationalization/Formatting/Date/ICUDateFormatter.swift at 71cf0809cc713ea25dfe569ea530dbb8d1b963e1 · apple/swift-foundation · GitHub
I think I see a couple of ways to make it happen, but raw value is internal so I don't think I can do it as just an extension?
extension Date {
public struct FormatString : Hashable, Sendable {
internal var rawFormat: String = ""
}
}
am I missing something simple to make it work already? I tried both
- Date.FormatString("YYYY-MM-dd' 'HH:mm:ss")
- Date.FormatString("YYYY-MM-dd HH:mm:ss")
Which compile but fail the test (expected when you look at the inits for FormatString
, specifically what asDateFormatLiteral()
does.)
The test.
func testDateFormats() throws {
let dateString = "2024-03-12 03:12:10"
//in real code this lives in an extension, hence vars for now
//.description
// \'YYYY-MM-dd\'\' \'\'HH:mm:ss\'
var withTime_fails1:Date.FormatString {
"YYYY-MM-dd' 'HH:mm:ss"
}
//.description
// \'YYYY-MM-dd HH:mm:ss\'
var withTime_fails2:Date.FormatString {
"YYYY-MM-dd HH:mm:ss"
}
//.description
// y\'-\'MM\'-\'dd\' \'HH\':\'mm\':\'ss
var withTime_passes:Date.FormatString {
"\(year: .defaultDigits)-\(month: .twoDigits)-\(day: .twoDigits) \(hour: .twoDigits(clock: .twentyFourHour, hourCycle: .zeroBased)):\(minute: .twoDigits):\(second: .twoDigits)"
}
//cant use the local var in real code as default value so this init is LONG.
func _decodeDateNew(_ value:String, format:Date.FormatString = withTime_passes) throws -> Date {
let strategy = Date.ParseStrategy(format: format, timeZone: .gmt)
//format for error message not that useful.
guard let date = try? Date(value, strategy: strategy) else {
throw DecodingError.dataCorrupted(
.init(codingPath: [], debugDescription: "String not in expected \(strategy.format.description) format.")
)
}
return date
}
let dateValue = try _decodeDateNew(dateString, format: withTime_passes)
//let dateValue = try _decodeDateNew(dateString, format: withTime_fails1)
//let dateValue = try _decodeDateNew(dateString, format: withTime_fails2)
func _decodeDateClassic(from value:String, formatString:String = "YYYY-MM-dd' 'HH:mm:ss") throws -> Date {
let dateFormatter = DateFormatter() //in real code defined at top level.
dateFormatter.timeZone = .gmt
dateFormatter.dateFormat = formatString
guard let date = dateFormatter.date(from: value) else {
throw DecodingError.dataCorrupted(
.init(codingPath: [], debugDescription: "String not in expected \(dateFormatter.dateFormat.description) format.")
)
}
return date
}
let dateValueClassic = try _decodeDateClassic(from: dateString)
XCTAssertEqual(dateValue, dateValueClassic)
}