8

I am trying to format NSDates in a form where it uses the relative format when applicable and the day of the week when not: "Today", "Tomorrow", "Sunday", "Monday", …

The problem is, NSDateFormatter’s doesRelativeFormatting only works when using dateStyle, and not with dateFormat. (Basically, I’d need the functionality of dateFormat = "EEEE" for all days after tomorrow.)

At the moment, I’m using the following code:

let dateFormatter = NSDateFormatter()
dateFormatter.timeStyle = .NoStyle
dateFormatter.dateStyle = .FullStyle
dateFormatter.doesRelativeDateFormatting = true

let dateString = dateFormatter.stringFromDate(theDate)
return dateString.componentsSeparatedByString(" ")[0]

Which just happens to work in my specific locale where NSDateFormatterStyle.FullStyle outputs something like "Sunday 23 August 2015", but obviously that’s not a good or general solution.

The closest thing I found would be this question, but it seems unnecessarily complex for my use case, and I’d like something more elegant, if possible.

Thanks!

Community
  • 1
  • 1
attoPascal
  • 111
  • 8

3 Answers3

8

I'd use 3 date formatters:

  1. dateStyle = .FullStyle and doesRelativeDateFormatting = true
  2. dateStyle = .FullStyle and doesRelativeDateFormatting = false
  3. dateFormat = "EEEE" and doesRelativeDateFormatting = false

Get formatted strings for 1 and 2. If they are different, then use string from 1. If they are the same then get and use string from 3.

This should work reliably for all locales. For performance reasons make sure to keep all 3 formatters around instead of recreating them each time.

pointum
  • 2,987
  • 24
  • 31
2

Here's a working extension to DateFormatter():

import Foundation

extension DateFormatter {

    /*
     * Returns a string representation of a given date in relative format.
     * If the current local doesn't offer a relative format for the given date,
     * then then a given format is applied.
     *
     * - parameter _date: The date to be formatted
     * - parameter _format: The format to be applied to non-relative dates
     *
     * - returns: A string representing a formatted version of a given date
     */

    func relativeStringWithFormat(from: Date, format: String) -> String? {

        // Create date formatters
        let _formatRelative = DateFormatter()
            _formatRelative.dateStyle = .full
            _formatRelative.doesRelativeDateFormatting = true
            _formatRelative.timeStyle = .none

        let _formatFull = DateFormatter()
            _formatFull.dateStyle = .full
            _formatFull.doesRelativeDateFormatting = false
            _formatFull.timeStyle = .none

        let _formatCustom = DateFormatter()
            _formatCustom.dateFormat = format
            _formatCustom.doesRelativeDateFormatting = false

        // Get dates in available formats
        let _dateRelative = _formatRelative.string(from: from)
        let _dateFull = _formatFull.string(from: from)
        let _dateCustom = _formatCustom.string(from: from)

        // Return appropriatly formatted date/string
        if _dateRelative != _dateFull {

            return _dateRelative

        } else {

            return _dateCustom
        }
    }   
}
eranschau
  • 21
  • 2
0

@eranschau's answer doesn't cover localized dates, so I've added an optional parameter to set up Locale and also fixed a bug when the comparison won't work because unexpected uppercase in some cases.

extension DateFormatter
{
    /**
     * Returns a string representation of a given date in relative format.
     * If the current local doesn't offer a relative format for the given date,
     * then then a given format is applied.
     *
     * - parameter date: The date to be formatted
     * - parameter format: The format to be applied to non-relative dates
     * - parameter locale: The locale to be used for date formatting
     *
     * - returns: A string representing a formatted version of a given date
     */

    func relativeStringWithFormat(from: Date, format: String, locale: Locale? = nil) -> String
    {
        // Create date formatters
        let _formatRelative = DateFormatter()
            _formatRelative.dateStyle = .full
            _formatRelative.doesRelativeDateFormatting = true
            _formatRelative.timeStyle = .none
            _formatRelative.locale = locale ?? Locale.current

        let _formatFull = DateFormatter()
            _formatFull.dateStyle = .full
            _formatFull.doesRelativeDateFormatting = false
            _formatFull.timeStyle = .none
            _formatFull.locale = locale ?? Locale.current

        let _formatCustom = DateFormatter()
            _formatCustom.dateFormat = DateFormatter.dateFormat(fromTemplate: format, options: 0, locale: locale ?? Locale.current)

        // Get dates in available formats
        let _dateRelative = _formatRelative.string(from: from)
        let _dateFull = _formatFull.string(from: from)
        let _dateCustom = _formatCustom.string(from: from)

        // Return appropriatly formatted date/string
        if _dateRelative.caseInsensitiveCompare(_dateFull) != .orderedSame {
            return _dateRelative
        } else {
            return _dateCustom
        }
    }
}
nikans
  • 2,468
  • 1
  • 28
  • 35