Search code examples
xcodeswiftnsdatenscalendar

Swift - Check if date is in next week / month. ( isDateInNextWeek() isDateInNextMonth() )


We have those convenient functions in calendar:

let calendar = NSCalendar.currentCalendar()
calendar.isDateInToday(date)
calendar.isDateInTomorrow(date)

But I am missing those two:

  • calendar.isDateInNextWeek(date)

  • calendar.isDateInNextMonth(date)

As @Rob mentioned, the meaning is: "in the calendar week starting this coming Sunday, going through the following Saturday"

I having a hard time figuring out how to implement those functions in a robust way that covers all the corner cases.

Can someone provide an assistant?


Solution

  • The algorithm is not very spectacular:

    1. Calculate start of current week
    2. Use result from 1 to calculate start of next week
    3. Use result from 2 to calculate start of week after next week
    4. Date is in next week if its on or after the start of next week and before the start of the week after next week

    NSCalendar does most of the work for us. But it looks still a bit intimidating, at least if you don't use these date methods often. You should read the documentation of all of these methods to understand them.

    let calendar = NSCalendar.currentCalendar()
    let date = NSDate()
    
    var startOfThisWeek: NSDate?
    if !calendar.rangeOfUnit(.CalendarUnitWeekOfMonth, startDate: &startOfThisWeek, interval: nil, forDate: date) {
        fatalError("Can't calculate start of this week")
    }
    let startOfNextWeek = calendar.dateByAddingUnit(.CalendarUnitWeekOfMonth, value: 1, toDate: startOfThisWeek!, options: nil)!
    let startOfNextNextWeek = calendar.dateByAddingUnit(.CalendarUnitWeekOfMonth, value: 1, toDate: startOfNextWeek, options: nil)!
    

    Just switch .CalendarUnitWeekOfMonth with .CalendarUnitMonth and you get the calculation for this, next and the following month.

    var startOfThisMonth: NSDate?
    if !calendar.rangeOfUnit(.CalendarUnitMonth, startDate: &startOfThisMonth, interval: nil, forDate: date) {
        fatalError("Can't calculate start of this month")
    }
    let startOfNextMonth = calendar.dateByAddingUnit(.CalendarUnitMonth, value: 1, toDate: startOfThisMonth!, options: nil)!
    let startOfNextNextMonth = calendar.dateByAddingUnit(.CalendarUnitMonth, value: 1, toDate: startOfNextMonth, options: nil)!
    

     

    And the test:

    let testDate = NSDate(timeIntervalSinceNow: 7*24*60*60)
    
    if startOfThisWeek <= testDate && testDate < startOfNextWeek {
        println("\(testDate) is this week")
    }
    if startOfNextWeek <= testDate && testDate < startOfNextNextWeek {
        println("\(testDate) is next week")
    }
    if startOfThisMonth <= testDate && testDate < startOfNextMonth {
        println("\(testDate) is this month")
    }
    if startOfNextMonth <= testDate && testDate < startOfNextNextMonth {
        println("\(testDate) is next month")
    }
    

    Result:

    "2015-03-22 02:55:19 +0000 is next week"
    "2015-03-22 02:55:19 +0000 is this month"

    Sounds right.

    And if you want to use <=, ==, < etc. instead of the ugly (and confusing) NSComparisonResult you need this as well:

    public func ==(lhs: NSDate, rhs: NSDate) -> Bool {
        return lhs === rhs || lhs.compare(rhs) == .OrderedSame
    }
    
    public func <(lhs: NSDate, rhs: NSDate) -> Bool {
        return lhs.compare(rhs) == .OrderedAscending
    }
    
    extension NSDate: Comparable { }
    

    This is iOS8 code. If you want to write code that works on older versions you have to replace dateByAddingUnit:value::toDate:options: by good old NSDateComponents. i.e:

    let offSetComponents = NSDateComponents()
    offSetComponents.weekOfMonth = 1
    let startOfNextWeek = calendar.dateByAddingComponents(offSetComponents, toDate: startOfThisWeek, options: nil)