问题
We have those convenient functions in calendar:
let calendar = NSCalendar.currentCalendar()
calendar.isDateInToday(date)
calendar.isDateInTomorrow(date)
But I am missing those two:
calendar.isDateInNextWeek(date)
calendar.isDateInNextMonth(date)
As @Rob mentioned, the meaning is: "in the calendar week starting this coming Sunday, going through the following Saturday"
I having a hard time figuring out how to implement those functions in a robust way that covers all the corner cases.
Can someone provide an assistant?
回答1:
The algorithm is not very spectacular:
- Calculate start of current week
- Use result from 1 to calculate start of next week
- Use result from 2 to calculate start of week after next week
- Date is in next week if its on or after the start of next week and before the start of the week after next week
NSCalendar does most of the work for us. But it looks still a bit intimidating, at least if you don't use these date methods often. You should read the documentation of all of these methods to understand them.
let calendar = NSCalendar.currentCalendar()
let date = NSDate()
var startOfThisWeek: NSDate?
if !calendar.rangeOfUnit(.CalendarUnitWeekOfMonth, startDate: &startOfThisWeek, interval: nil, forDate: date) {
fatalError("Can't calculate start of this week")
}
let startOfNextWeek = calendar.dateByAddingUnit(.CalendarUnitWeekOfMonth, value: 1, toDate: startOfThisWeek!, options: nil)!
let startOfNextNextWeek = calendar.dateByAddingUnit(.CalendarUnitWeekOfMonth, value: 1, toDate: startOfNextWeek, options: nil)!
Just switch .CalendarUnitWeekOfMonth
with .CalendarUnitMonth
and you get the calculation for this, next and the following month.
var startOfThisMonth: NSDate?
if !calendar.rangeOfUnit(.CalendarUnitMonth, startDate: &startOfThisMonth, interval: nil, forDate: date) {
fatalError("Can't calculate start of this month")
}
let startOfNextMonth = calendar.dateByAddingUnit(.CalendarUnitMonth, value: 1, toDate: startOfThisMonth!, options: nil)!
let startOfNextNextMonth = calendar.dateByAddingUnit(.CalendarUnitMonth, value: 1, toDate: startOfNextMonth, options: nil)!
And the test:
let testDate = NSDate(timeIntervalSinceNow: 7*24*60*60)
if startOfThisWeek <= testDate && testDate < startOfNextWeek {
println("\(testDate) is this week")
}
if startOfNextWeek <= testDate && testDate < startOfNextNextWeek {
println("\(testDate) is next week")
}
if startOfThisMonth <= testDate && testDate < startOfNextMonth {
println("\(testDate) is this month")
}
if startOfNextMonth <= testDate && testDate < startOfNextNextMonth {
println("\(testDate) is next month")
}
Result:
"2015-03-22 02:55:19 +0000 is next week"
"2015-03-22 02:55:19 +0000 is this month"
Sounds right.
And if you want to use <=
, ==
, <
etc. instead of the ugly (and confusing) NSComparisonResult
you need this as well:
public func ==(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs === rhs || lhs.compare(rhs) == .OrderedSame
}
public func <(lhs: NSDate, rhs: NSDate) -> Bool {
return lhs.compare(rhs) == .OrderedAscending
}
extension NSDate: Comparable { }
This is iOS8 code. If you want to write code that works on older versions you have to replace dateByAddingUnit:value::toDate:options:
by good old NSDateComponents. i.e:
let offSetComponents = NSDateComponents()
offSetComponents.weekOfMonth = 1
let startOfNextWeek = calendar.dateByAddingComponents(offSetComponents, toDate: startOfThisWeek, options: nil)
回答2:
Problem solved using Swift 5
and extending Calendar
.
extension Calendar {
private var currentDate: Date { return Date() }
func isDateInThisWeek(_ date: Date) -> Bool {
return isDate(date, equalTo: currentDate, toGranularity: .weekOfYear)
}
func isDateInThisMonth(_ date: Date) -> Bool {
return isDate(date, equalTo: currentDate, toGranularity: .month)
}
func isDateInNextWeek(_ date: Date) -> Bool {
guard let nextWeek = self.date(byAdding: DateComponents(weekOfYear: 1), to: currentDate) else {
return false
}
return isDate(date, equalTo: nextWeek, toGranularity: .weekOfYear)
}
func isDateInNextMonth(_ date: Date) -> Bool {
guard let nextMonth = self.date(byAdding: DateComponents(month: 1), to: currentDate) else {
return false
}
return isDate(date, equalTo: nextMonth, toGranularity: .month)
}
func isDateInFollowingMonth(_ date: Date) -> Bool {
guard let followingMonth = self.date(byAdding: DateComponents(month: 2), to: currentDate) else {
return false
}
return isDate(date, equalTo: followingMonth, toGranularity: .month)
}
}
Usage:
let day: Double = 60 * 60 * 24
let currentDate = Date() // Thu 25 Apr 2019
let futureDate = Date(timeInterval: 3 * day, since: currentDate) // Sun 28 Apr 2019
if Calendar.current.isDateInThisWeek(futureDate) {
// this will be executed if first day of week is Monday
} else {
// this will be executed if first day of week is Sunday
}
Code evaluates if the dates are in the range (sameWeek, nextWeek) according to Calendar
instance.
If Calendar
instance is current
it will determine start of the week (Monday or Sunday) according to device settings, but if you want different behaviour you can change Calendar
instance:
Calendar(identifier: .chinese).isDateInNextWeek(someDate)
This code also works if we're trying to create DateComponents
with improper values like so: DateComponents(year: 2019, month: 13)
.
In this case it creates date 01 Jan 2020
.
回答3:
I came up with this naive code:
extension NSCalendar
{
func isDateInNextWeek(value: NSDate) -> Bool
{
let componentsValue = self.components(.WeekOfYearCalendarUnit | .YearCalendarUnit, fromDate: value)
let componentsToday = self.components(.WeekOfYearCalendarUnit | .YearCalendarUnit, fromDate: NSDate())
let weekOfYearValue = componentsValue.weekOfYear
let weekOfYearToday = componentsToday.weekOfYear
let yearValue = componentsValue.year
let yearToday = componentsToday.year
if(yearValue < yearToday)
{
return false
}
else if(yearValue == yearToday && weekOfYearValue - weekOfYearToday == 1)
{
return true
}
else if(yearValue - yearToday == 1 && weekOfYearValue == 1 && weekOfYearToday == 52)
{
return true
}
else
{
return false
}
}
}
But I must be missing some edge cases here.
来源:https://stackoverflow.com/questions/29055654/swift-check-if-date-is-in-next-week-month-isdateinnextweek-isdateinnext