Schedule/Sources/Schedule/Schedule.swift

447 lines
15 KiB
Swift

//
// Schedule.swift
// Schedule
//
// Created by Quentin Jin on 2018/7/2.
//
import Foundation
/// `Schedule` represents a plan that gives the times
/// at which a task should be executed.
///
/// `Schedule` is `Interval` based.
public struct Schedule {
private var sequence: AnySequence<Interval>
private init<S>(_ sequence: S) where S: Sequence, S.Element == Interval {
self.sequence = AnySequence(sequence)
}
func makeIterator() -> AnyIterator<Interval> {
return sequence.makeIterator()
}
/// Schedules a task with this schedule.
///
/// - Parameters:
/// - queue: The queue to which the task will be dispatched.
/// - host: The object to be hosted on. When this object is dealloced,
/// the task will not scheduled any more.
/// - onElapse: The action to do when time is out.
/// - Returns: The task just created.
@discardableResult
public func `do`(queue: DispatchQueue,
host: AnyObject? = nil,
onElapse: @escaping (Task) -> Void) -> Task {
return Task(schedule: self, queue: queue, host: host, onElapse: onElapse)
}
/// Schedules a task with this schedule.
///
/// - Parameters:
/// - queue: The queue to which the task will be dispatched.
/// - host: The object to be hosted on. When this object is dealloced,
/// the task will not scheduled any more.
/// - onElapse: The action to do when time is out.
/// - Returns: The task just created.
@discardableResult
public func `do`(queue: DispatchQueue,
host: AnyObject? = nil,
onElapse: @escaping () -> Void) -> Task {
return self.do(queue: queue, host: host, onElapse: { (_) in onElapse() })
}
}
extension Schedule {
/// Creates a schedule from a `makeUnderlyingIterator()` method.
///
/// The task will be executed after each interval
/// produced by the iterator that `makeUnderlyingIterator` returns.
///
/// For example:
///
/// let schedule = Schedule.make {
/// var i = 0
/// return AnyIterator {
/// i += 1
/// return i
/// }
/// }
/// schedule.do {
/// print(Date())
/// }
///
/// > "2001-01-01 00:00:00"
/// > "2001-01-01 00:00:01"
/// > "2001-01-01 00:00:03"
/// > "2001-01-01 00:00:06"
/// ...
public static func make<I>(_ makeUnderlyingIterator: @escaping () -> I) -> Schedule where I: IteratorProtocol, I.Element == Interval {
return Schedule(AnySequence(makeUnderlyingIterator))
}
/// Creates a schedule from an interval sequence.
/// The task will be executed after each interval in the sequence.
public static func from<S>(_ sequence: S) -> Schedule where S: Sequence, S.Element == Interval {
return Schedule(sequence)
}
/// Creates a schedule from an interval array.
/// The task will be executed after each interval in the array.
public static func of(_ intervals: Interval...) -> Schedule {
return Schedule(intervals)
}
}
extension Schedule {
/// Creates a schedule from a `makeUnderlyingIterator()` method.
///
/// The task will be executed at each date
/// produced by the iterator that `makeUnderlyingIterator` returns.
///
/// For example:
///
/// let schedule = Schedule.make {
/// return AnyIterator {
/// return Date().addingTimeInterval(3)
/// }
/// }
/// print("now:", Date())
/// schedule.do {
/// print("task", Date())
/// }
///
/// > "now: 2001-01-01 00:00:00"
/// > "task: 2001-01-01 00:00:03"
/// ...
///
/// You are not supposed to return `Date()` in making interator.
/// If you want to execute a task immediately,
/// use `Schedule.now` then `concat` another schedule instead.
public static func make<I>(_ makeUnderlyingIterator: @escaping () -> I) -> Schedule where I: IteratorProtocol, I.Element == Date {
return Schedule.make { () -> AnyIterator<Interval> in
var iterator = makeUnderlyingIterator()
var last: Date!
return AnyIterator {
last = last ?? Date()
guard let next = iterator.next() else { return nil }
defer { last = next }
return next.interval(since: last)
}
}
}
/// Creates a schedule from a date sequence.
/// The task will be executed at each date in the sequence.
public static func from<S>(_ sequence: S) -> Schedule where S: Sequence, S.Element == Date {
return Schedule.make(sequence.makeIterator)
}
/// Creates a schedule from a date array.
/// The task will be executed at each date in the array.
public static func of(_ dates: Date...) -> Schedule {
return Schedule.from(dates)
}
/// A dates sequence corresponding to this schedule.
public var dates: AnySequence<Date> {
return AnySequence { () -> AnyIterator<Date> in
let iterator = self.makeIterator()
var last: Date!
return AnyIterator {
last = last ?? Date()
guard let interval = iterator.next() else { return nil }
// swiftlint:disable shorthand_operator
last = last + interval
return last
}
}
}
}
extension Schedule {
/// A schedule with a distant past date.
public static var distantPast: Schedule {
return Schedule.of(Date.distantPast)
}
/// A schedule with a distant future date.
public static var distantFuture: Schedule {
return Schedule.of(Date.distantFuture)
}
/// A schedule that is never going to happen.
public static var never: Schedule {
return Schedule.make {
AnyIterator<Date> { nil }
}
}
}
extension Schedule {
/// Returns a new schedule by concatenating a schedule to this schedule.
///
/// For example:
///
/// let s0 = Schedule.of(1.second, 2.seconds, 3.seconds)
/// let s1 = Schedule.of(4.seconds, 4.seconds, 4.seconds)
/// let s2 = s0.concat(s1)
///
/// > s2
/// > 1.second, 2.seconds, 3.seconds, 4.seconds, 4.seconds, 4.seconds
public func concat(_ schedule: Schedule) -> Schedule {
return Schedule.make { () -> AnyIterator<Interval> in
let i0 = self.makeIterator()
let i1 = schedule.makeIterator()
return AnyIterator {
if let interval = i0.next() { return interval }
return i1.next()
}
}
}
/// Returns a new schedule by merging a schedule to this schedule.
///
/// For example:
///
/// let s0 = Schedule.of(1.second, 3.seconds, 5.seconds)
/// let s1 = Schedule.of(2.seconds, 4.seconds, 6.seconds)
/// let s2 = s0.concat(s1)
/// > s2
/// > 1.second, 1.seconds, 2.seconds, 2.seconds, 3.seconds, 3.seconds
public func merge(_ schedule: Schedule) -> Schedule {
return Schedule.make { () -> AnyIterator<Date> in
let i0 = self.dates.makeIterator()
let i1 = schedule.dates.makeIterator()
var buffer0: Date!
var buffer1: Date!
return AnyIterator<Date> {
if buffer0 == nil { buffer0 = i0.next() }
if buffer1 == nil { buffer1 = i1.next() }
var d: Date!
if let d0 = buffer0, let d1 = buffer1 {
d = Swift.min(d0, d1)
} else {
d = buffer0 ?? buffer1
}
if d == buffer0 { buffer0 = nil }
if d == buffer1 { buffer1 = nil }
return d
}
}
}
/// Returns a new schedule by only taking the first specific number of this schedule.
///
/// For example:
///
/// let s0 = Schedule.every(1.second)
/// let s1 = s0.first(3)
/// > s1
/// 1.second, 1.second, 1.second
public func first(_ count: Int) -> Schedule {
return Schedule.make { () -> AnyIterator<Interval> in
let iterator = self.makeIterator()
var num = 0
return AnyIterator {
guard num < count, let interval = iterator.next() else { return nil }
num += 1
return interval
}
}
}
/// Returns a new schedule by only taking the part before the date.
public func until(_ date: Date) -> Schedule {
return Schedule.make { () -> AnyIterator<Date> in
let iterator = self.dates.makeIterator()
return AnyIterator {
guard let next = iterator.next(), next < date else {
return nil
}
return next
}
}
}
}
extension Schedule {
/// Creates a schedule that executes the task immediately.
public static var now: Schedule {
return Schedule.of(0.nanosecond)
}
/// Creates a schedule that executes the task after delay.
public static func after(_ delay: Interval) -> Schedule {
return Schedule.of(delay)
}
/// Creates a schedule that executes the task every interval.
public static func every(_ interval: Interval) -> Schedule {
return Schedule.make {
AnyIterator { interval }
}
}
/// Creates a schedule that executes the task after delay then repeat
/// every interval.
public static func after(_ delay: Interval, repeating interval: Interval) -> Schedule {
return Schedule.after(delay).concat(Schedule.every(interval))
}
/// Creates a schedule that executes the task at the specific date.
public static func at(_ date: Date) -> Schedule {
return Schedule.of(date)
}
/// Creates a schedule that executes the task every period.
public static func every(_ period: Period) -> Schedule {
return Schedule.make { () -> AnyIterator<Interval> in
let calendar = Calendar.gregorian
var last: Date!
return AnyIterator {
last = last ?? Date()
guard let next = calendar.date(byAdding: period.toDateComponents(),
to: last) else {
return nil
}
defer { last = next }
return next.interval(since: last)
}
}
}
/// Creates a schedule that executes the task every period.
///
/// See Period's constructor
public static func every(_ period: String) -> Schedule {
guard let p = Period(period) else {
return Schedule.never
}
return Schedule.every(p)
}
}
extension Schedule {
/// `DateMiddleware` represents a middleware that wraps a schedule
/// which was only specified date without time.
///
/// You should call `at` method to get the schedule with time specified.
public struct DateMiddleware {
fileprivate let schedule: Schedule
/// Returns a schedule at the specific time.
public func at(_ time: Time) -> Schedule {
var interval = time.intervalSinceZeroClock
return Schedule.make { () -> AnyIterator<Interval> in
let it = self.schedule.makeIterator()
return AnyIterator {
if let next = it.next() {
defer { interval = 0.nanoseconds }
return next + interval
}
return nil
}
}
}
/// Returns a schedule at the specific time.
///
/// See Time's constructor
public func at(_ time: String) -> Schedule {
guard let time = Time(time) else {
return Schedule.never
}
return at(time)
}
/// Returns a schedule at the specific time.
///
/// .at(1) => 01
/// .at(1, 2) => 01:02
/// .at(1, 2, 3) => 01:02:03
/// .at(1, 2, 3, 456) => 01:02:03.456
public func at(_ time: Int...) -> Schedule {
let hour = time[0]
let minute = time.count > 1 ? time[1] : 0
let second = time.count > 2 ? time[2] : 0
let nanosecond = time.count > 3 ? time[3]: 0
guard let time = Time(hour: hour, minute: minute, second: second, nanosecond: nanosecond) else {
return Schedule.never
}
return at(time)
}
}
/// Creates a schedule that executes the task every specific weekday.
public static func every(_ weekday: Weekday) -> DateMiddleware {
let schedule = Schedule.make { () -> AnyIterator<Date> in
let calendar = Calendar.gregorian
var date: Date!
return AnyIterator<Date> {
if weekday.isToday {
date = Date().zeroClock()
} else if date == nil {
date = calendar.next(weekday, after: Date())
} else {
date = calendar.date(byAdding: .day, value: 7, to: date)
}
return date
}
}
return DateMiddleware(schedule: schedule)
}
/// Creates a schedule that executes the task every specific weekdays.
public static func every(_ weekdays: Weekday...) -> DateMiddleware {
var schedule = every(weekdays[0]).schedule
if weekdays.count > 1 {
for i in 1..<weekdays.count {
schedule = schedule.merge(Schedule.every(weekdays[i]).schedule)
}
}
return DateMiddleware(schedule: schedule)
}
/// Creates a schedule that executes the task every specific day in the month.
public static func every(_ monthday: Monthday) -> DateMiddleware {
let schedule = Schedule.make { () -> AnyIterator<Date> in
let calendar = Calendar.gregorian
var date: Date!
return AnyIterator<Date> {
if monthday.isToday {
date = Date().zeroClock()
} else if date == nil {
date = calendar.next(monthday, after: Date())
} else {
date = calendar.date(byAdding: .year, value: 1, to: date)
}
return date
}
}
return DateMiddleware(schedule: schedule)
}
/// Creates a schedule that executes the task every specific days in the months.
public static func every(_ mondays: Monthday...) -> DateMiddleware {
var schedule = every(mondays[0]).schedule
if mondays.count > 1 {
for i in 1..<mondays.count {
schedule = schedule.merge(Schedule.every(mondays[i]).schedule)
}
}
return DateMiddleware(schedule: schedule)
}
}