Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get number of weeks for a given ISO 8601 calendar year

I need to make a function for calculating the number of weeks of a given year in swift, according to the ISO-week-system (ISO8601). I need the number as an Int. First I figured I need to make a NSDate for december 31st and just ask what week that's in, then I realized sometimes december 31st is in week 1 of the following year (for example december 31st 2014) Anybody know of a way to do this?

I tried

var calendarr = NSCalendar(calendarIdentifier: NSCalendar.Identifier.ISO8601)!
var monday = "01-01-2005"

var dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy"
var datumet = dateFormatter.date(from: monday)
var weekRange = calendarr.range(of: .weekOfYear, in: .year, for: datumet!)
var weeksCount = weekRange.length

print("this many weeks", weeksCount)

But it gives me 53 weeks, no matter what year I put in. As a reference 2004 had 53 weeks and 2005 had 52 weeks

like image 422
Klas Zetterlund Avatar asked Mar 10 '23 17:03

Klas Zetterlund


1 Answers

A simple formula to determine the number of weeks in a year of the ISO calendar can be found in Long and Short ISO Calendar Years:

An ISO calendar year y is long (contains 53 weeks) if:

  • p(y) modulo 7 = 4
    or if:
  • p(y–1) modulo 7 = 3
    with:
  • p(y) = y + floor(y/4) – floor(y/100) + floor(y/400)

Note that the version in Wikipedia: Weeks per year seems to have a small error, "if p(year) = 5" should be "if p(year) = 4".

This is an implementation in Swift:

func weeks(in year: Int) -> Int {
    func p(_ year: Int) -> Int {
        return (year + year/4 - year/100 + year/400) % 7
    }
    return (p(year) == 4 || p(year-1) == 3) ? 53 : 52
}

Test:

print(weeks(in: 2015)) // 53
print(weeks(in: 2016)) // 52

let longYears = (2000...2100).filter({ weeks(in: $0) == 53 })
print(longYears)
// [2004, 2009, 2015, 2020, 2026, 2032, 2037, 2043, 2048, 2054, 2060, 2065, 2071, 2076, 2082, 2088, 2093, 2099]

which coincides with the list given in the Wikipedia article.


Re your edit: Your attempted code is almost correct. As @vadian already said in a comment, it should be

let weekRange = calendarr.range(of: .weekOfYear, in: .yearForWeekOfYear, for: datumet!)

In addition, January 1st may fall into the previous ISO year (e.g. for the year 2016). Choosing any day between January 4 and December 28 in the given year would solve that problem.

With some further simplifications that gives the following method (based on Kevin Sabbe's approach):

func weeks(in year: Int) -> Int {
    let cal = Calendar(identifier: .iso8601)
    let date = DateComponents(calendar: cal, year: year, month: 2, day: 1).date!
    let weeksRange = cal.range(of: .weekOfYear, in: .yearForWeekOfYear, for: date)!
    return weeksRange.count
}

which produces the same results as the function above.

Addendum: As @Olex suggested in a comment, this is even better (“no magic number '2' and stays in the paradigm of 'week-of-year' calculations”):

func weeks(in year: Int) -> Int {
    let cal = Calendar(identifier: .iso8601)
    let date = DateComponents(calendar: cal, weekday: 1, weekOfYear: 1, yearForWeekOfYear: year).date!
    let weeksRange = cal.range(of: .weekOfYear, in: .yearForWeekOfYear, for: date)!
    return weeksRange.count
}
like image 139
Martin R Avatar answered Mar 13 '23 07:03

Martin R