barnabus
barnabus

Reputation: 882

How to calculate number of weeks in a given month?

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.

Upvotes: 4

Views: 8009

Answers (6)

matsotaa
matsotaa

Reputation: 133

The simplest solution imho is

Calendar.current.range(of: .weekOfMonth, in: .month, for: date)

no need to set any firstWeekday or using NSDateComponents.. nothing, but date. fyi firstWeekday could lead to unexpected behaviour since some areas using Monday as first day of week, not Sunday

Upvotes: 1

Viktor
Viktor

Reputation: 1147

Swift 4+:

Generate Date from your components:

let dateComponents = DateComponents.init(year: 2019, month: 5)
let monthCurrentDayFirst = Calendar.current.date(from: dateComponents)!
let monthNextDayFirst = Calendar.current.date(byAdding: .month, value: 1, to: monthCurrentDayFirst)!
let monthCurrentDayLast = Calendar.current.date(byAdding: .day, value: -1, to: monthNextDayFirst)!

Detect week number from date:

let numberOfWeeks = Calendar.current.component(.weekOfMonth, from: monthCurrentDayLast)

Upvotes: 0

Rajesh Loganathan
Rajesh Loganathan

Reputation: 11247

Swift 4+:

//-- Get number of weeks from calendar
func numberOfWeeksInMonth(_ date: Date) -> Int {
     var calendar = Calendar(identifier: .gregorian)
     calendar.firstWeekday = 1
     let weekRange = calendar.range(of: .weekOfMonth,
                                    in: .month,
                                    for: date)
     return weekRange!.count
}

//-- Implementation
let weekCount = numberOfWeeksInMonth(Date)

Upvotes: 1

Martin R
Martin R

Reputation: 540075

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
}

Upvotes: 8

Mike Chirico
Mike Chirico

Reputation: 3491

Swift 2

The following code calculates the number of weeks in a month. It does not depend on what day the month started or ended.

func weeksInMonth(month: Int, year: Int) -> (Int)? {

  let calendar = NSCalendar.currentCalendar()

  let comps = NSDateComponents()
  comps.month = month+1
  comps.year = year
  comps.day = 0

  guard let last = calendar.dateFromComponents(comps) else {
    return nil
  }
  // Note: Could get other options as well
  let tag = calendar.components([.WeekOfMonth,.WeekOfYear,
       .YearForWeekOfYear,.Weekday,.Quarter],fromDate: last)

  return tag.weekOfMonth

}

// Example Usage:

if let numWeeks = weeksInMonth(8,year: 2015) {
  print(numWeeks) // Prints 6
}

if let numWeeks = weeksInMonth(12,year: 2015) {
  print(numWeeks) // Prints 5
}

Upvotes: 1

vadian
vadian

Reputation: 285270

Actually your question is : How many Mondays are in a given month?

My approach is to calculate the first Monday for the month, this can be accomplished by setting the CalendarUnit WeekdayOrdinal to 1. Then get the number of total days and do some math.

Swift 1.2

func mondaysInMonth(month: Int, forYear year: Int) -> Int?
{
  if 1...12 ~= month {

    let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
    let components = NSDateComponents()
    components.weekday = 2 // Monday
    components.weekdayOrdinal = 1
    components.month = month
    components.year = year

    if let date = calendar.dateFromComponents(components)  {
      let firstDay = calendar.component(.CalendarUnitDay, fromDate: date)
      let days = calendar.rangeOfUnit(.CalendarUnitDay, inUnit:.CalendarUnitMonth, forDate:date).length
      return (days - firstDay) / 7 + 1
    }
  }
  return nil
}

Swift 2

func mondaysInMonth(month: Int, forYear year: Int) -> Int?
{
  guard 1...12 ~= month else { return nil }

  let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
  let components = NSDateComponents()
  components.weekday = 2 // Monday
  components.weekdayOrdinal = 1
  components.month = month
  components.year = year

  if let date = calendar.dateFromComponents(components)  {
    let firstDay = calendar.component(.Day, fromDate: date)
    let days = calendar.rangeOfUnit(.Day, inUnit:.Month, forDate:date).length
    return (days - firstDay) / 7 + 1
  }
  return nil
}

Upvotes: 5

Related Questions