Description
Description
Calendar.RecurrenceRule
produces incorrect results or returns no results for ordinal weekday calculations (e.g., "1st Sunday", "last Friday") when the calendar's firstWeekday
property is not set to 1 (Sunday). This affects users in some European or other locales where Monday (or another weekday) is the first day of the week.
Steps to Reproduce 1
import Foundation
// Create calendar with Monday as first day (standard in some countries in Europe)
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
calendar.firstWeekday = 2 // Monday
// Find the last Sunday of January 2024
let rule = Calendar.RecurrenceRule(
calendar: calendar,
frequency: .monthly,
weekdays: [.nth(-1, .sunday)] // -1 means "last"
)
let jan2024 = calendar.date(from: DateComponents(year: 2024, month: 1, day: 1))!
let feb2024 = calendar.date(from: DateComponents(year: 2024, month: 2, day: 1))!
let results = Array(rule.recurrences(of: jan2024, in: jan2024..<feb2024))
// Expected: January 28 (the last Sunday)
// Actual: No results returned!
print("Results count: \(results.count)") // Prints "Results count: 0"
Expected Results
Ordinal weekday calculations should return the same dates regardless of firstWeekday
:
- Last Sunday of January 2024: January 28
- 1st Sunday of January 2024: January 7
Actual Results
With firstWeekday = 2
(Monday):
- Last Sunday of January 2024: No result ❌
- 1st Sunday of January 2024: January 14 ❌ (returns 2nd Sunday instead)
Full test results for "1st Sunday of January 2024":
firstWeekday=1
(Sun): January 7 ✅firstWeekday=2
(Mon): January 14 ❌ (wrong week)firstWeekday=3
(Tue): January 7 ✅firstWeekday=4
(Wed): January 7 ✅firstWeekday=5
(Thu): January 7 ✅firstWeekday=6
(Fri): January 7 ✅firstWeekday=7
(Sat): January 7 ✅
Steps to Reproduce 2
import Foundation
// Create a calendar with Wednesday as first day
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
calendar.firstWeekday = 4 // Wednesday
// Find the 4th Thursday of January 2024
let rule = Calendar.RecurrenceRule(
calendar: calendar,
frequency: .monthly,
weekdays: [.nth(4, .thursday)]
)
let startDate = calendar.date(from: DateComponents(year: 2024, month: 1, day: 1))!
let endDate = calendar.date(from: DateComponents(year: 2024, month: 2, day: 1))!
let results = Array(rule.recurrences(of: startDate, in: startDate..<endDate))
if let result = results.first {
let day = calendar.component(.day, from: result)
print("Got January \(day)") // Prints "Got January 18" (incorrect)
}
Expected Results
The 4th Thursday of January 2024 should always be January 25, regardless of the calendar's firstWeekday
setting.
Actual Results
The calculation returns different (incorrect) dates depending on firstWeekday
:
firstWeekday=1
(Sun): January 25 ✅firstWeekday=2
(Mon): January 25 ✅firstWeekday=3
(Tue): January 18 ❌firstWeekday=4
(Wed): January 18 ❌firstWeekday=5
(Thu): January 18 ❌firstWeekday=6
(Fri): January 25 ✅firstWeekday=7
(Sat): January 25 ✅
Environment
- iOS 18+, macOS 15.5
- Xcode 16.4
Workaround
Temporarily set calendar.firstWeekday = 1
before creating the RecurrenceRule
:
var calendar = Calendar(identifier: .gregorian)
calendar.locale = Locale(identifier: "en_GB") // UK locale (Monday first)
// ... configure calendar ...
// Workaround: Force Sunday as first day for calculation only
var calcCalendar = calendar
calcCalendar.firstWeekday = 1
let rule = Calendar.RecurrenceRule(
calendar: calcCalendar,
frequency: .monthly,
weekdays: [.nth(-1, .sunday)]
)
// Now returns correct results