swiftdatensdatecomponents

Initialising DateComponents with weekOfMonth returning wrong date


Here is the code:

let components = DateComponents(year: 2022,month: 9,weekOfMonth: 3)
let date = Calendar.autoupdatingCurrent.date(from: components)!
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .none
print(formatter.string(from: date))

Here is output:

September 1, 2022

But the correct answer should be September 11, 2022. Why? Is this a bug or I just missed something?


Solution

  • This method will create a date for start of the given year, month and week in month. This will return nil if the start of the week is not in the month specified:

    func startOfWeek(in year: Int, month: Int, week: Int, calendar: Calendar = Calendar(identifier: .iso8601)) -> Date?{
        //You would need to set this calendar according when your week starts.
        //For me it´s monday so I need .iso8601
        //For sunday set it to gregorian
        
        var components = DateComponents(calendar: calendar, year: year, month: month, weekOfMonth: week )
        
        //Get the first day of the month. This will probably never fail but....
        guard let firstOfMonth = components.date else{
            return nil
        }
        
        // Handling edge case first of month is start of week
        // if the first is a monday and we look for the first return it
        let startOfWeek = calendar.dateComponents([.calendar, .yearForWeekOfYear, .weekOfYear], from: firstOfMonth).date
        if startOfWeek == firstOfMonth, week == 1{
            return firstOfMonth
        }
        // else just ommit the additional week and move on
        else if startOfWeek == firstOfMonth {
            components = DateComponents(calendar: calendar, year: year, month: month, weekOfMonth: week)
        }
        
        // search for the next date starting at first of month according to components given
        return calendar.nextDate(after: firstOfMonth, matching: components, matchingPolicy: .nextTime)
    }
    

    Most of the code should be self explanatory.


    Testing:

    let dates = [(2022, 1, 2), (2022, 2, 1), (2022, 3, 4), (2022, 8, 1), (2022, 9, 1), (2022, 9, 2), (2022, 9, 3), (2022, 9, 4), (2022, 12, 4) , (2021, 11, 5)]
    
    let formatter = DateFormatter()
    formatter.dateFormat = "dd MM yyyy"
    
    dates.forEach { year, month, week in
        let date = startOfWeek(in: year, month: month, week: week, calendar: Calendar(identifier: .gregorian))
        print(date != nil ? formatter.string(from: date!) : "no date found")
    }
    

    Output:

    02 01 2022
    no date found
    20 03 2022
    no date found
    no date found
    04 09 2022
    11 09 2022 <-- thrid week in september 2022
    18 09 2022
    18 12 2022
    28 11 2021