Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calculate number of weeks in a given month - Swift

Tags:

swift

swift2

Having looked at a few different suggestions on SO, I've not been able to determine why the function below does not work. It seems to return 6 for some months and 5 for others. When changing weeks for days it works perfectly.

For example, trying

weeksInMonth(8, forYear 2015) 

Results in 6.

I believe I have a mis-understanding of what 'firstWeekday' property on calendar does but haven't found an adequate explanation by Apple or online.

Have tried both .WeekOfMonth and .WeekOfYear. Again can't find explanation of exact difference.

Any suggestions would be greatly welcome.

func weeksInMonth(month: Int, forYear year: Int) -> Int?
{
    if (month < 1 || month > 12) { return nil }

    let dateString = String(format: "%4d/%d/01", year, month)
    let dateFormatter = NSDateFormatter()
    dateFormatter.dateFormat = "yyyy/MM/dd"
    if let date = dateFormatter.dateFromString(dateString),
       let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
    {
        calendar.firstWeekday = 2 // Monday
        let weekRange = calendar.rangeOfUnit(.WeekOfMonth, inUnit: .Month, forDate: date)
        let weeksCount = weekRange.length
        return weeksCount
    }
    else
    {
        return nil
    }
}

Update:

Apologies my question was not clear. I'm trying to work out how many weeks there are in a month that include a Monday in them. For August this should be 5.

like image 598
barnabus Avatar asked Dec 10 '22 21:12

barnabus


1 Answers

Your code computes the number of weeks which occur (complete or partially) in a month. What you apparently want is the number of Mondays in the given month. With NSDateComponents and in particular with the weekdayOrdinal property you can compute the first (weekdayOrdinal=1) and last (weekdayOrdinal=-1) Monday in a month. Then compute the difference in weeks (and add one).

A possible implementation in Swift 2:

func numberOfMondaysInMonth(month: Int, forYear year: Int) -> Int?
{
    let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
    calendar.firstWeekday = 2 // 2 == Monday

    // First monday in month:
    let comps = NSDateComponents()
    comps.month = month
    comps.year = year
    comps.weekday = calendar.firstWeekday
    comps.weekdayOrdinal = 1
    guard let first = calendar.dateFromComponents(comps)  else {
        return nil
    }

    // Last monday in month:
    comps.weekdayOrdinal = -1
    guard let last = calendar.dateFromComponents(comps)  else {
        return nil
    }

    // Difference in weeks:
    let weeks = calendar.components(.WeekOfMonth, fromDate: first, toDate: last, options: [])
    return weeks.weekOfMonth + 1
}

Note: That a negative weekdayOrdinal counts backwards from the end of the month is not apparent form the documentation. It was observed in Determine NSDate for Memorial Day in a given year and confirmed by Dave DeLong).


Update for Swift 3:

func numberOfMondaysInMonth(_ month: Int, forYear year: Int) -> Int? {
    var calendar = Calendar(identifier: .gregorian)
    calendar.firstWeekday = 2 // 2 == Monday

    // First monday in month:
    var comps = DateComponents(year: year, month: month,
                               weekday: calendar.firstWeekday, weekdayOrdinal: 1)
    guard let first = calendar.date(from: comps)  else {
        return nil
    }

    // Last monday in month:
    comps.weekdayOrdinal = -1
    guard let last = calendar.date(from: comps)  else {
        return nil
    }

    // Difference in weeks:
    let weeks = calendar.dateComponents([.weekOfMonth], from: first, to: last)
    return weeks.weekOfMonth! + 1
}
like image 188
Martin R Avatar answered Dec 29 '22 10:12

Martin R