You already have the input formatter correctly defined but you will need two more, one date formatter for the output and one formatter to get the day of month correct
let outputFormatter = DateFormatter()
outputFormatter.locale = .current
outputFormatter.timeZone = TimeZone(secondsFromGMT: 0)
outputFormatter.dateFormat = "MMMM hh:mm a"
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .ordinal
numberFormatter.locale = .current
Note that you need to set the time zone for the second date formatter and that it and the number formatter needs a Locale set, I am using the users current locale here since this should be respected.
With that set the whole conversion becomes
if let convertedDate = dateFormatter.date(from: "2021-05-23T12:09:18Z") {
let day = Calendar.current.component(.day, from: convertedDate)
if let dayString = numberFormatter.string(from: day as NSNumber) {
let output = "\(dayString) \(outputFormatter.string(from: convertedDate))"
print(output)
}
}
The solution for formatting the day of month was taken from this answer
Output for this with an English locale is
23rd May 12:09 PM
and an example with a Swedish locale
23:e maj 12:09 em
Following a comment by @LeoDabus I decided to make a more localisable version that respects if day should be printed before or after the month. Assuming the formatters are the same as before and already declared this function can be used
func formatDate(_ date: String, locale: Locale = .current) -> String? {
outputFormatter.locale = locale
numberFormatter.locale = locale
guard let inputDate = dateFormatter.date(from: date) else {
return nil }
let dayOfMonth = Calendar.current.component(.day, from: inputDate)
guard let ordinalDay = numberFormatter.string(for: dayOfMonth),
let output = outputFormatter.string(for: inputDate) else {
return nil }
return output.replacingOccurrences(of: "\\b\(dayOfMonth)\\b",
with: ordinalDay,
options: .regularExpression)
}
A simple test
for locale in [Locale(identifier: "en_US"), Locale(identifier: "sv_SE"),Locale(identifier: "pt_BR")] {
if let date = formatDate("2021-05-23T12:09:18Z", locale: locale) {
print("\(locale): \(date)")
}
}
en_US (fixed): May 23rd, 2021
sv_SE (fixed): 23:e maj 2021
pt_BR (fixed): 23º de maio de 2021
And if we are going to respect what language/format etc the user has chosen completely then we ignore the ordinal number handling
func formatDate(_ date: String, locale: Locale = .current) -> String? {
outputFormatter.locale = locale
numberFormatter.locale = locale
guard let inputDate = dateFormatter.date(from: date) else {
return nil }
return outputFormatter.string(for: inputDate)
}
and the same test would generate
en_US (fixed): May 23, 2021
sv_SE (fixed): 23 maj 2021
pt_BR (fixed): 23 de maio de 2021