Как найти начало недели из NSDate?

Я реализую представление календаря, и я хотел бы, чтобы он начинался в начале недели, содержащей определенную дату. Например. Если целевая дата - понедельник, 29 февраля 2016 года, и текущий календарь будет установлен в воскресенье, я хотел бы, чтобы мое представление начиналось с воскресенья, 28 февраля.

Кажется, это должно быть просто:

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

Но это не удается:

Завершение приложения из-за неперехваченного исключения "NSInvalidArgumentException", причина: "Необходимо указать только один параметр из набора {NSCalendarMatchPreviousTimePreservingSmallerUnits, NSCalendarMatchNextTimePreservingSmallerUnits, NSCalendarMatchNextTime}. '

Я могу получить в основном то, что хочу:

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

Но это похоже на плохую практику, так как иногда количество секунд в день составляет не 86400.

Есть ли способ лучше?

Ответы

Ответ 1

Вы можете использовать метод Calendar date(from: DateComponents), передавая компоненты [.yearForWeekOfYear, .weekOfYear] с любой даты, которая вернет первый день недели из используемого календаря. Поэтому, если вы хотите получить воскресенье, просто используйте григорианский календарь. Если вы хотите, чтобы понедельник был первым днем недели, вы можете использовать Календарь .iso8601, как вы можете видеть в этом ответе

.Xcode 8.3.2 • Swift 3.1 или более поздняя версия

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))
    }
}

Использование:

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

Ответ 2

В календаре есть механизм для определения даты в начале заданного интервала времени (например, недели года или месяца), который содержит заданную дату:

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

Я выяснил в соответствии с моим требованием, где у меня есть даты для следующих.

1. Today

2. Tomorrow 

3. This Week 

4. This Weekend 

5. Next Week 

6. Next Weekend

Итак, я создал Date Extension для получения дат текущей недели и следующей недели.

КОД

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)
    }
}

ИСПОЛЬЗОВАНИЕ:

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

Вы можете изменить Extension в соответствии со своими потребностями.

Спасибо!

Ответ 4

В основном использовать

NSCalender

а также

dateByAddingComponents

Для решения проблемы вы пытаетесь использовать этот пример кода:

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

У меня были проблемы со всеми предыдущими решениями, так как они не учитывали настройки календаря пользователя. Следующий код будет учитывать это.

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!)

    }
}