Reputation: 2114
I want to find the "last Sunday of a month before the current date" in Swift, but using the Calendar nextDate function doesn't work (always returns nil).
var calendar: Calendar = Calendar(identifier: .gregorian)
calendar.timeZone = .gmt
let lastSundayDateComponents: DateComponents = DateComponents(
weekday: 1,
weekdayOrdinal: -1
)
let previousLastSundayDate: Date? = calendar.nextDate(
after: Date.now,
matching: lastSundayDateComponents,
matchingPolicy: .nextTime,
repeatedTimePolicy: .first,
direction: .backward
)
print(previousLastSundayDate ?? "Not found") // "Not found"
If I use a positive weekdayOrdinal, it's working normally and the same nextDate method provides the correct date.
let firstSundayDateComponents: DateComponents = DateComponents(
weekday: 1,
weekdayOrdinal: 1
)
When I check if the date components can provide a valid date for the given calendar, it returns false...
let lastSundayInNovember2023DateComponents: DateComponents = DateComponents(
year: 2023,
month: 11,
weekday: 1,
weekdayOrdinal: -1
)
// THIS RETURNS FALSE
let isValid: Bool = lastSundayInNovember2023DateComponents.isValidDate(in: calendar)
print(isValid) // false
... even if the correct date can be created.
let lastSundayInNovember2023: Date = calendar.date(from: lastSundayInNovember2023DateComponents)!
print(lastSundayInNovember2023) // 2023-11-26 00:00:00 +0000
Is that a bug in Foundation?
Upvotes: 1
Views: 305
Reputation: 70946
A simpler approach would be to get the first day of the current month, and then look for the most recent Sunday before that.
You can do the first part by getting the current year and month and constructing a date with the same values but zeroes for everything else. That's the start of the current month.
Then use nextDate
to look backwards from that date to a date where the components have a weekday
of 1, for Sunday.
Put it together and it looks like this:
func lastSundayOfLastMonth(before referenceDate: Date = Date()) throws -> Date {
enum DateError: Error {
case lastSundayOfLastMonthNotFound
}
var calendar: Calendar = Calendar(identifier: .gregorian)
calendar.timeZone = .gmt
let sundayComponents = DateComponents(calendar: calendar, weekday: 1)
guard let startOfCurrentMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: referenceDate)) else {
throw DateError.lastSundayOfLastMonthNotFound
}
let matchedSunday = calendar.nextDate(after: startOfCurrentMonth, matching: sundayComponents, matchingPolicy: .nextTime, repeatedTimePolicy: .first, direction: .backward)
guard let lastSunday = matchedSunday else {
throw DateError.lastSundayOfLastMonthNotFound
}
return lastSunday
}
Call this as lastSundayOfLastMonth()
to use the current date as the reference, or as lastSundayOfLastMonth(before: someOtherDate)
to use a different date.
Upvotes: 0
Reputation: 271410
From the documentation of weekdayOrdinal
, it doesn't sound like it should ever be negative:
Weekday ordinal units represent the position of the weekday within the next larger calendar unit, such as the month. For example, 2 is the weekday ordinal unit for the second Friday of the month.
Its name also suggests that it is an ordinal number, which cannot be negative.
I would find the last Sunday of a month by finding what the weekdayOrdinal
of the next Sunday.
and so on.
var calendar: Calendar = Calendar(identifier: .gregorian)
calendar.timeZone = .gmt
let now = Date.now
let currentMonth = calendar.component(.month, from: now)
let nextSunday = calendar.nextDate(
after: now,
matching: DateComponents(weekday: 1),
matchingPolicy: .nextTime
)!
let nextSundayOrdinal = calendar.component(.weekdayOrdinal, from: nextSunday)
let previousLastSundayDate = calendar.date(
byAdding: .day,
value: -nextSundayOrdinal * 7,
to: nextSunday
)
print(previousLastSundayDate ?? "Not found")
Upvotes: 1