【问题标题】:How do I find the beginning of the week from an NSDate?如何从 NSDate 中找到一周的开始?
【发布时间】:2016-06-11 18:36:44
【问题描述】:

我正在实现一个日历视图,我希望它在包含特定日期的一周开始时开始。例如。如果目标日期是 2016 年 2 月 29 日星期一,并且当前日历设置为从星期日开始,我希望我的视图从 2 月 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。

有没有更好的办法?

【问题讨论】:

    标签: swift date calendar


    【解决方案1】:

    您可以使用Calendar 方法date(from: DateComponents) 从任何日期传递[.yearForWeekOfYear, .weekOfYear] 组件,它将从使用的日历返回一周的第一天。因此,如果您想获得星期天,只需使用公历。如果您想将星期一作为一周的第一天,您可以使用日历 .iso8601,正如您在 answer 中看到的那样

    Xcode 12 • Swift 5.3 或更高版本(也适用于以前的 Swift 版本)

    extension Calendar {
        static let gregorian = Calendar(identifier: .gregorian)
    }
    

    extension Date {
        func startOfWeek(using calendar: Calendar = .gregorian) -> Date {
            calendar.dateComponents([.calendar, .yearForWeekOfYear, .weekOfYear], from: self).date!
        }
    }
    

    用法:

    Date().startOfWeek()  // "Sep 20, 2020 at 12:00 AM"
    


    如果您想在特定时区获得一周的开始,您只需要使用自定义日历:

    var gregorianUTC = Calendar.gregorian
    gregorianUTC.timeZone = TimeZone(identifier: "UTC")!
    print(Date().startOfWeek(using: gregorianUTC))  // "2020-09-20 00:00:00 +0000\n"
    

    【讨论】:

    • 谢谢!这是一种优雅的方式。
    • 这是一种很酷的实现方式,但它在夏令时方面存在一个小问题。这可以使用以下代码修复:var startOfWeek: NSDate { let date = Calendar.gregorian.dateFromComponents(Calendar.gregorian.components([.YearForWeekOfYear, .WeekOfYear ], fromDate: self))!; let dslTimeOffset = NSTimeZone.local.daylightSavingTimeOffset(for: date); return date.addingTimeInterval(dslTimeOffset); }
    • 最好使用 NSCalendar.autoupdatingCurrent 代替 gregorian
    • 你必须使用 Calendar.current 而不是 Calendar(identifier: .gregorian)
    • 可能不是,但我更喜欢让它保持可选并像往常一样使用防护
    【解决方案2】:

    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

    谢谢!

    【讨论】:

    • 别忘了您可以使用以下方法设置您想考虑的第一天:var gregorian = Calendar(identifier: .gregorian) gregorian.firstWeekday = 2 // Monday
    • 正确,谢谢指正
    【解决方案3】:

    您可以将其实现为 Date 类扩展或其他东西。它应该返回类似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()))!
        )
    } 
    

    【讨论】:

    • 这将返回 UTC 时间而不是本地时间的一周开始时间。
    【解决方案4】:

    日历有一种机制可以在包含给定日期的给定时间间隔(例如一年中的一周或一个月)的开始处查找日期:

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

    【讨论】:

      【解决方案5】:

      基本使用

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

      【讨论】:

      • 谢谢!虽然我最终选择了另一种解决问题的方法,但这个答案帮助我更好地理解了如何使用 dateByAddingComponents。
      【解决方案6】:

      我之前的所有解决方案都有问题,因为它们没有考虑用户的日历设置。下一个代码将考虑到这一点。

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

      【讨论】:

        【解决方案7】:

        为了正确遵守用户的区域设置,您应该在DateComponents 中使用用户的Calendar firstWeekday 属性。这是我通常使用的:

        // MARK: first day of week
        extension Date {
          /**
            Finds the first day of the week the subject date falls into.
           
           - Parameter calendar: The calendar to use. Defaults to the user's current calendar.
           
           - Returns: The `Date` of the first day of the week into which the subject date falls.
           
           `startOfWeek()` respects the user's locale settings, i.e. will automatically use Sunday/Monday/etc. as first
           weekday based on the user's region and locale settings.
           */
          func startOfWeek(using calendar: Calendar = .current) -> Date? {
            var components = calendar.dateComponents([.weekday, .year, .month, .weekOfYear], from: self)
            components.weekday = calendar.firstWeekday
            return calendar.date(from: components)
          }
        }
        
        

        【讨论】:

          猜你喜欢
          • 2015-10-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多