Improve api, add tests

This commit is contained in:
QuentinJin 2018-07-26 22:06:18 +08:00
parent f48758d4f7
commit 792eabf3d4
17 changed files with 639 additions and 347 deletions

View File

@ -1,18 +1,20 @@
# Schedule # 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 ## Features
- 📆 Date-based scheduling - 📆 Date-based scheduling
- ⏳ Interval-based scheduling - ⏳ Interval-based scheduling
- 📝 Mixture rules scheduling - 📝 Mixture rules
- 👩‍🎓‍ Human readable period parsing
- 🚦 Suspend, resume, cancel - 🚦 Suspend, resume, cancel
- 🏷 Tag related management - 🏷 Tag-based management
- 🍰 Action appending/removing
- 🍻 No need to concern about runloop - 🍻 No need to concern about runloop
- 👻 No need to concern about circular reference - 👻 No need to concern about circular reference
- 🍭 **Sweet apis** - 🍭 **Incredibly Sweet API**
## Usage ## Usage
@ -20,10 +22,12 @@
Scheduling a task can not be simplier. Scheduling a task can not be simplier.
```swift ```swift
func heartBeat() { } Schedule.every(1.second).do {
Schedule.every(0.5.seconds).do(heartBeat) print("heart beat")
}
``` ```
### Interval-based Scheduling ### Interval-based Scheduling
```swift ```swift
@ -43,7 +47,6 @@ Schedule.from([1.second, 2.minutes, 3.hours]).do { }
``` ```
### Date-based Scheduling ### Date-based Scheduling
```swift ```swift
@ -55,14 +58,15 @@ Schedule.every(.monday, .tuesday).at("11:11").do { }
Schedule.every(.september(30)).at("10:00:00").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.of(date0, date1, date2).do { }
Schedule.from([date0, date1, date2]).do { } Schedule.from([date0, date1, date2]).do { }
``` ```
### Mixture rules
### Custom Scheduling
```swift ```swift
import Schedule import Schedule
@ -76,28 +80,27 @@ birthdaySchedule.do {
} }
/// merge /// merge
let s3 = Schedule.every(.january(1)).at(8, 30) let s3 = Schedule.every(.january(1)).at("8:00")
let s4 = Schedule.every(.october(1)).at(8, 30) let s4 = Schedule.every(.october(1)).at("9:00 AM")
let holiday = s3.merge(s3) let holiday = s3.merge(s3)
holidaySchedule.do { holidaySchedule.do {
print("Happy holiday") print("Happy holiday")
} }
/// count /// cut
let s5 = Schedule.after(5.seconds).concat(Schedule.every(1.day)) let s5 = Schedule.after(5.seconds).concat(Schedule.every(1.day))
let s6 = s5.count(10) let s6 = s5.cut(10)
/// until /// until
let s7 = Schedule.every(.monday).at(11, 12) let s7 = Schedule.every(.monday).at(11, 12)
let s8 = s7.until(date) let s8 = s7.until(date)
``` ```
### Task management ### 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 ```swift
let task = Schedule.every(1.day).do { } let task = Schedule.every(1.day).do { }
@ -142,7 +145,22 @@ let key = dailyTask.addAction {
dailyTask.removeAction(byKey: key) 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 ```swift
Schedule.every(1.second).do(dependOn: self) { Schedule.every(1.second).do(dependOn: self) {
@ -152,11 +170,11 @@ Schedule.every(1.second).do(dependOn: self) {
## Contribution ## Contribution
Feel free to criticize! Feel free to criticize. 🍺
## Installation ## Installation
Schedul supports all popular dependency managers. Schedule supports all popular dependency managers.
### Cocoapods ### Cocoapods
@ -174,6 +192,6 @@ github "jianstm/Schedule"
```swift ```swift
dependencies: [ dependencies: [
.package(url: "https://github.com/jianstm/Schedule", .exact("0.0.4")) .package(url: "https://github.com/jianstm/Schedule", .upToNextMinor("0.0.0"))
] ]
``` ```

View File

@ -30,6 +30,7 @@
6624105421075FDC00013B00 /* ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6624105221075F8A00013B00 /* ExtensionsTests.swift */; }; 6624105421075FDC00013B00 /* ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6624105221075F8A00013B00 /* ExtensionsTests.swift */; };
66241056210761F000013B00 /* BucketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66241055210761F000013B00 /* BucketTests.swift */; }; 66241056210761F000013B00 /* BucketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66241055210761F000013B00 /* BucketTests.swift */; };
662410592107804400013B00 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662410582107804400013B00 /* Lock.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 */; }; 669D215020FE1B7300AFFDF7 /* Interval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669D214F20FE1B7300AFFDF7 /* Interval.swift */; };
669D215220FE1B8A00AFFDF7 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669D215120FE1B8A00AFFDF7 /* Time.swift */; }; 669D215220FE1B8A00AFFDF7 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669D215120FE1B8A00AFFDF7 /* Time.swift */; };
669D215420FE1BA600AFFDF7 /* Period.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669D215320FE1BA600AFFDF7 /* Period.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_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_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_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_49 /* SchedulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* SchedulesTests.swift */; };
OBJ_50 /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* Util.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_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 */; }; OBJ_53 /* Schedule.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "Schedule::Schedule::Product" /* Schedule.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -66,6 +67,7 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
62661F402108433300055501 /* WeakSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakSet.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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_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_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_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_16 /* SchedulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchedulesTests.swift; sourceTree = "<group>"; };
OBJ_17 /* Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Util.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_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>"; }; 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; }; "Schedule::Schedule::Product" /* Schedule.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Schedule.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -113,22 +116,14 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
62EF93FB21085312001F7A47 /* Utils */ = { 62EF93FB21085312001F7A47 /* UtilsTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
6624105221075F8A00013B00 /* ExtensionsTests.swift */, 6624105221075F8A00013B00 /* ExtensionsTests.swift */,
66241055210761F000013B00 /* BucketTests.swift */, 66241055210761F000013B00 /* BucketTests.swift */,
62EF93F821085309001F7A47 /* WeakSetTests.swift */, 62EF93F821085309001F7A47 /* WeakSetTests.swift */,
); );
name = Utils; name = UtilsTests;
sourceTree = "<group>";
};
62EF93FC210863FA001F7A47 /* DateTime */ = {
isa = PBXGroup;
children = (
OBJ_15 /* DateTimeTests.swift */,
);
name = DateTime;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
662410462104A0EC00013B00 /* Utils */ = { 662410462104A0EC00013B00 /* Utils */ = {
@ -138,6 +133,7 @@
6624104D2104AF2100013B00 /* Extensions.swift */, 6624104D2104AF2100013B00 /* Extensions.swift */,
662410582107804400013B00 /* Lock.swift */, 662410582107804400013B00 /* Lock.swift */,
62661F402108433300055501 /* WeakSet.swift */, 62661F402108433300055501 /* WeakSet.swift */,
666AEAF22109F90C00A50F27 /* Atomic.swift */,
); );
name = Utils; name = Utils;
sourceTree = "<group>"; sourceTree = "<group>";
@ -166,6 +162,7 @@
OBJ_13 /* Tests */ = { OBJ_13 /* Tests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
626C7209210968C6005F10A7 /* LinuxMain.swift */,
OBJ_14 /* ScheduleTests */, OBJ_14 /* ScheduleTests */,
); );
name = Tests; name = Tests;
@ -174,12 +171,12 @@
OBJ_14 /* ScheduleTests */ = { OBJ_14 /* ScheduleTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
62EF93FC210863FA001F7A47 /* DateTime */, OBJ_15 /* DateTimeTests.swift */,
62EF93FB21085312001F7A47 /* Utils */, OBJ_17 /* Misc.swift */,
OBJ_16 /* ScheduleTests.swift */, OBJ_16 /* SchedulesTests.swift */,
OBJ_17 /* Util.swift */,
OBJ_18 /* XCTestManifests.swift */,
62EF93FD21086411001F7A47 /* TaskCenterTests.swift */, 62EF93FD21086411001F7A47 /* TaskCenterTests.swift */,
OBJ_18 /* XCTestManifests.swift */,
62EF93FB21085312001F7A47 /* UtilsTests */,
); );
name = ScheduleTests; name = ScheduleTests;
path = Tests/ScheduleTests; path = Tests/ScheduleTests;
@ -315,6 +312,7 @@
669D215020FE1B7300AFFDF7 /* Interval.swift in Sources */, 669D215020FE1B7300AFFDF7 /* Interval.swift in Sources */,
669D215620FE1BB400AFFDF7 /* Weekday.swift in Sources */, 669D215620FE1BB400AFFDF7 /* Weekday.swift in Sources */,
62EF940821086F63001F7A47 /* Timeline.swift in Sources */, 62EF940821086F63001F7A47 /* Timeline.swift in Sources */,
666AEAF32109F90C00A50F27 /* Atomic.swift in Sources */,
OBJ_28 /* Task.swift in Sources */, OBJ_28 /* Task.swift in Sources */,
OBJ_29 /* Schedule.swift in Sources */, OBJ_29 /* Schedule.swift in Sources */,
62661F412108433400055501 /* WeakSet.swift in Sources */, 62661F412108433400055501 /* WeakSet.swift in Sources */,
@ -343,8 +341,8 @@
6624105421075FDC00013B00 /* ExtensionsTests.swift in Sources */, 6624105421075FDC00013B00 /* ExtensionsTests.swift in Sources */,
62EF93FA2108530D001F7A47 /* WeakSetTests.swift in Sources */, 62EF93FA2108530D001F7A47 /* WeakSetTests.swift in Sources */,
OBJ_48 /* DateTimeTests.swift in Sources */, OBJ_48 /* DateTimeTests.swift in Sources */,
OBJ_49 /* ScheduleTests.swift in Sources */, OBJ_49 /* SchedulesTests.swift in Sources */,
OBJ_50 /* Util.swift in Sources */, OBJ_50 /* Misc.swift in Sources */,
OBJ_51 /* XCTestManifests.swift in Sources */, OBJ_51 /* XCTestManifests.swift in Sources */,
66241056210761F000013B00 /* BucketTests.swift in Sources */, 66241056210761F000013B00 /* BucketTests.swift in Sources */,
62EF93FF21086420001F7A47 /* TaskCenterTests.swift in Sources */, 62EF93FF21086420001F7A47 /* TaskCenterTests.swift in Sources */,

View File

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

View File

@ -8,21 +8,14 @@
import Foundation import Foundation
/// `Interval` represents a length of time. /// `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 { public struct Interval {
let ns: Int
/// The length of this interval in nanoseconds. /// The length of this interval in nanoseconds.
public var nanoseconds: Double { public var nanoseconds: Double
return Double(ns)
}
/// Creates an interval from the given number of nanoseconds. /// Creates an interval from the given number of nanoseconds.
public init(nanoseconds: Double) { public init(nanoseconds: Double) {
self.ns = nanoseconds.clampedToInt() self.nanoseconds = nanoseconds
} }
/// A boolean value indicating whether this interval is negative. /// 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`. /// but the interval comparing `1.hour` to `3.hour` is `-2.hour`.
/// In this case, `-2.hour` means **two hours shorter** /// In this case, `-2.hour` means **two hours shorter**
public var isNegative: Bool { public var isNegative: Bool {
return ns < 0 return nanoseconds.isLess(than: 0)
} }
/// The absolute value of the length of this interval, /// The absolute value of the length of this interval,
/// measured in nanoseconds, but disregarding its sign. /// measured in nanoseconds, but disregarding its sign.
public var magnitude: UInt { public var magnitude: Double {
return ns.magnitude return nanoseconds.magnitude
} }
} }
@ -72,25 +65,25 @@ extension Interval {
return intervals.sorted(by: { $0.magnitude < $1.magnitude })[0] 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 /// 1.hour * 2 == 2.hours
public static func *(lhs: Interval, rhs: Double) -> Interval { public func multiplying(by number: Double) -> Interval {
return Interval(nanoseconds: lhs.nanoseconds * rhs) 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 /// 1.hour + 1.hour == 2.hours
public static func +(lhs: Interval, rhs: Interval) -> Interval { public func adding(_ other: Interval) -> Interval {
return Interval(nanoseconds: lhs.nanoseconds + rhs.nanoseconds) 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 /// 2.hours - 1.hour == 1.hour
public static func -(lhs: Interval, rhs: Interval) -> Interval { public func subtracting(_ other: Interval) -> Interval {
return Interval(nanoseconds: lhs.nanoseconds - rhs.nanoseconds) 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 { extension Interval: Hashable {
/// The hashValue of this interval. /// The hashValue of this interval.
@ -140,6 +158,7 @@ extension Interval: Hashable {
} }
} }
extension Date { extension Date {
/// The interval between this date and the current date and time. /// 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. /// 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) return addingTimeInterval(interval.seconds)
} }
/// Returns a date with an interval added to it. /// Returns a date with an interval added to it.
public static func +(lhs: Date, rhs: Interval) -> Date { public static func +(lhs: Date, rhs: Interval) -> Date {
return lhs.addingInterval(rhs) return lhs.adding(rhs)
}
/// Adds a interval to the date.
public static func +=(lhs: inout Date, rhs: Interval) {
lhs = lhs + rhs
} }
} }
extension DispatchSourceTimer { extension DispatchSourceTimer {
func schedule(after interval: Interval) { func schedule(after interval: Interval) {
@ -179,16 +194,20 @@ extension DispatchSourceTimer {
schedule(wallDeadline: .distantFuture) schedule(wallDeadline: .distantFuture)
return 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. /// `IntervalConvertible` provides a set of intuitive api for creating interval.
public protocol IntervalConvertible { public protocol IntervalConvertible {
var nanoseconds: Interval { get } var nanoseconds: Interval { get }
} }
extension Int: IntervalConvertible { extension Int: IntervalConvertible {
public var nanoseconds: Interval { public var nanoseconds: Interval {
@ -196,6 +215,7 @@ extension Int: IntervalConvertible {
} }
} }
extension Double: IntervalConvertible { extension Double: IntervalConvertible {
public var nanoseconds: Interval { public var nanoseconds: Interval {
@ -203,6 +223,7 @@ extension Double: IntervalConvertible {
} }
} }
extension IntervalConvertible { extension IntervalConvertible {
public var nanosecond: Interval { public var nanosecond: Interval {

View File

@ -7,7 +7,7 @@
import Foundation import Foundation
/// `MonthDay` represents a day in month, without a time. /// `Monthday` represents a day in month, without a time.
public enum MonthDay { public enum MonthDay {
case january(Int) case january(Int)

View File

@ -11,21 +11,23 @@ extension Schedule {
/// Schedules a task with this 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, /// 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 be scheduled any more, something parasitism. /// task will not be scheduled any more, something like parasitism.
/// ///
/// This feature is very useful when you want a timer live and die /// This feature is very useful when you want a timer live and die
/// with a controller. /// with a controller.
/// ///
/// - Parameters: /// - Parameters:
/// - queue: The dispatch queue to which the task will be submitted. /// - 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. /// - host: The object to host on.
/// - onElapse: The action to do when time is out. /// - onElapse: The action to do when time is out.
/// - Returns: The task just created. /// - Returns: The task just created.
@discardableResult @discardableResult
public func `do`(queue: DispatchQueue? = nil, public func `do`(queue: DispatchQueue? = nil,
tag: String? = nil,
host: AnyObject, host: AnyObject,
onElapse: @escaping (Task) -> Void) -> Task { onElapse: @escaping (Task) -> Void) -> Task {
return ParasiticTask(schedule: self, queue: queue, host: host, onElapse: onElapse) return ParasiticTask(schedule: self, queue: queue, host: host, onElapse: onElapse)
@ -33,9 +35,9 @@ extension Schedule {
/// Schedules a task with this 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, /// 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. /// task will not scheduled any more, something like parasitism.
/// ///
/// This feature is very useful when you want a timer live and die /// This feature is very useful when you want a timer live and die
@ -43,11 +45,13 @@ extension Schedule {
/// ///
/// - Parameters: /// - Parameters:
/// - queue: The dispatch queue to which the task will be submitted. /// - 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. /// - host: The object to host on.
/// - onElapse: The action to do when time is out. /// - onElapse: The action to do when time is out.
/// - Returns: The task just created. /// - Returns: The task just created.
@discardableResult @discardableResult
public func `do`(queue: DispatchQueue? = nil, public func `do`(queue: DispatchQueue? = nil,
tag: String? = nil,
host: AnyObject, host: AnyObject,
onElapse: @escaping () -> Void) -> Task { onElapse: @escaping () -> Void) -> Task {
return self.do(queue: queue, host: host, onElapse: { (_) in onElapse() }) return self.do(queue: queue, host: host, onElapse: { (_) in onElapse() })

View File

@ -24,19 +24,19 @@ import Foundation
/// in these two cases are quite different. /// in these two cases are quite different.
public struct Period { 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, public init(years: Int = 0, months: Int = 0, days: Int = 0,
hours: Int = 0, minutes: Int = 0, seconds: Int = 0, hours: Int = 0, minutes: Int = 0, seconds: Int = 0,
@ -50,27 +50,121 @@ public struct Period {
self.nanoseconds = nanoseconds 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 { public static func +(lhs: Period, rhs: Period) -> Period {
return Period(years: lhs.years.clampedAdding(rhs.years), return lhs.adding(rhs)
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))
} }
/// Returns a date with a period added to it. /// Returns a period with an interval 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.
public static func +(lhs: Period, rhs: Interval) -> Period { public static func +(lhs: Period, rhs: Interval) -> Period {
return Period(years: lhs.years, months: lhs.months, days: lhs.days, return lhs.adding(rhs)
hours: lhs.hours, minutes: lhs.minutes, seconds: lhs.seconds, }
nanoseconds: lhs.nanoseconds.clampedAdding(rhs.ns))
/// 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 { 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 { extension Int {
public var years: Period { public var years: Period {

View File

@ -8,12 +8,9 @@
import Foundation import Foundation
/// `Schedule` represents a plan that gives the times /// `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. /// `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 { public struct Schedule {
private var sequence: AnySequence<Interval> private var sequence: AnySequence<Interval>
@ -29,7 +26,7 @@ public struct Schedule {
/// ///
/// - Parameters: /// - Parameters:
/// - queue: The dispatch queue to which the task will be submitted. /// - 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. /// - onElapse: The task to invoke when time is out.
/// - Returns: The task just created. /// - Returns: The task just created.
@discardableResult @discardableResult
@ -43,7 +40,7 @@ public struct Schedule {
/// ///
/// - Parameters: /// - Parameters:
/// - queue: The dispatch queue to which the task will be submitted. /// - 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. /// - onElapse: The task to invoke when time is out.
/// - Returns: The task just created. /// - Returns: The task just created.
@discardableResult @discardableResult
@ -58,7 +55,7 @@ extension Schedule {
/// Creates a schedule from a `makeUnderlyingIterator()` method. /// 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. /// produced by the iterator that `makeUnderlyingIterator` returns.
/// ///
/// For example: /// For example:
@ -84,13 +81,13 @@ extension Schedule {
} }
/// Creates a schedule from an interval sequence. /// 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 { public static func from<S>(_ sequence: S) -> Schedule where S: Sequence, S.Element == Interval {
return Schedule(sequence) return Schedule(sequence)
} }
/// Creates a schedule from an interval array. /// 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 { public static func of(_ intervals: Interval...) -> Schedule {
return Schedule(intervals) return Schedule(intervals)
} }
@ -100,7 +97,7 @@ extension Schedule {
/// Creates a schedule from a `makeUnderlyingIterator()` method. /// 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. /// produced by the iterator that `makeUnderlyingIterator` returns.
/// ///
/// For example: /// For example:
@ -116,33 +113,33 @@ extension Schedule {
/// } /// }
/// ///
/// > "now: 2001-01-01 00:00:00" /// > "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 /// You are not supposed to return `Date()` in making interator,
/// if you want to invoke a task immediately, /// if you want to execute a task immediately,
/// use `Schedule.now` then `concat` another schedule instead. /// use `Schedule.now` then `concat` another schedule instead.
public static func make<I>(_ makeUnderlyingIterator: @escaping () -> I) -> Schedule where I: IteratorProtocol, I.Element == Date { public static func make<I>(_ makeUnderlyingIterator: @escaping () -> I) -> Schedule where I: IteratorProtocol, I.Element == Date {
return Schedule.make { () -> AnyIterator<Interval> in return Schedule.make { () -> AnyIterator<Interval> in
var iterator = makeUnderlyingIterator() var iterator = makeUnderlyingIterator()
var previous: Date! var last: Date!
return AnyIterator { return AnyIterator {
previous = previous ?? Date() last = last ?? Date()
guard let next = iterator.next() else { return nil } guard let next = iterator.next() else { return nil }
defer { previous = next } defer { last = next }
return next.interval(since: previous) return next.interval(since: last)
} }
} }
} }
/// Creates a schedule from a date sequence. /// 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 { public static func from<S>(_ sequence: S) -> Schedule where S: Sequence, S.Element == Date {
return Schedule.make(sequence.makeIterator) return Schedule.make(sequence.makeIterator)
} }
/// Creates a schedule from a date array. /// 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 { public static func of(_ dates: Date...) -> Schedule {
return Schedule.from(dates) return Schedule.from(dates)
} }
@ -151,12 +148,12 @@ extension Schedule {
public var dates: AnySequence<Date> { public var dates: AnySequence<Date> {
return AnySequence { () -> AnyIterator<Date> in return AnySequence { () -> AnyIterator<Date> in
let iterator = self.makeIterator() let iterator = self.makeIterator()
var previous: Date! var last: Date!
return AnyIterator { return AnyIterator {
previous = previous ?? Date() last = last ?? Date()
guard let interval = iterator.next() else { return nil } guard let interval = iterator.next() else { return nil }
previous = previous + interval last = last + interval
return previous return last
} }
} }
} }
@ -174,7 +171,7 @@ extension Schedule {
return Schedule.of(Date.distantFuture) return Schedule.of(Date.distantFuture)
} }
/// A schedule with no date. /// A schedule that is never going to happen.
public static var never: Schedule { public static var never: Schedule {
return Schedule.make { return Schedule.make {
AnyIterator<Date> { nil } AnyIterator<Date> { nil }
@ -191,6 +188,7 @@ extension Schedule {
/// let s0 = Schedule.of(1.second, 2.seconds, 3.seconds) /// let s0 = Schedule.of(1.second, 2.seconds, 3.seconds)
/// let s1 = Schedule.of(4.seconds, 4.seconds, 4.seconds) /// let s1 = Schedule.of(4.seconds, 4.seconds, 4.seconds)
/// let s2 = s0.concat(s1) /// let s2 = s0.concat(s1)
///
/// > s2 /// > s2
/// > 1.second, 2.seconds, 3.seconds, 4.seconds, 4.seconds, 4.seconds /// > 1.second, 2.seconds, 3.seconds, 4.seconds, 4.seconds, 4.seconds
public func concat(_ schedule: Schedule) -> Schedule { public func concat(_ schedule: Schedule) -> Schedule {
@ -212,14 +210,14 @@ extension Schedule {
/// let s1 = Schedule.of(2.seconds, 4.seconds, 6.seconds) /// let s1 = Schedule.of(2.seconds, 4.seconds, 6.seconds)
/// let s2 = s0.concat(s1) /// let s2 = s0.concat(s1)
/// > s2 /// > 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 { public func merge(_ schedule: Schedule) -> Schedule {
return Schedule.make { () -> AnyIterator<Interval> in return Schedule.make { () -> AnyIterator<Date> in
let i0 = self.dates.makeIterator() let i0 = self.dates.makeIterator()
let i1 = schedule.dates.makeIterator() let i1 = schedule.dates.makeIterator()
var buffer0: Date! var buffer0: Date!
var buffer1: Date! var buffer1: Date!
let iterator = AnyIterator<Date> { return AnyIterator<Date> {
if buffer0 == nil { buffer0 = i0.next() } if buffer0 == nil { buffer0 = i0.next() }
if buffer1 == nil { buffer1 = i1.next() } if buffer1 == nil { buffer1 = i1.next() }
@ -234,149 +232,149 @@ extension Schedule {
if d == buffer1 { buffer1 = nil } if d == buffer1 { buffer1 = nil }
return d 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: /// For example:
/// ///
/// let s0 = Schedule.every(1.second) /// let s0 = Schedule.every(1.second)
/// let s1 = s0.count(3) /// let s1 = s0.first(3)
/// > s1 /// > s1
/// 1.second, 1.second, 1.second /// 1.second, 1.second, 1.second
public func count(_ count: Int) -> Schedule { public func first(_ count: Int) -> Schedule {
return Schedule.make { () -> AnyIterator<Interval> in return Schedule.make { () -> AnyIterator<Interval> in
let iterator = self.makeIterator() let iterator = self.makeIterator()
var tick = 0 var num = 0
return AnyIterator { return AnyIterator {
guard tick < count, let interval = iterator.next() else { return nil } guard num < count, let interval = iterator.next() else { return nil }
tick += 1 num += 1
return interval 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 { public func until(_ date: Date) -> Schedule {
return Schedule.make { () -> AnyIterator<Interval> in return Schedule.make { () -> AnyIterator<Date> in
let iterator = self.makeIterator() let iterator = self.dates.makeIterator()
var previous: Date!
return AnyIterator { return AnyIterator {
previous = previous ?? Date() guard let next = iterator.next(), next < date else {
guard let interval = iterator.next(),
previous.addingTimeInterval(interval.seconds) < date else {
return nil return nil
} }
previous.addTimeInterval(interval.seconds) return next
return interval
} }
} }
} }
/// Creates a schedule that invokes the task immediately. /// Creates a schedule that executes the task immediately.
public static var now: Schedule { public static var now: Schedule {
return Schedule.of(0.nanosecond) 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 { public static func after(_ delay: Interval) -> Schedule {
return Schedule.of(delay) 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 { public static func every(_ interval: Interval) -> Schedule {
return Schedule.make { return Schedule.make {
AnyIterator { interval } 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 { public static func at(_ date: Date) -> Schedule {
return Schedule.of(date) 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. /// every interval.
public static func after(_ delay: Interval, repeating interval: Interval) -> Schedule { public static func after(_ delay: Interval, repeating interval: Interval) -> Schedule {
return Schedule.after(delay).concat(Schedule.every(interval)) 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 { public static func every(_ period: Period) -> Schedule {
return Schedule.make { () -> AnyIterator<Interval> in return Schedule.make { () -> AnyIterator<Interval> in
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar(identifier: .gregorian)
var previous: Date! var last: Date!
return AnyIterator { return AnyIterator {
previous = previous ?? Date() last = last ?? Date()
guard let next = calendar.date(byAdding: period.asDateComponents(), guard let next = calendar.date(byAdding: period.asDateComponents(),
to: previous) else { to: last) else {
return nil return nil
} }
defer { previous = next } defer { last = next }
return next.interval(since: previous) 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 { extension Schedule {
/// `EveryDateMiddleware` represents a middleware that wraps a schedule /// `DateMiddleware` represents a middleware that wraps a schedule
/// which only specify date without time. /// which was only specified date without time.
/// ///
/// You should call `at` method to get the time specified schedule. /// You should call `at` method to get the schedule with time specified.
public struct EveryDateMiddleware { public struct DateMiddleware {
fileprivate let schedule: Schedule fileprivate let schedule: Schedule
/// Returns a schedule at the specific timing. /// Returns a schedule at the specific time.
public func at(_ timing: Time) -> Schedule { public func at(_ time: Time) -> Schedule {
return Schedule.make { () -> AnyIterator<Date> in
return Schedule.make { () -> AnyIterator<Interval> in
let iterator = self.schedule.dates.makeIterator() let iterator = self.schedule.dates.makeIterator()
let calendar = Calendar.autoupdatingCurrent var calendar = Calendar(identifier: .gregorian)
var previous: Date! var last: Date!
return AnyIterator { return AnyIterator {
previous = previous ?? Date() last = last ?? Date()
guard let date = iterator.next(), guard let date = iterator.next(),
let next = calendar.nextDate(after: date, let next = calendar.nextDate(after: date,
matching: timing.asDateComponents(), matching: time.asDateComponents(),
matchingPolicy: .strict) else { matchingPolicy: .strict) else {
return nil return nil
} }
defer { previous = next } defer { last = next }
return next.interval(since: previous) return next
} }
} }
} }
/// Returns a schedule at the specific timing. /// Returns a schedule at the specific time.
/// ///
/// For example: /// See Time's constructor
/// public func at(_ time: String) -> Schedule {
/// let s = Schedule.every(.monday).at("11:11") guard let time = Time(time) else {
///
/// 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 {
return Schedule.never return Schedule.never
} }
return at(time) return at(time)
} }
/// Returns a schedule at the specific timing. /// Returns a schedule at the specific time.
public func at(_ timing: Int...) -> Schedule { ///
let hour = timing[0] /// .at(1) => 01
let minute = timing.count > 1 ? timing[1] : 0 /// .at(1, 2) => 01:02
let second = timing.count > 2 ? timing[2] : 0 /// .at(1, 2, 3) => 01:02:03
let nanosecond = timing.count > 3 ? timing[3]: 0 /// .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 { guard let time = Time(hour: hour, minute: minute, second: second, nanosecond: nanosecond) else {
return Schedule.never return Schedule.never
@ -385,13 +383,13 @@ extension Schedule {
} }
} }
/// Creates a schedule that invokes the task every specific weekday. /// Creates a schedule that executes the task every specific weekday.
public static func every(_ weekday: Weekday) -> EveryDateMiddleware { public static func every(_ weekday: Weekday) -> DateMiddleware {
let schedule = Schedule.make { () -> AnyIterator<Interval> in let schedule = Schedule.make { () -> AnyIterator<Date> in
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar(identifier: .gregorian)
let components = DateComponents(weekday: weekday.rawValue) let components = DateComponents(weekday: weekday.rawValue)
var date: Date! var date: Date!
let iterator = AnyIterator<Date> { return AnyIterator<Date> {
if date == nil { if date == nil {
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict) date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
} else { } else {
@ -399,30 +397,29 @@ extension Schedule {
} }
return date 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. /// Creates a schedule that executes the task every specific weekdays.
public static func every(_ weekdays: Weekday...) -> EveryDateMiddleware { public static func every(_ weekdays: Weekday...) -> DateMiddleware {
var schedule = every(weekdays[0]).schedule var schedule = every(weekdays[0]).schedule
if weekdays.count > 1 { if weekdays.count > 1 {
for i in 1..<weekdays.count { for i in 1..<weekdays.count {
schedule = schedule.merge(Schedule.every(weekdays[i]).schedule) 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. /// Creates a schedule that executes the task every specific day in the month.
public static func every(_ monthDay: MonthDay) -> EveryDateMiddleware { public static func every(_ monthDay: MonthDay) -> DateMiddleware {
let schedule = Schedule.make { () -> AnyIterator<Interval> in let schedule = Schedule.make { () -> AnyIterator<Date> in
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar(identifier: .gregorian)
let components = monthDay.asDateComponents() let components = monthDay.asDateComponents()
var date: Date! var date: Date!
let iterator = AnyIterator<Date> { return AnyIterator<Date> {
if date == nil { if date == nil {
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict) date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
} else { } else {
@ -430,19 +427,18 @@ extension Schedule {
} }
return date 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. /// Creates a schedule that executes the task every specific days in the months.
public static func every(_ mondays: MonthDay...) -> EveryDateMiddleware { public static func every(_ mondays: MonthDay...) -> DateMiddleware {
var schedule = every(mondays[0]).schedule var schedule = every(mondays[0]).schedule
if mondays.count > 1 { if mondays.count > 1 {
for i in 1..<mondays.count { for i in 1..<mondays.count {
schedule = schedule.merge(Schedule.every(mondays[i]).schedule) schedule = schedule.merge(Schedule.every(mondays[i]).schedule)
} }
} }
return EveryDateMiddleware(schedule: schedule) return DateMiddleware(schedule: schedule)
} }
} }

View File

@ -53,7 +53,7 @@ public class Task {
let now = Date() let now = Date()
self._timeline.activate = now self._timeline.activate = now
self._timeline.nextSchedule = now.addingInterval(interval) self._timeline.nextSchedule = now.adding(interval)
TaskCenter.shared.add(self, withTag: tag) TaskCenter.shared.add(self, withTag: tag)
self._timer?.resume() self._timer?.resume()
@ -75,7 +75,7 @@ public class Task {
return return
} }
_timeline.nextSchedule = _timeline.nextSchedule?.addingInterval(interval) _timeline.nextSchedule = _timeline.nextSchedule?.adding(interval)
_timer?.schedule(after: (_timeline.nextSchedule ?? Date.distantFuture).interval(since: now)) _timer?.schedule(after: (_timeline.nextSchedule ?? Date.distantFuture).interval(since: now))
let actions = _actions let actions = _actions

View File

@ -26,8 +26,9 @@ public struct Time {
/// ///
/// If any parameter is illegal, return nil. /// If any parameter is illegal, return nil.
/// ///
/// Time(hour: 25) == nil /// Time(hour: 11, minute: 11) => "11:11:00.000"
/// Time(hour: 1, minute: 61) == nil /// Time(hour: 25) => nil
/// Time(hour: 1, minute: 61) => nil
public init?(hour: Int, minute: Int = 0, second: Int = 0, nanosecond: Int = 0) { public init?(hour: Int, minute: Int = 0, second: Int = 0, nanosecond: Int = 0) {
guard (0...23).contains(hour) else { return nil } guard (0...23).contains(hour) else { return nil }
guard (0...59).contains(minute) else { return nil } guard (0...59).contains(minute) else { return nil }
@ -40,7 +41,13 @@ public struct Time {
self.nanosecond = nanosecond 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. /// 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("11:12:13.123") == Time(hour: 11, minute: 12, second: 13, nanosecond: 123000000)
/// ///
/// Time("-1.0") == nil /// Time("-1.0") == nil
public init?(timing: String) { ///
let fields = timing.split(separator: ":") /// Each of previous examples can have a period suffixes("am", "AM", "pm", "PM") separated by spaces.
if fields.count > 3 { return nil } public init?(_ string: String) {
var is12HourClock = false
var h = 0, m = 0, s = 0, ns = 0 for word in ["am", "pm", "AM", "PM"] {
if string.contains(word) {
guard let _h = Int(fields[0]) else { return nil } is12HourClock = true
h = _h break
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)))
} }
} }
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 { func asDateComponents() -> DateComponents {

View File

@ -26,6 +26,8 @@ extension WeakBox: Hashable {
} }
} }
/// An alternative to `NSHashTable`, since it is unavailable on linux.
struct WeakSet<T: AnyObject> { struct WeakSet<T: AnyObject> {
private var set = Set<WeakBox<T>>() private var set = Set<WeakBox<T>>()

View File

@ -1,5 +1,4 @@
import XCTest import XCTest
import ScheduleTests import ScheduleTests
var tests = [XCTestCaseEntry]() var tests = [XCTestCaseEntry]()

View File

@ -10,45 +10,88 @@ import XCTest
final class DateTimeTests: XCTestCase { 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(1.nanoseconds, Interval(nanoseconds: 1))
XCTAssertEqual(2.microseconds, Interval(nanoseconds: 2 * K.ns_per_us)) XCTAssertEqual(2.microseconds, Interval(nanoseconds: 2 * Constants.NSEC_PER_USEC))
XCTAssertEqual(3.milliseconds, Interval(nanoseconds: 3 * K.ns_per_ms)) XCTAssertEqual(3.milliseconds, Interval(nanoseconds: 3 * Constants.NSEC_PER_MSEC))
XCTAssertEqual(4.seconds, Interval(nanoseconds: 4 * K.ns_per_s)) XCTAssertEqual(4.seconds, Interval(nanoseconds: 4 * Constants.NSEC_PER_SEC))
XCTAssertEqual(5.1.minutes, Interval(nanoseconds: 5.1 * K.ns_per_m)) XCTAssertEqual(5.1.minutes, Interval(nanoseconds: 5.1 * Constants.NSEC_PER_M))
XCTAssertEqual(6.2.hours, Interval(nanoseconds: 6.2 * K.ns_per_h)) XCTAssertEqual(6.2.hours, Interval(nanoseconds: 6.2 * Constants.NSEC_PER_H))
XCTAssertEqual(7.3.days, Interval(nanoseconds: 7.3 * K.ns_per_d)) XCTAssertEqual(7.3.days, Interval(nanoseconds: 7.3 * Constants.NSEC_PER_D))
XCTAssertEqual(8.4.weeks, Interval(nanoseconds: 8.4 * K.ns_per_w)) 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) let t0 = Time(hour: -1, minute: -2, second: -3, nanosecond: -4)
XCTAssertNil(t0) XCTAssertNil(t0)
let t1 = Time(timing: "11:12:13.456") let t1 = Time("11:12:13.456")
XCTAssertNotNil(t1) XCTAssertNotNil(t1)
XCTAssertEqual(t1?.hour, 11) XCTAssertEqual(t1?.hour, 11)
XCTAssertEqual(t1?.minute, 12) XCTAssertEqual(t1?.minute, 12)
XCTAssertEqual(t1?.second, 13) 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() { func testPeriod() {
let dateComponents: Period = 1.year + 2.months + 3.days let p0 = (1.year + 2.months + 3.days).tidied(to: .day)
XCTAssertEqual(dateComponents.years, 1) XCTAssertEqual(p0.years, 1)
XCTAssertEqual(dateComponents.months, 2) XCTAssertEqual(p0.months, 2)
XCTAssertEqual(dateComponents.days, 3) 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)) XCTAssertEqual(MonthDay.april(3).asDateComponents(), DateComponents(month: 4, day: 3))
} }
static var allTests = [ static var allTests = [
("testInterval", testIntervalConvertible), ("testInterval", testInterval),
("testTimeConstructor", testTimeConstructor), ("testTime", testTime),
("testPeriodAnd", testPeriodAnd), ("testPeriod", testPeriod),
("testMonthDay", testMonthDay) ("testMonthday", testMonthday)
] ]
} }

View File

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

View File

@ -1,14 +1,16 @@
import XCTest import XCTest
@testable import Schedule @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 intervals = [1.second, 2.hours, 3.days, 4.weeks]
let s0 = Schedule.of(intervals[0], intervals[1], intervals[2], intervals[3]) 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) 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 d0 = Date() + intervals[0]
let d1 = d0 + intervals[1] let d1 = d0 + intervals[1]
@ -17,18 +19,18 @@ final class ScheduleTests: XCTestCase {
let s2 = Schedule.of(d0, d1, d2, d3) let s2 = Schedule.of(d0, d1, d2, d3)
let s3 = Schedule.from([d0, d1, d2, d3]) let s3 = Schedule.from([d0, d1, d2, d3])
XCTAssert(s2.makeIterator().isEqual(to: intervals, leeway: 0.001.seconds)) XCTAssert(s2.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
XCTAssert(s3.makeIterator().isEqual(to: intervals, leeway: 0.001.seconds)) XCTAssert(s3.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
} }
func testDates() { func testDates() {
let iterator = Schedule.of(1.days, 2.weeks).dates.makeIterator() let iterator = Schedule.of(1.days, 2.weeks).dates.makeIterator()
var next = iterator.next() var next = iterator.next()
XCTAssertNotNil(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() next = iterator.next()
XCTAssertNotNil(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() { func testNever() {
@ -40,19 +42,27 @@ final class ScheduleTests: XCTestCase {
let s1: [Interval] = [4.days, 5.weeks] let s1: [Interval] = [4.days, 5.weeks]
let s3 = Schedule.from(s0).concat(Schedule.from(s1)) let s3 = Schedule.from(s0).concat(Schedule.from(s1))
let s4 = Schedule.from(s0 + 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() { func testAt() {
let s = Schedule.at(Date() + 1.second) let s = Schedule.at(Date() + 1.second)
let next = s.makeIterator().next() let next = s.makeIterator().next()
XCTAssertNotNil(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 var count = 10
let s = Schedule.every(1.second).count(count) let s = Schedule.every(1.second).first(count)
let i = s.makeIterator() let i = s.makeIterator()
while count > 0 { while count > 0 {
XCTAssertNotNil(i.next()) XCTAssertNotNil(i.next())
@ -70,16 +80,20 @@ final class ScheduleTests: XCTestCase {
} }
} }
func testMerge() { func testNow() {
let intervals0: [Interval] = [1.second, 2.minutes, 1.hour] let s0 = Schedule.now
let intervals1: [Interval] = [2.seconds, 1.minutes, 1.seconds] let s1 = Schedule.of(Date())
let scheudle0 = Schedule.from(intervals0).merge(Schedule.from(intervals1)) XCTAssertTrue(s0.isAlmostEqual(to: s1, leeway: leeway))
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 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() { func testEveryPeriod() {
let s = Schedule.every(1.year).count(10) let s = Schedule.every(1.year).first(10)
var date = Date() var date = Date()
for i in s.dates { for i in s.dates {
XCTAssertEqual(i.dateComponents.year!, date.dateComponents.year! + 1) XCTAssertEqual(i.dateComponents.year!, date.dateComponents.year! + 1)
@ -90,7 +104,7 @@ final class ScheduleTests: XCTestCase {
} }
func testEveryWeekday() { 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 { for i in s.dates {
XCTAssertEqual(i.dateComponents.weekday, 6) XCTAssertEqual(i.dateComponents.weekday, 6)
XCTAssertEqual(i.dateComponents.hour, 11) XCTAssertEqual(i.dateComponents.hour, 11)
@ -98,7 +112,7 @@ final class ScheduleTests: XCTestCase {
} }
func testEveryMonthday() { 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 { for i in s.dates {
XCTAssertEqual(i.dateComponents.month, 4) XCTAssertEqual(i.dateComponents.month, 4)
XCTAssertEqual(i.dateComponents.day, 2) XCTAssertEqual(i.dateComponents.day, 2)
@ -107,14 +121,16 @@ final class ScheduleTests: XCTestCase {
} }
static var allTests = [ static var allTests = [
("testMakeSchedule", testMakeSchedule), ("testMake", testMake),
("testDates", testDates), ("testDates", testDates),
("testNever", testNever), ("testNever", testNever),
("testConcat", testConcat), ("testConcat", testConcat),
("testAt", testAt),
("testCount", testCount),
("testUntil", testUntil),
("testMerge", testMerge), ("testMerge", testMerge),
("testAt", testAt),
("testFirst", testFirst),
("testUntil", testUntil),
("testNow", testNow),
("testAfterAndRepeating", testAfterAndRepeating),
("testEveryPeriod", testEveryPeriod), ("testEveryPeriod", testEveryPeriod),
("testEveryWeekday", testEveryWeekday), ("testEveryWeekday", testEveryWeekday),
("testEveryMonthday", testEveryMonthday) ("testEveryMonthday", testEveryMonthday)

View File

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

View File

@ -3,7 +3,12 @@ import XCTest
#if !os(macOS) #if !os(macOS)
public func allTests() -> [XCTestCaseEntry] { public func allTests() -> [XCTestCaseEntry] {
return [ return [
testCase(ScheduleTests.allTests), testCase(DateTimeTests.allTests),
testCase(SchedulesTests.allTests),
testCase(TaskCenterTests.allTests),
testCase(ExtensionsTests.allTests),
testCase(BucketTests.allTests),
testCase(WeakSetTests.allTests),
] ]
} }
#endif #endif