Improve api, add tests
This commit is contained in:
parent
f48758d4f7
commit
792eabf3d4
58
README.md
58
README.md
|
@ -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"))
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
|
@ -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 */,
|
||||||
|
|
|
@ -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
|
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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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() })
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>>()
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
import ScheduleTests
|
import ScheduleTests
|
||||||
|
|
||||||
var tests = [XCTestCaseEntry]()
|
var tests = [XCTestCaseEntry]()
|
||||||
|
|
|
@ -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)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
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)
|
|
@ -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)
|
#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
|
||||||
|
|
Loading…
Reference in New Issue