Improve api, add tests
This commit is contained in:
parent
f48758d4f7
commit
792eabf3d4
58
README.md
58
README.md
|
@ -1,18 +1,20 @@
|
|||
# Schedule
|
||||
|
||||
⏰ A interval-based and date-based task scheduler for swift, with incredibly sweet apis.
|
||||
⏰ An interval-based and date-based task scheduler for swift, with incredibly sweet api.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- 📆 Date-based scheduling
|
||||
- ⏳ Interval-based scheduling
|
||||
- 📝 Mixture rules scheduling
|
||||
- 📝 Mixture rules
|
||||
- 👩🎓 Human readable period parsing
|
||||
- 🚦 Suspend, resume, cancel
|
||||
- 🏷 Tag related management
|
||||
- 🏷 Tag-based management
|
||||
- 🍰 Action appending/removing
|
||||
- 🍻 No need to concern about runloop
|
||||
- 👻 No need to concern about circular reference
|
||||
- 🍭 **Sweet apis**
|
||||
- 🍭 **Incredibly Sweet API**
|
||||
|
||||
|
||||
## Usage
|
||||
|
@ -20,10 +22,12 @@
|
|||
Scheduling a task can not be simplier.
|
||||
|
||||
```swift
|
||||
func heartBeat() { }
|
||||
Schedule.every(0.5.seconds).do(heartBeat)
|
||||
Schedule.every(1.second).do {
|
||||
print("heart beat")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Interval-based Scheduling
|
||||
|
||||
```swift
|
||||
|
@ -43,7 +47,6 @@ Schedule.from([1.second, 2.minutes, 3.hours]).do { }
|
|||
```
|
||||
|
||||
|
||||
|
||||
### Date-based Scheduling
|
||||
|
||||
```swift
|
||||
|
@ -55,14 +58,15 @@ Schedule.every(.monday, .tuesday).at("11:11").do { }
|
|||
|
||||
Schedule.every(.september(30)).at("10:00:00").do { }
|
||||
|
||||
Schedule.every("one month and ten days").do { }
|
||||
|
||||
Schedule.of(date0, date1, date2).do { }
|
||||
|
||||
Schedule.from([date0, date1, date2]).do { }
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Custom Scheduling
|
||||
### Mixture rules
|
||||
|
||||
```swift
|
||||
import Schedule
|
||||
|
@ -76,28 +80,27 @@ birthdaySchedule.do {
|
|||
}
|
||||
|
||||
/// merge
|
||||
let s3 = Schedule.every(.january(1)).at(8, 30)
|
||||
let s4 = Schedule.every(.october(1)).at(8, 30)
|
||||
let s3 = Schedule.every(.january(1)).at("8:00")
|
||||
let s4 = Schedule.every(.october(1)).at("9:00 AM")
|
||||
let holiday = s3.merge(s3)
|
||||
holidaySchedule.do {
|
||||
print("Happy holiday")
|
||||
}
|
||||
|
||||
/// count
|
||||
/// cut
|
||||
let s5 = Schedule.after(5.seconds).concat(Schedule.every(1.day))
|
||||
let s6 = s5.count(10)
|
||||
let s6 = s5.cut(10)
|
||||
|
||||
/// until
|
||||
let s7 = Schedule.every(.monday).at(11, 12)
|
||||
let s8 = s7.until(date)
|
||||
```
|
||||
|
||||
|
||||
### Task management
|
||||
|
||||
In general, you don't need to care about the reference management of task. All tasks will be held by a inner shared instance of class `TaskCenter`, so they won't be released, unless you do that yourself.
|
||||
In general, you don't need to concern about the reference management of task. All tasks will be retained internally, so they won't be released, unless you do it yourself.
|
||||
|
||||
#### Operation
|
||||
#### Manipulation
|
||||
|
||||
```swift
|
||||
let task = Schedule.every(1.day).do { }
|
||||
|
@ -142,7 +145,22 @@ let key = dailyTask.addAction {
|
|||
dailyTask.removeAction(byKey: key)
|
||||
```
|
||||
|
||||
There is also a more elegant way to deal with task's lifecycle:
|
||||
### Lifecycle
|
||||
|
||||
You can get the current timeline of the task:
|
||||
|
||||
```swift
|
||||
let timeline = task.timeline
|
||||
print(timeline.firstSchedule)
|
||||
print(timeline.lastSchedule)
|
||||
print(timeline.lastSchedule)
|
||||
print(timeline.activate)
|
||||
print(timeline.cancel)
|
||||
```
|
||||
|
||||
### Parasitism
|
||||
|
||||
There is a more elegant way to deal with task's lifecycle:
|
||||
|
||||
```swift
|
||||
Schedule.every(1.second).do(dependOn: self) {
|
||||
|
@ -152,11 +170,11 @@ Schedule.every(1.second).do(dependOn: self) {
|
|||
|
||||
## Contribution
|
||||
|
||||
Feel free to criticize!
|
||||
Feel free to criticize. 🍺
|
||||
|
||||
## Installation
|
||||
|
||||
Schedul supports all popular dependency managers.
|
||||
Schedule supports all popular dependency managers.
|
||||
|
||||
### Cocoapods
|
||||
|
||||
|
@ -174,6 +192,6 @@ github "jianstm/Schedule"
|
|||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/jianstm/Schedule", .exact("0.0.4"))
|
||||
.package(url: "https://github.com/jianstm/Schedule", .upToNextMinor("0.0.0"))
|
||||
]
|
||||
```
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
6624105421075FDC00013B00 /* ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6624105221075F8A00013B00 /* ExtensionsTests.swift */; };
|
||||
66241056210761F000013B00 /* BucketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66241055210761F000013B00 /* BucketTests.swift */; };
|
||||
662410592107804400013B00 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662410582107804400013B00 /* Lock.swift */; };
|
||||
666AEAF32109F90C00A50F27 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 666AEAF22109F90C00A50F27 /* Atomic.swift */; };
|
||||
669D215020FE1B7300AFFDF7 /* Interval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669D214F20FE1B7300AFFDF7 /* Interval.swift */; };
|
||||
669D215220FE1B8A00AFFDF7 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669D215120FE1B8A00AFFDF7 /* Time.swift */; };
|
||||
669D215420FE1BA600AFFDF7 /* Period.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669D215320FE1BA600AFFDF7 /* Period.swift */; };
|
||||
|
@ -41,8 +42,8 @@
|
|||
OBJ_29 /* Schedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* Schedule.swift */; };
|
||||
OBJ_37 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
|
||||
OBJ_48 /* DateTimeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* DateTimeTests.swift */; };
|
||||
OBJ_49 /* ScheduleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* ScheduleTests.swift */; };
|
||||
OBJ_50 /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* Util.swift */; };
|
||||
OBJ_49 /* SchedulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* SchedulesTests.swift */; };
|
||||
OBJ_50 /* Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* Misc.swift */; };
|
||||
OBJ_51 /* XCTestManifests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* XCTestManifests.swift */; };
|
||||
OBJ_53 /* Schedule.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "Schedule::Schedule::Product" /* Schedule.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
@ -66,6 +67,7 @@
|
|||
|
||||
/* Begin PBXFileReference section */
|
||||
62661F402108433300055501 /* WeakSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakSet.swift; sourceTree = "<group>"; };
|
||||
626C7209210968C6005F10A7 /* LinuxMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LinuxMain.swift; path = Tests/LinuxMain.swift; sourceTree = "<group>"; };
|
||||
62EF93F821085309001F7A47 /* WeakSetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakSetTests.swift; sourceTree = "<group>"; };
|
||||
62EF93FD21086411001F7A47 /* TaskCenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCenterTests.swift; sourceTree = "<group>"; };
|
||||
62EF940721086F63001F7A47 /* Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = "<group>"; };
|
||||
|
@ -75,6 +77,7 @@
|
|||
6624105221075F8A00013B00 /* ExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsTests.swift; sourceTree = "<group>"; };
|
||||
66241055210761F000013B00 /* BucketTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BucketTests.swift; sourceTree = "<group>"; };
|
||||
662410582107804400013B00 /* Lock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lock.swift; sourceTree = "<group>"; };
|
||||
666AEAF22109F90C00A50F27 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = "<group>"; };
|
||||
669D214F20FE1B7300AFFDF7 /* Interval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interval.swift; sourceTree = "<group>"; };
|
||||
669D215120FE1B8A00AFFDF7 /* Time.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = "<group>"; };
|
||||
669D215320FE1BA600AFFDF7 /* Period.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Period.swift; sourceTree = "<group>"; };
|
||||
|
@ -86,8 +89,8 @@
|
|||
OBJ_10 /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = "<group>"; };
|
||||
OBJ_11 /* Schedule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Schedule.swift; sourceTree = "<group>"; };
|
||||
OBJ_15 /* DateTimeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_16 /* ScheduleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_17 /* Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = "<group>"; };
|
||||
OBJ_16 /* SchedulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchedulesTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_17 /* Misc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Misc.swift; sourceTree = "<group>"; };
|
||||
OBJ_18 /* XCTestManifests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestManifests.swift; sourceTree = "<group>"; };
|
||||
OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
|
||||
"Schedule::Schedule::Product" /* Schedule.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Schedule.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -113,22 +116,14 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
62EF93FB21085312001F7A47 /* Utils */ = {
|
||||
62EF93FB21085312001F7A47 /* UtilsTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6624105221075F8A00013B00 /* ExtensionsTests.swift */,
|
||||
66241055210761F000013B00 /* BucketTests.swift */,
|
||||
62EF93F821085309001F7A47 /* WeakSetTests.swift */,
|
||||
);
|
||||
name = Utils;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
62EF93FC210863FA001F7A47 /* DateTime */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_15 /* DateTimeTests.swift */,
|
||||
);
|
||||
name = DateTime;
|
||||
name = UtilsTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
662410462104A0EC00013B00 /* Utils */ = {
|
||||
|
@ -138,6 +133,7 @@
|
|||
6624104D2104AF2100013B00 /* Extensions.swift */,
|
||||
662410582107804400013B00 /* Lock.swift */,
|
||||
62661F402108433300055501 /* WeakSet.swift */,
|
||||
666AEAF22109F90C00A50F27 /* Atomic.swift */,
|
||||
);
|
||||
name = Utils;
|
||||
sourceTree = "<group>";
|
||||
|
@ -166,6 +162,7 @@
|
|||
OBJ_13 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
626C7209210968C6005F10A7 /* LinuxMain.swift */,
|
||||
OBJ_14 /* ScheduleTests */,
|
||||
);
|
||||
name = Tests;
|
||||
|
@ -174,12 +171,12 @@
|
|||
OBJ_14 /* ScheduleTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
62EF93FC210863FA001F7A47 /* DateTime */,
|
||||
62EF93FB21085312001F7A47 /* Utils */,
|
||||
OBJ_16 /* ScheduleTests.swift */,
|
||||
OBJ_17 /* Util.swift */,
|
||||
OBJ_18 /* XCTestManifests.swift */,
|
||||
OBJ_15 /* DateTimeTests.swift */,
|
||||
OBJ_17 /* Misc.swift */,
|
||||
OBJ_16 /* SchedulesTests.swift */,
|
||||
62EF93FD21086411001F7A47 /* TaskCenterTests.swift */,
|
||||
OBJ_18 /* XCTestManifests.swift */,
|
||||
62EF93FB21085312001F7A47 /* UtilsTests */,
|
||||
);
|
||||
name = ScheduleTests;
|
||||
path = Tests/ScheduleTests;
|
||||
|
@ -315,6 +312,7 @@
|
|||
669D215020FE1B7300AFFDF7 /* Interval.swift in Sources */,
|
||||
669D215620FE1BB400AFFDF7 /* Weekday.swift in Sources */,
|
||||
62EF940821086F63001F7A47 /* Timeline.swift in Sources */,
|
||||
666AEAF32109F90C00A50F27 /* Atomic.swift in Sources */,
|
||||
OBJ_28 /* Task.swift in Sources */,
|
||||
OBJ_29 /* Schedule.swift in Sources */,
|
||||
62661F412108433400055501 /* WeakSet.swift in Sources */,
|
||||
|
@ -343,8 +341,8 @@
|
|||
6624105421075FDC00013B00 /* ExtensionsTests.swift in Sources */,
|
||||
62EF93FA2108530D001F7A47 /* WeakSetTests.swift in Sources */,
|
||||
OBJ_48 /* DateTimeTests.swift in Sources */,
|
||||
OBJ_49 /* ScheduleTests.swift in Sources */,
|
||||
OBJ_50 /* Util.swift in Sources */,
|
||||
OBJ_49 /* SchedulesTests.swift in Sources */,
|
||||
OBJ_50 /* Misc.swift in Sources */,
|
||||
OBJ_51 /* XCTestManifests.swift in Sources */,
|
||||
66241056210761F000013B00 /* BucketTests.swift in Sources */,
|
||||
62EF93FF21086420001F7A47 /* TaskCenterTests.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// Atomic.swift
|
||||
// Schedule
|
||||
//
|
||||
// Created by Quentin Jin on 2018/7/26.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Atomic<T> {
|
||||
|
||||
private var value: T
|
||||
private var lock = Lock()
|
||||
|
||||
init(_ value: T) {
|
||||
self.value = value
|
||||
}
|
||||
|
||||
func execute(_ body: (T) -> Void) {
|
||||
lock.withLock { body(value) }
|
||||
}
|
||||
|
||||
func mutate(_ body: (inout T) -> Void) {
|
||||
lock.withLock { body(&value) }
|
||||
}
|
||||
|
||||
func read() -> T {
|
||||
return lock.withLock { value }
|
||||
}
|
||||
|
||||
func write(_ new: T) {
|
||||
lock.withLock { value = new }
|
||||
}
|
||||
}
|
|
@ -8,21 +8,14 @@
|
|||
import Foundation
|
||||
|
||||
/// `Interval` represents a length of time.
|
||||
///
|
||||
/// The value range of interval is [Int.min.nanoseconds...Int.max.nanoseconds],
|
||||
/// that is, about -292.years ~ 292.years, enough for us!
|
||||
public struct Interval {
|
||||
|
||||
let ns: Int
|
||||
|
||||
/// The length of this interval in nanoseconds.
|
||||
public var nanoseconds: Double {
|
||||
return Double(ns)
|
||||
}
|
||||
public var nanoseconds: Double
|
||||
|
||||
/// Creates an interval from the given number of nanoseconds.
|
||||
public init(nanoseconds: Double) {
|
||||
self.ns = nanoseconds.clampedToInt()
|
||||
self.nanoseconds = nanoseconds
|
||||
}
|
||||
|
||||
/// A boolean value indicating whether this interval is negative.
|
||||
|
@ -37,13 +30,13 @@ public struct Interval {
|
|||
/// but the interval comparing `1.hour` to `3.hour` is `-2.hour`.
|
||||
/// In this case, `-2.hour` means **two hours shorter**
|
||||
public var isNegative: Bool {
|
||||
return ns < 0
|
||||
return nanoseconds.isLess(than: 0)
|
||||
}
|
||||
|
||||
/// The absolute value of the length of this interval,
|
||||
/// measured in nanoseconds, but disregarding its sign.
|
||||
public var magnitude: UInt {
|
||||
return ns.magnitude
|
||||
public var magnitude: Double {
|
||||
return nanoseconds.magnitude
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,25 +65,25 @@ extension Interval {
|
|||
return intervals.sorted(by: { $0.magnitude < $1.magnitude })[0]
|
||||
}
|
||||
|
||||
/// Returns a new interval by multipling the left interval by the right number.
|
||||
/// Returns a new interval by multipling this interval by a double number.
|
||||
///
|
||||
/// 1.hour * 2 == 2.hours
|
||||
public static func *(lhs: Interval, rhs: Double) -> Interval {
|
||||
return Interval(nanoseconds: lhs.nanoseconds * rhs)
|
||||
public func multiplying(by number: Double) -> Interval {
|
||||
return Interval(nanoseconds: nanoseconds * number)
|
||||
}
|
||||
|
||||
/// Returns a new interval by adding the right interval to the left interval.
|
||||
/// Returns a new interval by adding an interval to this interval.
|
||||
///
|
||||
/// 1.hour + 1.hour == 2.hours
|
||||
public static func +(lhs: Interval, rhs: Interval) -> Interval {
|
||||
return Interval(nanoseconds: lhs.nanoseconds + rhs.nanoseconds)
|
||||
public func adding(_ other: Interval) -> Interval {
|
||||
return Interval(nanoseconds: nanoseconds + other.nanoseconds)
|
||||
}
|
||||
|
||||
/// Returns a new instarval by subtracting the right interval from the left interval.
|
||||
/// Returns a new interval by subtracting an interval from this interval.
|
||||
///
|
||||
/// 2.hours - 1.hour == 1.hour
|
||||
public static func -(lhs: Interval, rhs: Interval) -> Interval {
|
||||
return Interval(nanoseconds: lhs.nanoseconds - rhs.nanoseconds)
|
||||
public func subtracting(_ other: Interval) -> Interval {
|
||||
return Interval(nanoseconds: nanoseconds - other.nanoseconds)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,6 +120,31 @@ extension Interval {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
extension Interval {
|
||||
|
||||
/// Returns a new interval by multipling the left interval by the right number.
|
||||
///
|
||||
/// 1.hour * 2 == 2.hours
|
||||
public static func *(lhs: Interval, rhs: Double) -> Interval {
|
||||
return lhs.multiplying(by: rhs)
|
||||
}
|
||||
|
||||
/// Returns a new interval by adding the right interval to the left interval.
|
||||
///
|
||||
/// 1.hour + 1.hour == 2.hours
|
||||
public static func +(lhs: Interval, rhs: Interval) -> Interval {
|
||||
return lhs.adding(rhs)
|
||||
}
|
||||
|
||||
/// Returns a new interval by subtracting the right interval from the left interval.
|
||||
///
|
||||
/// 2.hours - 1.hour == 1.hour
|
||||
public static func -(lhs: Interval, rhs: Interval) -> Interval {
|
||||
return lhs.subtracting(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
extension Interval: Hashable {
|
||||
|
||||
/// The hashValue of this interval.
|
||||
|
@ -140,6 +158,7 @@ extension Interval: Hashable {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
extension Date {
|
||||
|
||||
/// The interval between this date and the current date and time.
|
||||
|
@ -157,21 +176,17 @@ extension Date {
|
|||
}
|
||||
|
||||
/// Returns a new date by adding an interval to this date.
|
||||
public func addingInterval(_ interval: Interval) -> Date {
|
||||
public func adding(_ interval: Interval) -> Date {
|
||||
return addingTimeInterval(interval.seconds)
|
||||
}
|
||||
|
||||
/// Returns a date with an interval added to it.
|
||||
public static func +(lhs: Date, rhs: Interval) -> Date {
|
||||
return lhs.addingInterval(rhs)
|
||||
}
|
||||
|
||||
/// Adds a interval to the date.
|
||||
public static func +=(lhs: inout Date, rhs: Interval) {
|
||||
lhs = lhs + rhs
|
||||
return lhs.adding(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension DispatchSourceTimer {
|
||||
|
||||
func schedule(after interval: Interval) {
|
||||
|
@ -179,16 +194,20 @@ extension DispatchSourceTimer {
|
|||
schedule(wallDeadline: .distantFuture)
|
||||
return
|
||||
}
|
||||
schedule(wallDeadline: .now() + DispatchTimeInterval.nanoseconds(interval.ns))
|
||||
|
||||
let ns = interval.nanoseconds.clampedToInt()
|
||||
schedule(wallDeadline: .now() + DispatchTimeInterval.nanoseconds(ns))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// `IntervalConvertible` provides a set of intuitive api for creating interval.
|
||||
public protocol IntervalConvertible {
|
||||
|
||||
var nanoseconds: Interval { get }
|
||||
}
|
||||
|
||||
|
||||
extension Int: IntervalConvertible {
|
||||
|
||||
public var nanoseconds: Interval {
|
||||
|
@ -196,6 +215,7 @@ extension Int: IntervalConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
extension Double: IntervalConvertible {
|
||||
|
||||
public var nanoseconds: Interval {
|
||||
|
@ -203,6 +223,7 @@ extension Double: IntervalConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
extension IntervalConvertible {
|
||||
|
||||
public var nanosecond: Interval {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// `MonthDay` represents a day in month, without a time.
|
||||
/// `Monthday` represents a day in month, without a time.
|
||||
public enum MonthDay {
|
||||
|
||||
case january(Int)
|
||||
|
|
|
@ -11,21 +11,23 @@ extension Schedule {
|
|||
|
||||
/// Schedules a task with this schedule.
|
||||
///
|
||||
/// This method will receive a `host` object as parameter,
|
||||
/// This method will receive a `host` object as a parameter,
|
||||
/// the returned task will not retain this object, on the contrary,
|
||||
/// it will observe this object: when this object is dealloced,
|
||||
/// task will not be scheduled any more, something parasitism.
|
||||
/// it will observe this object, when this object is dealloced,
|
||||
/// task will not be scheduled any more, something like parasitism.
|
||||
///
|
||||
/// This feature is very useful when you want a timer live and die
|
||||
/// with a controller.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - queue: The dispatch queue to which the task will be submitted.
|
||||
/// - tag: The tag to associate with the task.
|
||||
/// - host: The object to host on.
|
||||
/// - onElapse: The action to do when time is out.
|
||||
/// - Returns: The task just created.
|
||||
@discardableResult
|
||||
public func `do`(queue: DispatchQueue? = nil,
|
||||
tag: String? = nil,
|
||||
host: AnyObject,
|
||||
onElapse: @escaping (Task) -> Void) -> Task {
|
||||
return ParasiticTask(schedule: self, queue: queue, host: host, onElapse: onElapse)
|
||||
|
@ -33,9 +35,9 @@ extension Schedule {
|
|||
|
||||
/// Schedules a task with this schedule.
|
||||
///
|
||||
/// This method will receive a `host` object as parameter,
|
||||
/// This method will receive a `host` object as a parameter,
|
||||
/// the returned task will not retain this object, on the contrary,
|
||||
/// it will observe this object: when this object is dealloced,
|
||||
/// it will observe this object, when this object is dealloced,
|
||||
/// task will not scheduled any more, something like parasitism.
|
||||
///
|
||||
/// This feature is very useful when you want a timer live and die
|
||||
|
@ -43,11 +45,13 @@ extension Schedule {
|
|||
///
|
||||
/// - Parameters:
|
||||
/// - queue: The dispatch queue to which the task will be submitted.
|
||||
/// - tag: The tag to associate with the task.
|
||||
/// - host: The object to host on.
|
||||
/// - onElapse: The action to do when time is out.
|
||||
/// - Returns: The task just created.
|
||||
@discardableResult
|
||||
public func `do`(queue: DispatchQueue? = nil,
|
||||
tag: String? = nil,
|
||||
host: AnyObject,
|
||||
onElapse: @escaping () -> Void) -> Task {
|
||||
return self.do(queue: queue, host: host, onElapse: { (_) in onElapse() })
|
||||
|
|
|
@ -24,19 +24,19 @@ import Foundation
|
|||
/// in these two cases are quite different.
|
||||
public struct Period {
|
||||
|
||||
public let years: Int
|
||||
public private(set) var years: Int
|
||||
|
||||
public let months: Int
|
||||
public private(set) var months: Int
|
||||
|
||||
public let days: Int
|
||||
public private(set) var days: Int
|
||||
|
||||
public let hours: Int
|
||||
public private(set) var hours: Int
|
||||
|
||||
public let minutes: Int
|
||||
public private(set) var minutes: Int
|
||||
|
||||
public let seconds: Int
|
||||
public private(set) var seconds: Int
|
||||
|
||||
public let nanoseconds: Int
|
||||
public private(set) var nanoseconds: Int
|
||||
|
||||
public init(years: Int = 0, months: Int = 0, days: Int = 0,
|
||||
hours: Int = 0, minutes: Int = 0, seconds: Int = 0,
|
||||
|
@ -50,27 +50,121 @@ public struct Period {
|
|||
self.nanoseconds = nanoseconds
|
||||
}
|
||||
|
||||
/// Returns a new date by adding the right period to the left period.
|
||||
private static var map: Atomic<[String: Int]> = Atomic([
|
||||
"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6,
|
||||
"seven": 7, "eight": 8, "nine": 9, "ten": 10, "eleven": 11, "twelve": 12
|
||||
])
|
||||
|
||||
public static func registerQuantifier(_ word: String, for number: Int) {
|
||||
map.mutate {
|
||||
$0[word] = number
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a period from a natural expression.
|
||||
///
|
||||
/// Period("one second") => Period(seconds: 1)
|
||||
/// Period("two hours and ten minutes") => Period(hours: 2, minutes: 10)
|
||||
/// Period("1 year, 2 months and 3 days") => Period(years: 1, months: 2, days: 3)
|
||||
public init?(_ string: String) {
|
||||
var period = string
|
||||
for (word, number) in Period.map.read() {
|
||||
period = period.replacingOccurrences(of: word, with: "\(number)")
|
||||
}
|
||||
guard let regex = try? NSRegularExpression(pattern: "( and |, )") else {
|
||||
return nil
|
||||
}
|
||||
period = regex.stringByReplacingMatches(in: period, range: NSRange(location: 0, length: period.count), withTemplate: "$")
|
||||
|
||||
var result = 0.year
|
||||
for pair in period.split(separator: "$").map({ $0.split(separator: " ") }) {
|
||||
guard pair.count == 2 else { return nil }
|
||||
guard let number = Int(pair[0]) else { return nil }
|
||||
var word = String(pair[1])
|
||||
if word.last == "s" { word.removeLast() }
|
||||
switch word {
|
||||
case "year": result = result + number.years
|
||||
case "month": result = result + number.months
|
||||
case "day": result = result + number.days
|
||||
case "week": result = result + (number * 7).days
|
||||
case "hour": result = result + number.hours
|
||||
case "minute": result = result + number.minutes
|
||||
case "second": result = result + number.second
|
||||
case "nanosecond": result = result + number.nanosecond
|
||||
default: break
|
||||
}
|
||||
}
|
||||
self = result
|
||||
}
|
||||
|
||||
/// Returns a new period by adding a period to this period.
|
||||
public func adding(_ other: Period) -> Period {
|
||||
return Period(years: years.clampedAdding(other.years),
|
||||
months: months.clampedAdding(other.months),
|
||||
days: days.clampedAdding(other.days),
|
||||
hours: hours.clampedAdding(other.hours),
|
||||
minutes: minutes.clampedAdding(other.minutes),
|
||||
seconds: seconds.clampedAdding(other.seconds),
|
||||
nanoseconds: nanoseconds.clampedAdding(other.nanoseconds))
|
||||
}
|
||||
|
||||
/// Returns a new period by adding an interval to this period.
|
||||
///
|
||||
/// You can tidy the new period by specify the unit parameter.
|
||||
///
|
||||
/// 1.month.adding(25.hour, tiyding: .day) => Period(months: 1, days: 1, hours: 1)
|
||||
public func adding(_ interval: Interval, tidying unit: Unit = .day) -> Period {
|
||||
var period = Period(years: years, months: months, days: days,
|
||||
hours: hours, minutes: minutes, seconds: seconds,
|
||||
nanoseconds: nanoseconds.clampedAdding(interval.nanoseconds.clampedToInt()))
|
||||
period = period.tidied(to: unit)
|
||||
return period
|
||||
}
|
||||
|
||||
/// Adds two periods and produces their sum.
|
||||
public static func +(lhs: Period, rhs: Period) -> Period {
|
||||
return Period(years: lhs.years.clampedAdding(rhs.years),
|
||||
months: lhs.months.clampedAdding(rhs.months),
|
||||
days: lhs.days.clampedAdding(rhs.days),
|
||||
hours: lhs.hours.clampedAdding(rhs.hours),
|
||||
minutes: lhs.minutes.clampedAdding(rhs.minutes),
|
||||
seconds: lhs.seconds.clampedAdding(rhs.seconds),
|
||||
nanoseconds: lhs.nanoseconds.clampedAdding(rhs.nanoseconds))
|
||||
return lhs.adding(rhs)
|
||||
}
|
||||
|
||||
/// Returns a date with a period added to it.
|
||||
public static func +(lhs: Date, rhs: Period) -> Date {
|
||||
return Calendar.autoupdatingCurrent.date(byAdding: rhs.asDateComponents(), to: lhs) ?? .distantFuture
|
||||
}
|
||||
|
||||
/// Return a period with a interval added to it.
|
||||
/// Returns a period with an interval added to it.
|
||||
public static func +(lhs: Period, rhs: Interval) -> Period {
|
||||
return Period(years: lhs.years, months: lhs.months, days: lhs.days,
|
||||
hours: lhs.hours, minutes: lhs.minutes, seconds: lhs.seconds,
|
||||
nanoseconds: lhs.nanoseconds.clampedAdding(rhs.ns))
|
||||
return lhs.adding(rhs)
|
||||
}
|
||||
|
||||
/// Type used to as tidy's parameter.
|
||||
public enum Unit {
|
||||
case day, hour, minute, second, nanosecond
|
||||
}
|
||||
|
||||
/// Returns the tidied period.
|
||||
///
|
||||
/// Period(hours: 25).tidied(to .day) => Period(days: 1, hours: 1)
|
||||
public func tidied(to unit: Unit) -> Period {
|
||||
var period = self
|
||||
if case .nanosecond = unit { return period }
|
||||
if period.nanoseconds.magnitude >= UInt(NSEC_PER_SEC) {
|
||||
period.seconds += period.nanoseconds / Int(NSEC_PER_SEC)
|
||||
period.nanoseconds %= Int(NSEC_PER_SEC)
|
||||
}
|
||||
|
||||
if case .second = unit { return period }
|
||||
if period.seconds.magnitude >= 60 {
|
||||
period.minutes += period.seconds / 60
|
||||
period.seconds %= 60
|
||||
}
|
||||
|
||||
if case .minute = unit { return period }
|
||||
if period.minutes.magnitude >= 60 {
|
||||
period.hours += period.minutes / 60
|
||||
period.minutes %= 60
|
||||
}
|
||||
|
||||
if case .hour = unit { return period }
|
||||
if period.hours.magnitude >= 24 {
|
||||
period.days += period.hours / 24
|
||||
period.hours %= 24
|
||||
}
|
||||
return period
|
||||
}
|
||||
|
||||
func asDateComponents() -> DateComponents {
|
||||
|
@ -80,6 +174,19 @@ public struct Period {
|
|||
}
|
||||
}
|
||||
|
||||
extension Date {
|
||||
|
||||
/// Returns a new date by adding a period to this date.
|
||||
public func adding(_ period: Period) -> Date {
|
||||
return Calendar(identifier: .gregorian).date(byAdding: period.asDateComponents(), to: self) ?? .distantFuture
|
||||
}
|
||||
|
||||
/// Returns a date with a period added to it.
|
||||
public static func +(lhs: Date, rhs: Period) -> Date {
|
||||
return lhs.adding(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
extension Int {
|
||||
|
||||
public var years: Period {
|
||||
|
|
|
@ -8,12 +8,9 @@
|
|||
import Foundation
|
||||
|
||||
/// `Schedule` represents a plan that gives the times
|
||||
/// at which a task should be invoked.
|
||||
/// at which a task should be executed.
|
||||
///
|
||||
/// `Schedule` is interval based.
|
||||
/// When a new task is created in the `do` method, it will ask for the first
|
||||
/// interval in this schedule, then set up a timer to invoke itself
|
||||
/// after the interval.
|
||||
public struct Schedule {
|
||||
|
||||
private var sequence: AnySequence<Interval>
|
||||
|
@ -29,7 +26,7 @@ public struct Schedule {
|
|||
///
|
||||
/// - Parameters:
|
||||
/// - queue: The dispatch queue to which the task will be submitted.
|
||||
/// - tag: The tag to attach to the task.
|
||||
/// - tag: The tag to associate with the task.
|
||||
/// - onElapse: The task to invoke when time is out.
|
||||
/// - Returns: The task just created.
|
||||
@discardableResult
|
||||
|
@ -43,7 +40,7 @@ public struct Schedule {
|
|||
///
|
||||
/// - Parameters:
|
||||
/// - queue: The dispatch queue to which the task will be submitted.
|
||||
/// - tag: The tag to attach to the queue
|
||||
/// - tag: The tag to associate with the queue
|
||||
/// - onElapse: The task to invoke when time is out.
|
||||
/// - Returns: The task just created.
|
||||
@discardableResult
|
||||
|
@ -58,7 +55,7 @@ extension Schedule {
|
|||
|
||||
/// Creates a schedule from a `makeUnderlyingIterator()` method.
|
||||
///
|
||||
/// The task will be invoke after each interval
|
||||
/// The task will be executed after each interval
|
||||
/// produced by the iterator that `makeUnderlyingIterator` returns.
|
||||
///
|
||||
/// For example:
|
||||
|
@ -84,13 +81,13 @@ extension Schedule {
|
|||
}
|
||||
|
||||
/// Creates a schedule from an interval sequence.
|
||||
/// The task will be invoke after each interval in the 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 invoke after each interval in the array.
|
||||
/// The task will be executed after each interval in the array.
|
||||
public static func of(_ intervals: Interval...) -> Schedule {
|
||||
return Schedule(intervals)
|
||||
}
|
||||
|
@ -100,7 +97,7 @@ extension Schedule {
|
|||
|
||||
/// Creates a schedule from a `makeUnderlyingIterator()` method.
|
||||
///
|
||||
/// The task will be invoke at each date
|
||||
/// The task will be executed at each date
|
||||
/// produced by the iterator that `makeUnderlyingIterator` returns.
|
||||
///
|
||||
/// For example:
|
||||
|
@ -116,33 +113,33 @@ extension Schedule {
|
|||
/// }
|
||||
///
|
||||
/// > "now: 2001-01-01 00:00:00"
|
||||
/// > "task: 2001-01-01 00:00:03
|
||||
/// > "task: 2001-01-01 00:00:03"
|
||||
/// ...
|
||||
///
|
||||
/// You should not return `Date()` in making interator
|
||||
/// if you want to invoke a task immediately,
|
||||
/// 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 previous: Date!
|
||||
var last: Date!
|
||||
return AnyIterator {
|
||||
previous = previous ?? Date()
|
||||
last = last ?? Date()
|
||||
guard let next = iterator.next() else { return nil }
|
||||
defer { previous = next }
|
||||
return next.interval(since: previous)
|
||||
defer { last = next }
|
||||
return next.interval(since: last)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a schedule from a date sequence.
|
||||
/// The task will be invoke at each date in the 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 invoke at each date in the array.
|
||||
/// The task will be executed at each date in the array.
|
||||
public static func of(_ dates: Date...) -> Schedule {
|
||||
return Schedule.from(dates)
|
||||
}
|
||||
|
@ -151,12 +148,12 @@ extension Schedule {
|
|||
public var dates: AnySequence<Date> {
|
||||
return AnySequence { () -> AnyIterator<Date> in
|
||||
let iterator = self.makeIterator()
|
||||
var previous: Date!
|
||||
var last: Date!
|
||||
return AnyIterator {
|
||||
previous = previous ?? Date()
|
||||
last = last ?? Date()
|
||||
guard let interval = iterator.next() else { return nil }
|
||||
previous = previous + interval
|
||||
return previous
|
||||
last = last + interval
|
||||
return last
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +171,7 @@ extension Schedule {
|
|||
return Schedule.of(Date.distantFuture)
|
||||
}
|
||||
|
||||
/// A schedule with no date.
|
||||
/// A schedule that is never going to happen.
|
||||
public static var never: Schedule {
|
||||
return Schedule.make {
|
||||
AnyIterator<Date> { nil }
|
||||
|
@ -191,6 +188,7 @@ extension Schedule {
|
|||
/// 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 {
|
||||
|
@ -212,14 +210,14 @@ extension Schedule {
|
|||
/// let s1 = Schedule.of(2.seconds, 4.seconds, 6.seconds)
|
||||
/// let s2 = s0.concat(s1)
|
||||
/// > s2
|
||||
/// > 1.second, 1.second, 1.second, 1.second, 1.second, 1.second
|
||||
/// > 1.second, 1.seconds, 2.seconds, 2.seconds, 3.seconds, 3.seconds
|
||||
public func merge(_ schedule: Schedule) -> Schedule {
|
||||
return Schedule.make { () -> AnyIterator<Interval> in
|
||||
return Schedule.make { () -> AnyIterator<Date> in
|
||||
let i0 = self.dates.makeIterator()
|
||||
let i1 = schedule.dates.makeIterator()
|
||||
var buffer0: Date!
|
||||
var buffer1: Date!
|
||||
let iterator = AnyIterator<Date> {
|
||||
return AnyIterator<Date> {
|
||||
if buffer0 == nil { buffer0 = i0.next() }
|
||||
if buffer1 == nil { buffer1 = i1.next() }
|
||||
|
||||
|
@ -234,149 +232,149 @@ extension Schedule {
|
|||
if d == buffer1 { buffer1 = nil }
|
||||
return d
|
||||
}
|
||||
return Schedule.make({ iterator }).makeIterator()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new schedule by cutting out a specific number of this schedule.
|
||||
/// 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.count(3)
|
||||
/// let s1 = s0.first(3)
|
||||
/// > s1
|
||||
/// 1.second, 1.second, 1.second
|
||||
public func count(_ count: Int) -> Schedule {
|
||||
public func first(_ count: Int) -> Schedule {
|
||||
return Schedule.make { () -> AnyIterator<Interval> in
|
||||
let iterator = self.makeIterator()
|
||||
var tick = 0
|
||||
var num = 0
|
||||
return AnyIterator {
|
||||
guard tick < count, let interval = iterator.next() else { return nil }
|
||||
tick += 1
|
||||
guard num < count, let interval = iterator.next() else { return nil }
|
||||
num += 1
|
||||
return interval
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new schedule by cutting out the part which is before the date.
|
||||
/// Returns a new schedule by only taking the part before the date.
|
||||
public func until(_ date: Date) -> Schedule {
|
||||
return Schedule.make { () -> AnyIterator<Interval> in
|
||||
let iterator = self.makeIterator()
|
||||
var previous: Date!
|
||||
return Schedule.make { () -> AnyIterator<Date> in
|
||||
let iterator = self.dates.makeIterator()
|
||||
return AnyIterator {
|
||||
previous = previous ?? Date()
|
||||
guard let interval = iterator.next(),
|
||||
previous.addingTimeInterval(interval.seconds) < date else {
|
||||
guard let next = iterator.next(), next < date else {
|
||||
return nil
|
||||
}
|
||||
previous.addTimeInterval(interval.seconds)
|
||||
return interval
|
||||
return next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the task immediately.
|
||||
/// Creates a schedule that executes the task immediately.
|
||||
public static var now: Schedule {
|
||||
return Schedule.of(0.nanosecond)
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the task after the delay.
|
||||
/// Creates a schedule that executes the task after delay.
|
||||
public static func after(_ delay: Interval) -> Schedule {
|
||||
return Schedule.of(delay)
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the task every interval.
|
||||
/// 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 invokes the task at the specific date.
|
||||
/// 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 invokes the task after the delay then repeat
|
||||
/// 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 invokes the task every period.
|
||||
/// 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.autoupdatingCurrent
|
||||
var previous: Date!
|
||||
let calendar = Calendar(identifier: .gregorian)
|
||||
var last: Date!
|
||||
return AnyIterator {
|
||||
previous = previous ?? Date()
|
||||
last = last ?? Date()
|
||||
guard let next = calendar.date(byAdding: period.asDateComponents(),
|
||||
to: previous) else {
|
||||
to: last) else {
|
||||
return nil
|
||||
}
|
||||
defer { previous = next }
|
||||
return next.interval(since: previous)
|
||||
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 {
|
||||
|
||||
/// `EveryDateMiddleware` represents a middleware that wraps a schedule
|
||||
/// which only specify date without time.
|
||||
/// `DateMiddleware` represents a middleware that wraps a schedule
|
||||
/// which was only specified date without time.
|
||||
///
|
||||
/// You should call `at` method to get the time specified schedule.
|
||||
public struct EveryDateMiddleware {
|
||||
/// 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 timing.
|
||||
public func at(_ timing: Time) -> Schedule {
|
||||
|
||||
return Schedule.make { () -> AnyIterator<Interval> in
|
||||
/// Returns a schedule at the specific time.
|
||||
public func at(_ time: Time) -> Schedule {
|
||||
return Schedule.make { () -> AnyIterator<Date> in
|
||||
let iterator = self.schedule.dates.makeIterator()
|
||||
let calendar = Calendar.autoupdatingCurrent
|
||||
var previous: Date!
|
||||
var calendar = Calendar(identifier: .gregorian)
|
||||
var last: Date!
|
||||
return AnyIterator {
|
||||
previous = previous ?? Date()
|
||||
last = last ?? Date()
|
||||
guard let date = iterator.next(),
|
||||
let next = calendar.nextDate(after: date,
|
||||
matching: timing.asDateComponents(),
|
||||
matching: time.asDateComponents(),
|
||||
matchingPolicy: .strict) else {
|
||||
return nil
|
||||
}
|
||||
defer { previous = next }
|
||||
return next.interval(since: previous)
|
||||
defer { last = next }
|
||||
return next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a schedule at the specific timing.
|
||||
/// Returns a schedule at the specific time.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let s = Schedule.every(.monday).at("11:11")
|
||||
///
|
||||
/// Available format:
|
||||
///
|
||||
/// Time("11") == Time(hour: 11)
|
||||
/// Time("11:12") == Time(hour: 11, minute: 12)
|
||||
/// Time("11:12:13") == Time(hour: 11, minute: 12, second: 13)
|
||||
/// Time("11:12:13.123") == Time(hour: 11, minute: 12, second: 13, nanosecond: 123)
|
||||
public func at(_ timing: String) -> Schedule {
|
||||
guard let time = Time(timing: timing) else {
|
||||
/// 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 timing.
|
||||
public func at(_ timing: Int...) -> Schedule {
|
||||
let hour = timing[0]
|
||||
let minute = timing.count > 1 ? timing[1] : 0
|
||||
let second = timing.count > 2 ? timing[2] : 0
|
||||
let nanosecond = timing.count > 3 ? timing[3]: 0
|
||||
/// 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
|
||||
|
@ -385,13 +383,13 @@ extension Schedule {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the task every specific weekday.
|
||||
public static func every(_ weekday: Weekday) -> EveryDateMiddleware {
|
||||
let schedule = Schedule.make { () -> AnyIterator<Interval> in
|
||||
let calendar = Calendar.autoupdatingCurrent
|
||||
/// 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(identifier: .gregorian)
|
||||
let components = DateComponents(weekday: weekday.rawValue)
|
||||
var date: Date!
|
||||
let iterator = AnyIterator<Date> {
|
||||
return AnyIterator<Date> {
|
||||
if date == nil {
|
||||
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
|
||||
} else {
|
||||
|
@ -399,30 +397,29 @@ extension Schedule {
|
|||
}
|
||||
return date
|
||||
}
|
||||
return Schedule.make({ iterator }).makeIterator()
|
||||
}
|
||||
return EveryDateMiddleware(schedule: schedule)
|
||||
return DateMiddleware(schedule: schedule)
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the task every specific weekdays.
|
||||
public static func every(_ weekdays: Weekday...) -> EveryDateMiddleware {
|
||||
/// 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 EveryDateMiddleware(schedule: schedule)
|
||||
return DateMiddleware(schedule: schedule)
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the task every specific day in the month.
|
||||
public static func every(_ monthDay: MonthDay) -> EveryDateMiddleware {
|
||||
let schedule = Schedule.make { () -> AnyIterator<Interval> in
|
||||
let calendar = Calendar.autoupdatingCurrent
|
||||
/// 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(identifier: .gregorian)
|
||||
let components = monthDay.asDateComponents()
|
||||
|
||||
var date: Date!
|
||||
let iterator = AnyIterator<Date> {
|
||||
return AnyIterator<Date> {
|
||||
if date == nil {
|
||||
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
|
||||
} else {
|
||||
|
@ -430,19 +427,18 @@ extension Schedule {
|
|||
}
|
||||
return date
|
||||
}
|
||||
return Schedule.make({ iterator }).makeIterator()
|
||||
}
|
||||
return EveryDateMiddleware(schedule: schedule)
|
||||
return DateMiddleware(schedule: schedule)
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the task every specific days in the months.
|
||||
public static func every(_ mondays: MonthDay...) -> EveryDateMiddleware {
|
||||
/// 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 EveryDateMiddleware(schedule: schedule)
|
||||
return DateMiddleware(schedule: schedule)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ public class Task {
|
|||
|
||||
let now = Date()
|
||||
self._timeline.activate = now
|
||||
self._timeline.nextSchedule = now.addingInterval(interval)
|
||||
self._timeline.nextSchedule = now.adding(interval)
|
||||
TaskCenter.shared.add(self, withTag: tag)
|
||||
|
||||
self._timer?.resume()
|
||||
|
@ -75,7 +75,7 @@ public class Task {
|
|||
return
|
||||
}
|
||||
|
||||
_timeline.nextSchedule = _timeline.nextSchedule?.addingInterval(interval)
|
||||
_timeline.nextSchedule = _timeline.nextSchedule?.adding(interval)
|
||||
|
||||
_timer?.schedule(after: (_timeline.nextSchedule ?? Date.distantFuture).interval(since: now))
|
||||
let actions = _actions
|
||||
|
|
|
@ -26,8 +26,9 @@ public struct Time {
|
|||
///
|
||||
/// If any parameter is illegal, return nil.
|
||||
///
|
||||
/// Time(hour: 25) == nil
|
||||
/// Time(hour: 1, minute: 61) == nil
|
||||
/// Time(hour: 11, minute: 11) => "11:11:00.000"
|
||||
/// Time(hour: 25) => nil
|
||||
/// Time(hour: 1, minute: 61) => nil
|
||||
public init?(hour: Int, minute: Int = 0, second: Int = 0, nanosecond: Int = 0) {
|
||||
guard (0...23).contains(hour) else { return nil }
|
||||
guard (0...59).contains(minute) else { return nil }
|
||||
|
@ -40,7 +41,13 @@ public struct Time {
|
|||
self.nanosecond = nanosecond
|
||||
}
|
||||
|
||||
/// Creates a time with a timing string.
|
||||
private static let cache: NSCache<NSString, DateFormatter> = {
|
||||
let c = NSCache<NSString, DateFormatter>()
|
||||
c.countLimit = 5
|
||||
return c
|
||||
}()
|
||||
|
||||
/// Creates a time with a string.
|
||||
///
|
||||
/// If parameter is illegal, return nil.
|
||||
///
|
||||
|
@ -50,31 +57,57 @@ public struct Time {
|
|||
/// Time("11:12:13.123") == Time(hour: 11, minute: 12, second: 13, nanosecond: 123000000)
|
||||
///
|
||||
/// Time("-1.0") == nil
|
||||
public init?(timing: String) {
|
||||
let fields = timing.split(separator: ":")
|
||||
if fields.count > 3 { return nil }
|
||||
|
||||
var h = 0, m = 0, s = 0, ns = 0
|
||||
|
||||
guard let _h = Int(fields[0]) else { return nil }
|
||||
h = _h
|
||||
if fields.count > 1 {
|
||||
guard let _m = Int(fields[1]) else { return nil }
|
||||
m = _m
|
||||
}
|
||||
if fields.count > 2 {
|
||||
let values = fields[2].split(separator: ".")
|
||||
if values.count > 2 { return nil }
|
||||
guard let _s = Int(values[0]) else { return nil }
|
||||
s = _s
|
||||
|
||||
if values.count > 1 {
|
||||
guard let _ns = Int(values[1]) else { return nil }
|
||||
let digits = values[1].count
|
||||
ns = Int(Double(_ns) * pow(10, Double(9 - digits)))
|
||||
///
|
||||
/// Each of previous examples can have a period suffixes("am", "AM", "pm", "PM") separated by spaces.
|
||||
public init?(_ string: String) {
|
||||
var is12HourClock = false
|
||||
for word in ["am", "pm", "AM", "PM"] {
|
||||
if string.contains(word) {
|
||||
is12HourClock = true
|
||||
break
|
||||
}
|
||||
}
|
||||
self.init(hour: h, minute: m, second: s, nanosecond: ns)
|
||||
|
||||
let supportedFormats = [
|
||||
"HH", // 09
|
||||
"HH:mm", // 09:30
|
||||
"HH:mm:ss", // 09:30:26
|
||||
"HH:mm:ss.SSS", // 09:30:26.123
|
||||
]
|
||||
for format in supportedFormats {
|
||||
var fmt = format
|
||||
if is12HourClock {
|
||||
fmt = fmt.replacingOccurrences(of: "HH", with: "hh")
|
||||
fmt += " a"
|
||||
}
|
||||
var formatter: DateFormatter! = Time.cache.object(forKey: fmt as NSString)
|
||||
if formatter == nil {
|
||||
formatter = DateFormatter()
|
||||
formatter?.calendar = Calendar(identifier: .gregorian)
|
||||
formatter?.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter?.timeZone = TimeZone.autoupdatingCurrent
|
||||
formatter.dateFormat = fmt
|
||||
}
|
||||
if let date = formatter.date(from: string) {
|
||||
Time.cache.setObject(formatter, forKey: fmt as NSString)
|
||||
let calendar = Calendar(identifier: .gregorian)
|
||||
let components = calendar.dateComponents(in: TimeZone.autoupdatingCurrent, from: date)
|
||||
if let hour = components.hour,
|
||||
let minute = components.minute,
|
||||
let second = components.second,
|
||||
let nanosecond = components.nanosecond {
|
||||
self.init(hour: hour, minute: minute, second: second, nanosecond: nanosecond)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Interval since zero time.
|
||||
public var intervalSinceZeroTime: Interval {
|
||||
return Int(hour).hours + Int(minute).minutes + Int(second).seconds + Int(nanosecond).nanoseconds
|
||||
}
|
||||
|
||||
func asDateComponents() -> DateComponents {
|
||||
|
|
|
@ -26,6 +26,8 @@ extension WeakBox: Hashable {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// An alternative to `NSHashTable`, since it is unavailable on linux.
|
||||
struct WeakSet<T: AnyObject> {
|
||||
|
||||
private var set = Set<WeakBox<T>>()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import XCTest
|
||||
|
||||
import ScheduleTests
|
||||
|
||||
var tests = [XCTestCaseEntry]()
|
||||
tests += ScheduleTests.allTests()
|
||||
XCTMain(tests)
|
||||
XCTMain(tests)
|
||||
|
|
|
@ -10,45 +10,88 @@ import XCTest
|
|||
|
||||
final class DateTimeTests: XCTestCase {
|
||||
|
||||
func testIntervalConvertible() {
|
||||
func testInterval() {
|
||||
|
||||
XCTAssertTrue((-1).second.isNegative)
|
||||
XCTAssertEqual(1.1.second.magnitude, 1.1 * Constants.NSEC_PER_SEC)
|
||||
|
||||
XCTAssertTrue(1.1.second.isLonger(than: 1.0.second))
|
||||
XCTAssertTrue(3.days.isShorter(than: 1.week))
|
||||
XCTAssertEqual(Interval.longest(1.hour, 1.day, 1.week), 1.week)
|
||||
XCTAssertEqual(Interval.shortest(1.hour, 59.minutes, 3000.seconds), 3000.seconds)
|
||||
|
||||
XCTAssertEqual(1.second * 60, 1.minute)
|
||||
XCTAssertEqual(59.minutes + 60.seconds, 1.hour)
|
||||
XCTAssertEqual(1.week - 24.hours, 6.days)
|
||||
|
||||
XCTAssertEqual(1.nanoseconds, Interval(nanoseconds: 1))
|
||||
XCTAssertEqual(2.microseconds, Interval(nanoseconds: 2 * K.ns_per_us))
|
||||
XCTAssertEqual(3.milliseconds, Interval(nanoseconds: 3 * K.ns_per_ms))
|
||||
XCTAssertEqual(4.seconds, Interval(nanoseconds: 4 * K.ns_per_s))
|
||||
XCTAssertEqual(5.1.minutes, Interval(nanoseconds: 5.1 * K.ns_per_m))
|
||||
XCTAssertEqual(6.2.hours, Interval(nanoseconds: 6.2 * K.ns_per_h))
|
||||
XCTAssertEqual(7.3.days, Interval(nanoseconds: 7.3 * K.ns_per_d))
|
||||
XCTAssertEqual(8.4.weeks, Interval(nanoseconds: 8.4 * K.ns_per_w))
|
||||
XCTAssertEqual(2.microseconds, Interval(nanoseconds: 2 * Constants.NSEC_PER_USEC))
|
||||
XCTAssertEqual(3.milliseconds, Interval(nanoseconds: 3 * Constants.NSEC_PER_MSEC))
|
||||
XCTAssertEqual(4.seconds, Interval(nanoseconds: 4 * Constants.NSEC_PER_SEC))
|
||||
XCTAssertEqual(5.1.minutes, Interval(nanoseconds: 5.1 * Constants.NSEC_PER_M))
|
||||
XCTAssertEqual(6.2.hours, Interval(nanoseconds: 6.2 * Constants.NSEC_PER_H))
|
||||
XCTAssertEqual(7.3.days, Interval(nanoseconds: 7.3 * Constants.NSEC_PER_D))
|
||||
XCTAssertEqual(8.4.weeks, Interval(nanoseconds: 8.4 * Constants.NSEC_PER_W))
|
||||
}
|
||||
|
||||
func testTimeConstructor() {
|
||||
func testTime() {
|
||||
let t0 = Time(hour: -1, minute: -2, second: -3, nanosecond: -4)
|
||||
XCTAssertNil(t0)
|
||||
|
||||
let t1 = Time(timing: "11:12:13.456")
|
||||
let t1 = Time("11:12:13.456")
|
||||
XCTAssertNotNil(t1)
|
||||
XCTAssertEqual(t1?.hour, 11)
|
||||
XCTAssertEqual(t1?.minute, 12)
|
||||
XCTAssertEqual(t1?.second, 13)
|
||||
XCTAssertEqual(t1?.nanosecond, Int(0.456 * K.ns_per_s))
|
||||
if let i = t1?.nanosecond.nanoseconds {
|
||||
XCTAssertTrue(i.isAlmostEqual(to: (0.456 * Constants.NSEC_PER_SEC).nanoseconds, leeway: 0.001.seconds))
|
||||
}
|
||||
|
||||
let t2 = Time("11 pm")
|
||||
XCTAssertNotNil(t2)
|
||||
XCTAssertEqual(t2?.hour, 23)
|
||||
|
||||
let t3 = Time("12 am")
|
||||
XCTAssertNotNil(t3)
|
||||
XCTAssertEqual(t3?.hour, 0)
|
||||
}
|
||||
|
||||
func testPeriodAnd() {
|
||||
let dateComponents: Period = 1.year + 2.months + 3.days
|
||||
XCTAssertEqual(dateComponents.years, 1)
|
||||
XCTAssertEqual(dateComponents.months, 2)
|
||||
XCTAssertEqual(dateComponents.days, 3)
|
||||
func testPeriod() {
|
||||
let p0 = (1.year + 2.months + 3.days).tidied(to: .day)
|
||||
XCTAssertEqual(p0.years, 1)
|
||||
XCTAssertEqual(p0.months, 2)
|
||||
XCTAssertEqual(p0.days, 3)
|
||||
|
||||
let p1 = Period("one second")?.tidied(to: .second)
|
||||
XCTAssertNotNil(p1)
|
||||
XCTAssertEqual(p1!.seconds, 1)
|
||||
let p2 = Period("two hours and ten minutes")
|
||||
XCTAssertNotNil(p2)
|
||||
XCTAssertEqual(p2!.hours, 2)
|
||||
XCTAssertEqual(p2!.minutes, 10)
|
||||
let p3 = Period("1 year, 2 months and 3 days")
|
||||
XCTAssertNotNil(p3)
|
||||
XCTAssertEqual(p3!.years, 1)
|
||||
XCTAssertEqual(p3!.months, 2)
|
||||
XCTAssertEqual(p3!.days, 3)
|
||||
|
||||
let date = Date(year: 1989, month: 6, day: 4) + 1.year
|
||||
let year = Calendar(identifier: .gregorian).component(.year, from: date)
|
||||
XCTAssertEqual(year, 1990)
|
||||
|
||||
let p4 = Period(hours: 25).tidied(to: .day)
|
||||
XCTAssertEqual(p4.days, 1)
|
||||
}
|
||||
|
||||
func testMonthDay() {
|
||||
func testMonthday() {
|
||||
XCTAssertEqual(MonthDay.april(3).asDateComponents(), DateComponents(month: 4, day: 3))
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testInterval", testIntervalConvertible),
|
||||
("testTimeConstructor", testTimeConstructor),
|
||||
("testPeriodAnd", testPeriodAnd),
|
||||
("testMonthDay", testMonthDay)
|
||||
("testInterval", testInterval),
|
||||
("testTime", testTime),
|
||||
("testPeriod", testPeriod),
|
||||
("testMonthday", testMonthday)
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// Misc.swift
|
||||
// Schedule
|
||||
//
|
||||
// Created by Quentin Jin on 2018/7/2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import Schedule
|
||||
|
||||
extension Date {
|
||||
|
||||
var dateComponents: DateComponents {
|
||||
return Calendar(identifier: .gregorian).dateComponents(in: TimeZone.autoupdatingCurrent, from: self)
|
||||
}
|
||||
|
||||
var localizedDescription: String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.timeZone = TimeZone.autoupdatingCurrent
|
||||
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
|
||||
return formatter.string(from: self)
|
||||
}
|
||||
|
||||
init(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0, second: Int = 0, nanosecond: Int = 0) {
|
||||
let components = DateComponents(calendar: Calendar(identifier: .gregorian),
|
||||
timeZone: TimeZone.autoupdatingCurrent,
|
||||
year: year, month: month, day: day,
|
||||
hour: hour, minute: minute, second: second,
|
||||
nanosecond: nanosecond)
|
||||
self = components.date ?? Date.distantPast
|
||||
}
|
||||
}
|
||||
|
||||
extension Interval {
|
||||
|
||||
func isAlmostEqual(to interval: Interval, leeway: Interval) -> Bool {
|
||||
return (interval - self).magnitude <= leeway.magnitude
|
||||
}
|
||||
}
|
||||
|
||||
extension Double {
|
||||
|
||||
func isAlmostEqual(to double: Double, leeway: Double) -> Bool {
|
||||
return (double - self).magnitude <= leeway
|
||||
}
|
||||
}
|
||||
|
||||
extension Sequence where Element == Interval {
|
||||
|
||||
func isAlmostEqual<S>(to sequence: S, leeway: Interval) -> Bool where S: Sequence, S.Element == Element {
|
||||
var i0 = self.makeIterator()
|
||||
var i1 = sequence.makeIterator()
|
||||
while let l = i0.next(), let r = i1.next() {
|
||||
if (l - r).magnitude > leeway.magnitude {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return i0.next() == i1.next()
|
||||
}
|
||||
}
|
||||
|
||||
extension Schedule {
|
||||
|
||||
func isAlmostEqual(to schedule: Schedule, leeway: Interval) -> Bool {
|
||||
return makeIterator().isAlmostEqual(to: schedule.makeIterator(), leeway: leeway)
|
||||
}
|
||||
}
|
||||
|
||||
struct Constants {
|
||||
|
||||
static let NSEC_PER_USEC = Double(Foundation.NSEC_PER_USEC)
|
||||
static let NSEC_PER_MSEC = Double(Foundation.NSEC_PER_MSEC)
|
||||
static let NSEC_PER_SEC = Double(Foundation.NSEC_PER_SEC)
|
||||
static let NSEC_PER_M = Double(Foundation.NSEC_PER_SEC) * 60
|
||||
static let NSEC_PER_H = Double(Foundation.NSEC_PER_SEC) * 60 * 60
|
||||
static let NSEC_PER_D = Double(Foundation.NSEC_PER_SEC) * 60 * 60 * 24
|
||||
static let NSEC_PER_W = Double(Foundation.NSEC_PER_SEC) * 60 * 60 * 24 * 7
|
||||
}
|
|
@ -1,14 +1,16 @@
|
|||
import XCTest
|
||||
@testable import Schedule
|
||||
|
||||
final class ScheduleTests: XCTestCase {
|
||||
final class SchedulesTests: XCTestCase {
|
||||
|
||||
func testMakeSchedule() {
|
||||
let leeway = 0.01.seconds
|
||||
|
||||
func testMake() {
|
||||
let intervals = [1.second, 2.hours, 3.days, 4.weeks]
|
||||
let s0 = Schedule.of(intervals[0], intervals[1], intervals[2], intervals[3])
|
||||
XCTAssert(s0.makeIterator().isEqual(to: intervals, leeway: 0.001.seconds))
|
||||
XCTAssert(s0.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
|
||||
let s1 = Schedule.from(intervals)
|
||||
XCTAssert(s1.makeIterator().isEqual(to: intervals, leeway: 0.001.seconds))
|
||||
XCTAssert(s1.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
|
||||
|
||||
let d0 = Date() + intervals[0]
|
||||
let d1 = d0 + intervals[1]
|
||||
|
@ -17,18 +19,18 @@ final class ScheduleTests: XCTestCase {
|
|||
|
||||
let s2 = Schedule.of(d0, d1, d2, d3)
|
||||
let s3 = Schedule.from([d0, d1, d2, d3])
|
||||
XCTAssert(s2.makeIterator().isEqual(to: intervals, leeway: 0.001.seconds))
|
||||
XCTAssert(s3.makeIterator().isEqual(to: intervals, leeway: 0.001.seconds))
|
||||
XCTAssert(s2.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
|
||||
XCTAssert(s3.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
|
||||
}
|
||||
|
||||
func testDates() {
|
||||
let iterator = Schedule.of(1.days, 2.weeks).dates.makeIterator()
|
||||
var next = iterator.next()
|
||||
XCTAssertNotNil(next)
|
||||
XCTAssert(next!.intervalSinceNow.isEqual(to: 1.days, leeway: 0.001.seconds))
|
||||
XCTAssert(next!.intervalSinceNow.isAlmostEqual(to: 1.days, leeway: leeway))
|
||||
next = iterator.next()
|
||||
XCTAssertNotNil(next)
|
||||
XCTAssert(next!.intervalSinceNow.isEqual(to: 2.weeks + 1.days, leeway: 0.001.seconds))
|
||||
XCTAssert(next!.intervalSinceNow.isAlmostEqual(to: 2.weeks + 1.days, leeway: leeway))
|
||||
}
|
||||
|
||||
func testNever() {
|
||||
|
@ -40,19 +42,27 @@ final class ScheduleTests: XCTestCase {
|
|||
let s1: [Interval] = [4.days, 5.weeks]
|
||||
let s3 = Schedule.from(s0).concat(Schedule.from(s1))
|
||||
let s4 = Schedule.from(s0 + s1)
|
||||
XCTAssert(s3.makeIterator().isEqual(to: s4.makeIterator(), leeway: 0.001.seconds))
|
||||
XCTAssert(s3.isAlmostEqual(to: s4, leeway: leeway))
|
||||
}
|
||||
|
||||
func testMerge() {
|
||||
let intervals0: [Interval] = [1.second, 2.minutes, 1.hour]
|
||||
let intervals1: [Interval] = [2.seconds, 1.minutes, 1.seconds]
|
||||
let scheudle0 = Schedule.from(intervals0).merge(Schedule.from(intervals1))
|
||||
let scheudle1 = Schedule.of(1.second, 1.second, 1.minutes, 1.seconds, 58.seconds, 1.hour)
|
||||
XCTAssert(scheudle0.isAlmostEqual(to: scheudle1, leeway: leeway))
|
||||
}
|
||||
|
||||
func testAt() {
|
||||
let s = Schedule.at(Date() + 1.second)
|
||||
let next = s.makeIterator().next()
|
||||
XCTAssertNotNil(next)
|
||||
XCTAssert(next!.isEqual(to: 1.second, leeway: 0.001.seconds))
|
||||
XCTAssert(next!.isAlmostEqual(to: 1.second, leeway: leeway))
|
||||
}
|
||||
|
||||
func testCount() {
|
||||
func testFirst() {
|
||||
var count = 10
|
||||
let s = Schedule.every(1.second).count(count)
|
||||
let s = Schedule.every(1.second).first(count)
|
||||
let i = s.makeIterator()
|
||||
while count > 0 {
|
||||
XCTAssertNotNil(i.next())
|
||||
|
@ -70,16 +80,20 @@ final class ScheduleTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
func testMerge() {
|
||||
let intervals0: [Interval] = [1.second, 2.minutes, 1.hour]
|
||||
let intervals1: [Interval] = [2.seconds, 1.minutes, 1.seconds]
|
||||
let scheudle0 = Schedule.from(intervals0).merge(Schedule.from(intervals1))
|
||||
let scheudle1 = Schedule.of(1.second, 1.second, 1.minutes, 1.seconds, 58.seconds, 1.hour)
|
||||
XCTAssert(scheudle0.makeIterator().isEqual(to: scheudle1.makeIterator(), leeway: 0.001.seconds))
|
||||
func testNow() {
|
||||
let s0 = Schedule.now
|
||||
let s1 = Schedule.of(Date())
|
||||
XCTAssertTrue(s0.isAlmostEqual(to: s1, leeway: leeway))
|
||||
}
|
||||
|
||||
func testAfterAndRepeating() {
|
||||
let s0 = Schedule.after(1.day, repeating: 1.hour).first(3)
|
||||
let s1 = Schedule.of(1.day, 1.hour, 1.hour)
|
||||
XCTAssertTrue(s0.isAlmostEqual(to: s1, leeway: leeway))
|
||||
}
|
||||
|
||||
func testEveryPeriod() {
|
||||
let s = Schedule.every(1.year).count(10)
|
||||
let s = Schedule.every(1.year).first(10)
|
||||
var date = Date()
|
||||
for i in s.dates {
|
||||
XCTAssertEqual(i.dateComponents.year!, date.dateComponents.year! + 1)
|
||||
|
@ -90,7 +104,7 @@ final class ScheduleTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testEveryWeekday() {
|
||||
let s = Schedule.every(.friday).at("11:11:00").count(5)
|
||||
let s = Schedule.every(.friday).at("11:11:00").first(5)
|
||||
for i in s.dates {
|
||||
XCTAssertEqual(i.dateComponents.weekday, 6)
|
||||
XCTAssertEqual(i.dateComponents.hour, 11)
|
||||
|
@ -98,7 +112,7 @@ final class ScheduleTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testEveryMonthday() {
|
||||
let s = Schedule.every(.april(2)).at(11, 11).count(5)
|
||||
let s = Schedule.every(.april(2)).at(11, 11).first(5)
|
||||
for i in s.dates {
|
||||
XCTAssertEqual(i.dateComponents.month, 4)
|
||||
XCTAssertEqual(i.dateComponents.day, 2)
|
||||
|
@ -107,14 +121,16 @@ final class ScheduleTests: XCTestCase {
|
|||
}
|
||||
|
||||
static var allTests = [
|
||||
("testMakeSchedule", testMakeSchedule),
|
||||
("testMake", testMake),
|
||||
("testDates", testDates),
|
||||
("testNever", testNever),
|
||||
("testConcat", testConcat),
|
||||
("testAt", testAt),
|
||||
("testCount", testCount),
|
||||
("testUntil", testUntil),
|
||||
("testMerge", testMerge),
|
||||
("testAt", testAt),
|
||||
("testFirst", testFirst),
|
||||
("testUntil", testUntil),
|
||||
("testNow", testNow),
|
||||
("testAfterAndRepeating", testAfterAndRepeating),
|
||||
("testEveryPeriod", testEveryPeriod),
|
||||
("testEveryWeekday", testEveryWeekday),
|
||||
("testEveryMonthday", testEveryMonthday)
|
|
@ -1,62 +0,0 @@
|
|||
//
|
||||
// Util.swift
|
||||
// Schedule
|
||||
//
|
||||
// Created by Quentin Jin on 2018/7/2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import Schedule
|
||||
|
||||
extension Date {
|
||||
|
||||
var dateComponents: DateComponents {
|
||||
return Calendar.current.dateComponents(in: TimeZone.current, from: self)
|
||||
}
|
||||
|
||||
var localizedDescription: String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.timeZone = TimeZone.autoupdatingCurrent
|
||||
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
|
||||
return formatter.string(from: self)
|
||||
}
|
||||
|
||||
init(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int, nanosecond: Int) {
|
||||
let components = DateComponents(year: year, month: month, day: day, hour: hour, minute: minute, second: second, nanosecond: nanosecond)
|
||||
let date = Calendar.current.date(from: components) ?? Date.distantPast
|
||||
self.init(timeIntervalSinceNow: date.timeIntervalSinceNow)
|
||||
}
|
||||
}
|
||||
|
||||
extension Sequence where Element == Interval {
|
||||
|
||||
func isEqual<S>(to sequence: S, leeway: Interval) -> Bool where S: Sequence, S.Element == Element {
|
||||
var i0 = self.makeIterator()
|
||||
var i1 = sequence.makeIterator()
|
||||
while let l = i0.next(), let r = i1.next() {
|
||||
let diff = Interval.longest(l, r) - Interval.shortest(l, r)
|
||||
if diff.isShorter(than: leeway) { continue }
|
||||
return false
|
||||
}
|
||||
return i0.next() == i1.next()
|
||||
}
|
||||
}
|
||||
|
||||
extension Interval {
|
||||
|
||||
func isEqual(to interval: Interval, leeway: Interval) -> Bool {
|
||||
let diff = Interval.longest(self, interval) - Interval.shortest(self, interval)
|
||||
return diff.isShorter(than: leeway)
|
||||
}
|
||||
}
|
||||
|
||||
struct K {
|
||||
|
||||
static let ns_per_us = Double(NSEC_PER_USEC)
|
||||
static let ns_per_ms = Double(NSEC_PER_MSEC)
|
||||
static let ns_per_s = Double(NSEC_PER_SEC)
|
||||
static let ns_per_m = Double(NSEC_PER_SEC) * 60
|
||||
static let ns_per_h = Double(NSEC_PER_SEC) * 60 * 60
|
||||
static let ns_per_d = Double(NSEC_PER_SEC) * 60 * 60 * 24
|
||||
static let ns_per_w = Double(NSEC_PER_SEC) * 60 * 60 * 24 * 7
|
||||
}
|
|
@ -3,7 +3,12 @@ import XCTest
|
|||
#if !os(macOS)
|
||||
public func allTests() -> [XCTestCaseEntry] {
|
||||
return [
|
||||
testCase(ScheduleTests.allTests),
|
||||
testCase(DateTimeTests.allTests),
|
||||
testCase(SchedulesTests.allTests),
|
||||
testCase(TaskCenterTests.allTests),
|
||||
testCase(ExtensionsTests.allTests),
|
||||
testCase(BucketTests.allTests),
|
||||
testCase(WeakSetTests.allTests),
|
||||
]
|
||||
}
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue