42

What is the best way to know if a date is in the same week (or year or month) as another, preferably with an extension, and solely using Swift?

As an example, in Objective-C I have

- (BOOL)isSameWeekAs:(NSDate *)date {
    NSDateComponents *otherDay = [[NSCalendar currentCalendar] components:NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:self];
    NSDateComponents *today = [[NSCalendar currentCalendar] components:NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:date];
    return ([today weekOfYear]   == [otherDay weekOfYear] &&
            [today year]         == [otherDay year] &&
            [today era]          == [otherDay era]);
}

Please don't propose solutions bridging Date to NSDate

Tancrede Chazallet
  • 7,035
  • 6
  • 41
  • 62
  • 3
    Update your question with your attempt to convert this code to Swift. Clearly describe what issues you are having. – rmaddy Apr 27 '17 at 16:49
  • Did you read through this entire post? [NSDate Comparison using Swift](http://stackoverflow.com/q/26198526/2415822) – JAL Apr 27 '17 at 16:50
  • @rmaddy Sorry that question wasn't a problem per say, but having not found a direct answer through a search, I though it was a question worth getting into SO for people in need for the same answer. But yes I was lazy because I didn't came with the answer directly, mea culpa. – Tancrede Chazallet Apr 27 '17 at 17:22

2 Answers2

139

You can use calendar method isDate(equalTo:granularity:) to check it as follow:

Xcode 11 • Swift 5.1

extension Date {

    func isEqual(to date: Date, toGranularity component: Calendar.Component, in calendar: Calendar = .current) -> Bool {
        calendar.isDate(self, equalTo: date, toGranularity: component)
    }

    func isInSameYear(as date: Date) -> Bool { isEqual(to: date, toGranularity: .year) }
    func isInSameMonth(as date: Date) -> Bool { isEqual(to: date, toGranularity: .month) }
    func isInSameWeek(as date: Date) -> Bool { isEqual(to: date, toGranularity: .weekOfYear) }

    func isInSameDay(as date: Date) -> Bool { Calendar.current.isDate(self, inSameDayAs: date) }

    var isInThisYear:  Bool { isInSameYear(as: Date()) }
    var isInThisMonth: Bool { isInSameMonth(as: Date()) }
    var isInThisWeek:  Bool { isInSameWeek(as: Date()) }

    var isInYesterday: Bool { Calendar.current.isDateInYesterday(self) }
    var isInToday:     Bool { Calendar.current.isDateInToday(self) }
    var isInTomorrow:  Bool { Calendar.current.isDateInTomorrow(self) }

    var isInTheFuture: Bool { self > Date() }
    var isInThePast:   Bool { self < Date() }
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • 1
    Let's say we use `Calendar.current.isDate(self, equalTo: date, toGranularity: .day)` and dates are 1 january of 2016 and 2017, does it return false ? – Tancrede Chazallet Apr 27 '17 at 17:39
  • yes it would be same as isDateInSameDay. See my edit – Leo Dabus Apr 27 '17 at 17:40
  • 1
    Accepting that answer as I think readability > functional, but @Mr.Hedgehog is a very nice answer – Tancrede Chazallet Apr 27 '17 at 17:44
  • Why are you passing 'date' as argument ? this can be done like Calendar.current.isDate(self, equalTo: Date(), toGranularity: .year) and using like dateToBeChecked. isInSameYear() I guess, you are doing reverse (currentDate.isInSameYear(checkingDate)) – infiniteLoop Sep 02 '19 at 18:19
12

You can use this extension that is based on the Objective-C code you've provided:

extension Date {
    func hasSame(_ components: Calendar.Component..., as date: Date, using calendar: Calendar = .autoupdatingCurrent) -> Bool {
             return components.filter { calendar.component($0, from: date) != calendar.component($0, from: self) }.isEmpty
    }
}

The default calendar here is the autoupdatingCurrent so if user changes the calendar it will update accordingly. Also it uses variadic parameter components that allows you to specify any number of components without wrapping them into an array.

Example of usage:

let date1 = Date()
let date2 = Date()
let date3 = Date(timeIntervalSince1970: 30.0)

print(date1.hasSame(.weekOfYear, .day, .hour, as: date2)) // true
print(date1.hasSame(.weekOfYear, as: date3)) // false

EDIT

As @Leo Dabus pointed out we may use set here instead of array/variadic parameters - that way we'll avoid comparing the same components many times if user passes some duplicates to the method:

extension Date {
    func hasSame(_ components: Set<Calendar.Component>, as date: Date, using calendar: Calendar = .autoupdatingCurrent) -> Bool {
             return components.filter { calendar.component($0, from: date) != calendar.component($0, from: self) }.isEmpty
    }
}
Mr. Hedgehog
  • 966
  • 4
  • 13
  • Why would we use set here? I cannot see any point except for removing duplicates – Mr. Hedgehog Apr 27 '17 at 19:18
  • Ok i see, at first I was not convinced if we should prevent such a misusage but that's like no cost at all (except for the variadic syntax which is more of a personal preference). Thanks for your comment I'll update my answer – Mr. Hedgehog Apr 27 '17 at 19:22