Есть ли какое-то готовое решение для работы с повторами (как в эппловском приложении напоминания)? Необходимо задать настройки повтора и выдавать true/false для определенной даты. При этом частота настраивается также гибко, например: каждый 5 день, каждые 2 недели по вторникам, ежемесячно каждые 10, 20, 30 число, ежемесячно первый понедельник, ежемесячно в последний будний день и тд. Также есть дата начала отсчета и опционально дата окончания.
Повторы - гибкая настройка и проверка
big
#4
Реализовал следующим образом, получилось даже более гибко:
extension Date {
var day: Int {
return Calendar.current.component(.day, from: self)
}
var weekday: Int {
return (Calendar.current.component(.weekday, from: self) + 7 - Calendar.current.firstWeekday) % 7 + 1
}
var startOfMonth: Date {
return Calendar.current.date(from: Calendar.current.dateComponents([.month, .year], from: Calendar.current.startOfDay(for: self)))!
}
}
func date(day: Int, month: Int, year: Int, hour: Int? = nil, minute: Int? = nil, second: Int? = nil) -> Date {
return Calendar.current.date(from: DateComponents(year: year, month: month, day: day, hour: hour, minute: minute, second: second)) ?? Date()
}
class Repeat {
enum Month: Int {
case january = 1, february, march, april, may, june, july, august, september, october, november, december
}
enum Day: Int {
case monday = 1, tuesday, wednesday, thursday, friday, saturday, sunday
case workingDay, dayOff
}
enum Index: Int {
case first, second, third, fourth, fifth, last
}
enum Every {
case day(Int)
case week(Int, [Day])
case month(Int, [Int], [(Index, Day)])
case year(Int, [Month], [(Index, Day)])
}
var every: Every
var from: Date
var till: Date?
init(every: Every, from: Date = Date(), till: Date? = nil) {
self.every = every
self.from = Calendar.current.startOfDay(for: from)
self.till = (till != nil ? Calendar.current.startOfDay(for: till!) : nil)
}
func indexDayToNumber(_date: Date, index: Index, day: Day) -> Int? {
let dateComponents = Calendar.current.dateComponents([.month, .year], from: _date)
guard let duration = Calendar.current.dateComponents([.day], from: _date.startOfMonth, to: Calendar.current.date(byAdding: DateComponents(month: 1), to: _date.startOfMonth)!).day, let month = dateComponents.month, let year = dateComponents.year else { return nil }
let days = (1 ..< duration + 1).indices.map {
($0, date(day: $0, month: month, year: year).weekday)
}.filter { ($0.1 == day.rawValue) || (day == .workingDay ? (1...5).contains($0.1) : false) || (day == .dayOff ? (6...7).contains($0.1) : false) }
return days[index == .last ? days.count - 1 : index.rawValue].0
}
func check(date: Date) -> Bool {
let date = Calendar.current.startOfDay(for: date)
guard date >= from, (till != nil ? date <= till! : true) else { return false }
switch every {
case .day(let frequency):
guard let duration = Calendar.current.dateComponents([.day], from: from, to: date).day else { return false }
return modf(Double(duration) / Double(frequency)).1 == 0
case .week(let frequency, let days):
guard let duration = Calendar.current.dateComponents([.weekOfYear, .yearForWeekOfYear], from: from, to: date).weekOfYear else { return false }
return modf(Double(duration) / Double(frequency)).1 == 0 && days.contains(where: { $0.rawValue == date.weekday })
case .month(let frequency, let numbers, let days):
guard let duration = Calendar.current.dateComponents([.month, .year], from: from, to: date).month else { return false }
let _numbers = days.map {
indexDayToNumber(_date: date, index: $0.0, day: $0.1)
}
return modf(Double(duration) / Double(frequency)).1 == 0 && (numbers.contains(date.day) || _numbers.contains(date.day))
case .year(let frequency, let months, let days):
guard let duration = Calendar.current.dateComponents([.year], from: from, to: date).year, let month = Calendar.current.dateComponents([.month, .year], from: date).month else { return false }
let _numbers = days.map {
indexDayToNumber(_date: date, index: $0.0, day: $0.1)
}
return modf(Double(duration) / Double(frequency)).1 == 0 && months.contains(Month(rawValue: month)!) && _numbers.contains(date.day)
}
}
}
//Использование:
//let _repeat = Repeat(every: .day(3), from: date(day: 1, month: 3, year: 2021))
//let _repeat = Repeat(every: .week(2, [.monday, .friday]), from: date(day: 1, month: 3, year: 2021))
//let _repeat = Repeat(every: .month(1, [15], [(.first, .monday), (.third, .wednesday), (.last, .workingDay)]), from: date(day: 1, month: 1, year: 2021))
let _repeat = Repeat(every: .year(1, [.january, .march], [(.last, .workingDay)]), from: date(day: 1, month: 1, year: 2021))
for i in 1 ... 3 {
for j in 1 ... 31 {
print("\(j).\(i).2021", _repeat.check(date: date(day: j, month: i, year: 2021)))
}
}