user1354934
user1354934

Reputation: 8841

Is there a built in way to use DateFormatter to get value in the format of "Sun Sep 27 at 4pm"

I have the following formatter:

let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .short
dateFormatter.doesRelativeDateFormatting = true

My goal is to turn my dates into strings like:

Today at 3pm

Tomorrow at 3pm

Sun Sep 27 at 3pm

It works great for dates that are today or tomorrow, but for dates beyond that I want the following format:

"Sun Sep 27 at 4pm"

When I print the result of dateFormatter.string(from: DATE) for a date 3 days from now, I get the following:

Sep 27, 2020 at 4 PM

Is there a way to customize the dateStyle so I can instead get the desired string? I messed around with .full, .short, etc. but want to customize it my way.

Upvotes: 1

Views: 173

Answers (4)

pawello2222
pawello2222

Reputation: 54466

Here is a Locale-independent solution:

  1. Check if the Date is relative:

For this you can use Calendar functions .isDateInYesterday(date:) etc.

private func isDateRelative(_ date: Date) -> Bool {
    Calendar.current.isDateInYesterday(date)
        || Calendar.current.isDateInToday(date)
        || Calendar.current.isDateInTomorrow(date)
}
  1. If it's relative replace the date part with the date in the localizedFormat but keep the time part intact.

DateFormatter extension

extension DateFormatter {
    func relativeStringWithFormat(from date: Date, localizedFormat: String) -> String {
        if isDateRelative(date) {
            return relativeDateTimeString(from: date)
        }
        let dateStr = localizedDateString(from: date, localizedFormat: localizedFormat)
        let timeStr = relativeTimeString(from: date)
        return dateStr + timeStr
    }
    
    private func relativeDateTimeString(from date: Date) -> String {
        dateStyle = .medium
        timeStyle = .short
        doesRelativeDateFormatting = true
        return string(from: date)
    }
    
    private func relativeDateString(from date: Date) -> String {
        dateStyle = .medium
        timeStyle = .none
        doesRelativeDateFormatting = true
        return string(from: date)
    }
    
    private func relativeTimeString(from date: Date) -> String {
        relativeDateTimeString(from: date).replacingOccurrences(of: relativeDateString(from: date), with: "")
    }
    
    private func localizedDateString(from date: Date, localizedFormat: String) -> String {
        setLocalizedDateFormatFromTemplate(localizedFormat)
        doesRelativeDateFormatting = false
        return string(from: date)
    }

    private func isDateRelative(_ date: Date) -> Bool {
        Calendar.current.isDateInYesterday(date)
            || Calendar.current.isDateInToday(date)
            || Calendar.current.isDateInTomorrow(date)
    }
}

Testing

let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US")

let today = Date()
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: today)!
let nextWeek = Calendar.current.date(byAdding: .day, value: 7, to: today)!

let localizedFormat = "EEE d MMM"
print(dateFormatter.relativeStringWithFormat(from: today, localizedFormat: localizedFormat))
// Today at 6:20 PM
print(dateFormatter.relativeStringWithFormat(from: tomorrow, localizedFormat: localizedFormat))
// Tomorrow at 6:20 PM
print(dateFormatter.relativeStringWithFormat(from: nextWeek, localizedFormat: localizedFormat))
// Sat, Oct 3 at 6:20 PM

Upvotes: 1

WongWray
WongWray

Reputation: 2566

Here's a DateFormatter subclass that should give you what you want. Note that I handled the "Yesterday" case even though you didn't mention it in your question. Also note the if you want to display times other than just the hour portion you'll need to adjust the "ha" part of the dateFormat string.

class MyDateFormatter: DateFormatter {
    override init() {
        super.init()
        amSymbol = "am"
        pmSymbol = "pm"
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func string(from date: Date) -> String {
        setDateFormat(forDate: date)
        return super.string(from: date)
    }

    private func setDateFormat(forDate date: Date) {
        if calendar.isDateInYesterday(date) {
            dateFormat = "'Yesterday' 'at' ha"
            return
        } else if calendar.isDateInToday(date) {
            dateFormat = "'Today' 'at' ha"
            return
        } else if calendar.isDateInTomorrow(date) {
            dateFormat = "'Tomorrow' 'at' ha"
            return
        }
        dateFormat = "E MMM d 'at' ha"
    }
}

Just use it as you would a normal DateFormatter:

let formatter = MyDateFormatter()
let dateStr = formatter.string(from: Date())
print(dateStr) // Today at 12pm

Upvotes: 1

Alexis
Alexis

Reputation: 71

Try it like this:

dateformater.dateformat = "E, d MMM yyyy HH:mm:ss"

Output: Wed, 12 Sep 2018 14:11:54

Upvotes: 1

Sam MacGinty
Sam MacGinty

Reputation: 36

The quickest way to get what you want would just be

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EEEE MMM d 'at' ha"

If you want to ensure the AM and PM are lowercased, you could add

dateFormatter.amSymbol = "am"
dateFormatter.pmSymbol = "pm"

Upvotes: 0

Related Questions