How do I find the beginning of the week from an NS

2020-02-01 04:00发布

问题:

I'm implementing a calendar view, and I'd like it to start at the beginning of the week containing a particular date. Eg. If the target date is Monday, Feb 29, 2016, and the current calendar is set to start on Sunday, I'd like my view to start with Sunday, February 28.

This seems like it should be straightforward:

let calendar = NSCalendar.currentCalendar()
let firstDate = calendar.nextDateAfterDate(targetDate, 
                    matchingUnit: .Weekday,
                           value: calendar.firstWeekday,
                         options: .SearchBackwards)

But this fails with:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Exactly one option from the set {NSCalendarMatchPreviousTimePreservingSmallerUnits, NSCalendarMatchNextTimePreservingSmallerUnits, NSCalendarMatchNextTime} must be specified.'

I can get basically what I want with:

let firstDate = calendar.nextDateAfterDate(firstDate, 
                    matchingUnit: .Weekday,
                           value: calendar.firstWeekday,
                        options: .MatchPreviousTimePreservingSmallerUnits)?
                    .dateByAddingTimeInterval(-7 * 84600)

But it seems like a bad practice, since sometimes the number of seconds in a day isn't 86400.

Is there a better way?

回答1:

you can use Calendar method date(from: DateComponents) passing [.yearForWeekOfYear, .weekOfYear] components from any date it will return the first day of the week from the calendar used. So if you would like to get Sunday just use Gregorian calendar. If you would like to get the Monday as the first day of the week you can use Calendar .iso8601 as you can see in this answer

Xcode 8.3.2 • Swift 3.1 or later

extension Calendar {
    static let gregorian = Calendar(identifier: .gregorian)
}
extension Date {
    var startOfWeek: Date? {
        return Calendar.gregorian.date(from: Calendar.gregorian.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self))
    }
}

usage:

Date().startOfWeek   // "Oct 15, 2017 at 1:00 AM" (note that daylight savings took effect last Sunday, thats why it shows 1am)


回答2:

The Calendar has a mechanism for finding date at the start of a given time interval (say week of year, or month) that contains a given date:

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let date = dateFormatter.date(from: "2017-01-07")

if let date = date {
    let calendar = Calendar(identifier: .gregorian)

    var startDate : Date = Date()
    var interval : TimeInterval = 0

    if calendar.dateInterval(of: .weekOfYear, start: &startDate, interval: &interval, for: date) {
        print("Start of week is \(startDate)")
        // prints "Start of week is 2017-01-01 06:00:00 +0000"
    }
}


回答3:

Swift 4 Solution

I have figured out according to my requirement, where I have find out dates for following.

1. Today

2. Tomorrow 

3. This Week 

4. This Weekend 

5. Next Week 

6. Next Weekend

So, I have created Date Extension to get Dates of Current Week and Next Week.

CODE

extension Date {

    func getWeekDates() -> (thisWeek:[Date],nextWeek:[Date]) {
        var tuple: (thisWeek:[Date],nextWeek:[Date])
        var arrThisWeek: [Date] = []
        for i in 0..<7 {
            arrThisWeek.append(Calendar.current.date(byAdding: .day, value: i, to: startOfWeek)!)
        }
        var arrNextWeek: [Date] = []
        for i in 1...7 {
            arrNextWeek.append(Calendar.current.date(byAdding: .day, value: i, to: arrThisWeek.last!)!)
        }
        tuple = (thisWeek: arrThisWeek,nextWeek: arrNextWeek)
        return tuple
    }

    var tomorrow: Date {
        return Calendar.current.date(byAdding: .day, value: 1, to: noon)!
    }
    var noon: Date {
        return Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
    }

    var startOfWeek: Date {
        let gregorian = Calendar(identifier: .gregorian)
        let sunday = gregorian.date(from: gregorian.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self))
        return gregorian.date(byAdding: .day, value: 1, to: sunday!)!
    }

    func toDate(format: String) -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = format
        return formatter.string(from: self)
    }
}

USAGE:

let arrWeekDates = Date().getWeekDates() // Get dates of Current and Next week.
let dateFormat = "MMM dd" // Date format
let thisMon = arrWeekDates.thisWeek.first!.toDate(format: dateFormat)
let thisSat = arrWeekDates.thisWeek[arrWeekDates.thisWeek.count - 2].toDate(format: dateFormat)
let thisSun = arrWeekDates.thisWeek[arrWeekDates.thisWeek.count - 1].toDate(format: dateFormat)

let nextMon = arrWeekDates.nextWeek.first!.toDate(format: dateFormat)
let nextSat = arrWeekDates.nextWeek[arrWeekDates.nextWeek.count - 2].toDate(format: dateFormat)
let nextSun = arrWeekDates.nextWeek[arrWeekDates.nextWeek.count - 1].toDate(format: dateFormat)

print("Today: \(Date().toDate(format: dateFormat))") // Sep 26
print("Tomorrow: \(Date().tomorrow.toDate(format: dateFormat))") // Sep 27
print("This Week: \(thisMon) - \(thisSun)") // Sep 24 - Sep 30
print("This Weekend: \(thisSat) - \(thisSun)") // Sep 29 - Sep 30
print("Next Week: \(nextMon) - \(nextSun)") // Oct 01 - Oct 07
print("Next Weekend: \(nextSat) - \(nextSun)") // Oct 06 - Oct 07

You can modify Extension according to your need.

Thanks!



回答4:

Basically use

NSCalender

and

dateByAddingComponents

. For solving of you're problem try to use this code sample:

let cal = NSCalendar.currentCalendar()

let components = NSDateComponents()
components.weekOfYear -= 1

if let date = cal.dateByAddingComponents(components, toDate: NSDate(), options: NSCalendarOptions(0)) {
    var beginningOfWeek: NSDate?
    var weekDuration = NSTimeInterval()
    if cal.rangeOfUnit(.CalendarUnitWeekOfYear, startDate: &beginningOfWeek, interval: &weekDuration, forDate: date) {
         print(beginningOfWeek) 
    }
}


回答5:

I had problems with all previous solutions, since they do not take into account user's calendar setting. Next code will be taking into account that.

extension Date {    

    var startOfWeek: Date? {
        let calendar = Calendar.current
        var components: DateComponents? = calendar.dateComponents([.weekday, .year, .month, .day], from: self)
        var modifiedComponent = components
        modifiedComponent?.day = (components?.day ?? 0) - ((components?.weekday ?? 0) - 1)

        return calendar.date(from: modifiedComponent!)
    }

    var endOfWeek: Date? {
        let calendar = Calendar.current
        var components: DateComponents? = calendar.dateComponents([.weekday, .year, .month, .day], from: self)
        var modifiedComponent = components
        modifiedComponent?.day = (components?.day ?? 0) + (7 - (components?.weekday ?? 0))
        modifiedComponent?.hour = 23
        modifiedComponent?.minute = 59
        modifiedComponent?.second = 59

        return calendar.date(from: modifiedComponent!)

    }
}


回答6:

You can implement this as Date class extension or something. It should returns something like 2020-01-06 00:00:00 +0000

Xcode 11.3 Swift 5

func firstDayOfWeek() -> Date {
    var c = Calendar(identifier: .iso8601)
    c.timeZone = TimeZone(secondsFromGMT: 0)!
    print(
        c.date(from: c.dateComponents([.weekOfYear, .yearForWeekOfYear], from: Date()))!
    )
}