commit
b45944b5c5
|
@ -1 +0,0 @@
|
||||||
4.2
|
|
|
@ -1,10 +1,10 @@
|
||||||
included:
|
included:
|
||||||
- Sources
|
- Sources
|
||||||
|
- Tests
|
||||||
|
|
||||||
disabled_rules:
|
disabled_rules:
|
||||||
- cyclomatic_complexity
|
- cyclomatic_complexity
|
||||||
- file_length
|
- file_length
|
||||||
- function_body_length
|
- function_body_length
|
||||||
- identifier_name
|
- identifier_name
|
||||||
|
- type_name
|
||||||
line_length: 200
|
|
21
.travis.yml
21
.travis.yml
|
@ -3,23 +3,25 @@ branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
language: objective-c
|
language: objective-c
|
||||||
osx_image: xcode10
|
osx_image: xcode10.2
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- PROJECT="Schedule.xcodeproj"
|
||||||
|
- SCHEME="Schedule-Package"
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: osx
|
- os: osx
|
||||||
env:
|
env:
|
||||||
- SCHEME="Schedule-iOS"
|
- SDK="iphonesimulator12.2"
|
||||||
- SDK="iphonesimulator12.0"
|
- DESTINATION="platform=iOS Simulator,name=iPhone 8,OS=12.2"
|
||||||
- DESTINATION="OS=12.0,name=iPhone X"
|
|
||||||
- os: osx
|
- os: osx
|
||||||
env:
|
env:
|
||||||
- SCHEME="Schedule-macOS"
|
- SDK="macosx10.14"
|
||||||
- SDK="macosx10.13"
|
|
||||||
- DESTINATION="arch=x86_64"
|
- DESTINATION="arch=x86_64"
|
||||||
- os: osx
|
- os: osx
|
||||||
env:
|
env:
|
||||||
- SCHEME="Schedule-tvOS"
|
|
||||||
- SDK="appletvsimulator12.0"
|
- SDK="appletvsimulator12.0"
|
||||||
- DESTINATION="OS=12.0,name=Apple TV 4K"
|
- DESTINATION="OS=12.0,name=Apple TV 4K"
|
||||||
- os: linux
|
- os: linux
|
||||||
|
@ -28,7 +30,7 @@ matrix:
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
|
- if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
|
||||||
gem install xcpretty --no-rdoc --no-ri --no-document --quiet;
|
gem install xcpretty;
|
||||||
fi
|
fi
|
||||||
- if [[ $TRAVIS_OS_NAME == 'linux' ]]; then
|
- if [[ $TRAVIS_OS_NAME == 'linux' ]]; then
|
||||||
eval "$(curl -sL https://swiftenv.fuller.li/install.sh)";
|
eval "$(curl -sL https://swiftenv.fuller.li/install.sh)";
|
||||||
|
@ -36,10 +38,9 @@ before_install:
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
|
- if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
|
||||||
xcodebuild clean build test -project Schedule.xcodeproj -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -enableCodeCoverage YES | xcpretty;
|
xcodebuild clean build test -project "$PROJECT" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -enableCodeCoverage YES | xcpretty;
|
||||||
fi
|
fi
|
||||||
- if [[ $TRAVIS_OS_NAME == 'linux' ]]; then
|
- if [[ $TRAVIS_OS_NAME == 'linux' ]]; then
|
||||||
swift build;
|
|
||||||
swift test;
|
swift test;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
// swift-tools-version:4.2
|
// swift-tools-version:5.0
|
||||||
|
|
||||||
import PackageDescription
|
import PackageDescription
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "Schedule",
|
name: "Schedule",
|
||||||
|
platforms: [
|
||||||
|
.macOS(.v10_12),
|
||||||
|
.iOS(.v10),
|
||||||
|
.tvOS(.v10),
|
||||||
|
.watchOS(.v3)
|
||||||
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(name: "Schedule", targets: ["Schedule"]),
|
.library(name: "Schedule", targets: ["Schedule"])
|
||||||
.executable(name: "ScheduleDemo", targets: ["ScheduleDemo"])
|
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(name: "Schedule"),
|
.target(name: "Schedule"),
|
||||||
.testTarget(name: "ScheduleTests", dependencies: ["Schedule"]),
|
.testTarget(name: "ScheduleTests", dependencies: ["Schedule"])
|
||||||
.target(name: "ScheduleDemo", dependencies: ["Schedule"])
|
],
|
||||||
|
swiftLanguageVersions: [
|
||||||
|
.v5
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
260
README.md
260
README.md
|
@ -1,35 +1,31 @@
|
||||||
# Schedule([简体中文](README.zh_cn.md))
|
# Schedule([简体中文](README.zh_cn.md))
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
|
||||||
[](https://travis-ci.org/jianstm/Schedule)
|
|
||||||
[](https://codecov.io/gh/jianstm/Schedule)
|
|
||||||
<a href="https://github.com/jianstm/Schedule/releases">
|
<a href="https://github.com/jianstm/Schedule/releases">
|
||||||
<img src="https://img.shields.io/github/tag/jianstm/Schedule.svg">
|
<img src="https://img.shields.io/cocoapods/v/Schedule.svg">
|
||||||
</a>
|
</a>
|
||||||
|
<img src="https://img.shields.io/travis/jianstm/Schedule.svg">
|
||||||
|
<img src="https://img.shields.io/codecov/c/github/jianstm/Schedule.svg">
|
||||||
<img src="https://img.shields.io/badge/support-CocoaPods%20%7C%20Carthage%20%7C%20SwiftPM-brightgreen.svg">
|
<img src="https://img.shields.io/badge/support-CocoaPods%20%7C%20Carthage%20%7C%20SwiftPM-brightgreen.svg">
|
||||||
<img src="https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20tvOS%20%7C%20Linux-lightgrey.svg">
|
<img src="https://img.shields.io/cocoapods/p/Schedule.svg">
|
||||||
|
<img src="https://img.shields.io/github/license/jianstm/Schedule.svg">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Schedule is a lightweight timed tasks scheduler for Swift. It allows you run timed tasks using an incredibly human-friendly syntax.
|
Schedule is a timing tasks scheduler written in Swift. It allows you run timing tasks with elegant and intuitive syntax.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://raw.githubusercontent.com/jianstm/Schedule/master/assets/demo.png" width="700">
|
<img src="assets/demo.png" width="700">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- [x] Variety of Scheduling Rules
|
- [x] Elegant and intuitive API
|
||||||
- [x] Suspend, Resume, Cancel
|
- [x] Rich preset rules
|
||||||
- [x] Reschedule
|
- [x] Powerful management mechanism
|
||||||
- [x] Tag-based Task Management
|
- [x] Detailed execution history
|
||||||
- [x] Child-action Add/Remove
|
- [x] Thread safe
|
||||||
- [x] Natural Language Parse
|
- [x] Complete documentation
|
||||||
- [x] Atomic Operation
|
- [x] ~100%+ test coverage
|
||||||
- [x] Full Control Over Life Cycle
|
|
||||||
- [x] 95%+ Test Coverage
|
|
||||||
- [x] Complete Documentation(All Public Types & Methods)
|
|
||||||
- [x] Linux Support(Tested on Ubuntu 16.04)
|
|
||||||
|
|
||||||
### Why You Should Use Schedule
|
### Why You Should Use Schedule
|
||||||
|
|
||||||
|
@ -37,29 +33,26 @@ Schedule is a lightweight timed tasks scheduler for Swift. It allows you run tim
|
||||||
| --- | :---: | :---: | :---: |
|
| --- | :---: | :---: | :---: |
|
||||||
| ⏰ Interval-based Schedule | ✓ | ✓ | ✓ |
|
| ⏰ Interval-based Schedule | ✓ | ✓ | ✓ |
|
||||||
| 📆 Date-based Schedule | ✓ | | ✓ |
|
| 📆 Date-based Schedule | ✓ | | ✓ |
|
||||||
| 🌈 Mixing Rules Schedule | | | ✓ |
|
| 🌈 Combined Plan Schedule | | | ✓ |
|
||||||
|
| 🗣️ Natural Language Parse | | | ✓ |
|
||||||
|
| 🏷 Batch Task Management | | | ✓ |
|
||||||
|
| 📝 Execution Record | | | ✓ |
|
||||||
|
| 🎡 Plan Reset | | ✓ | ✓ |
|
||||||
| 🚦 Suspend, Resume, Cancel | | ✓ | ✓ |
|
| 🚦 Suspend, Resume, Cancel | | ✓ | ✓ |
|
||||||
| 🎡 Reschedule | | ✓ | ✓ |
|
| 🍰 Child-action | | | ✓ |
|
||||||
| 🏷 Tag-based Task Management | | | ✓ |
|
|
||||||
| 🍰 Child-action Add/Remove | | | ✓ |
|
|
||||||
| 📝 Natural Language Parse | | | ✓ |
|
|
||||||
| 🚔 Atomic Operation | | | ✓ |
|
|
||||||
| 🕕 Lifecycly Bind | | | ✓ |
|
|
||||||
| 🚀 Realtime Timeline Inspect | | | ✓ |
|
|
||||||
| 🎯 Lifetime Specify | | | ✓ |
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Overview
|
### Overview
|
||||||
|
|
||||||
Scheduling a task has never been so simple and intuitive, all you have to do is:
|
Scheduling a task has never been so elegant and intuitive, all you have to do is:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
// 1. define your plan:
|
// 1. define your plan:
|
||||||
let plan = Plan.after(3.seconds)
|
let plan = Plan.after(3.seconds)
|
||||||
|
|
||||||
// 2. do your task:
|
// 2. do your task:
|
||||||
plan.do {
|
let task = plan.do {
|
||||||
print("3 seconds passed!")
|
print("3 seconds passed!")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -68,58 +61,59 @@ plan.do {
|
||||||
|
|
||||||
#### Interval-based Schedule
|
#### Interval-based Schedule
|
||||||
|
|
||||||
Schedule uses a self-defined type `Interval` to configure timed tasks, so you don't have to worry about extensions of built-in type polluting your namespace. The smooth constructors make the configuration like a comfortable conversation:
|
The running mechanism of Schedule is based on `Plan`, and `Plan` is actually a sequence of `Interval`.
|
||||||
|
|
||||||
|
Schedule makes `Plan` definitions more elegant and intuitive by extending `Int` and `Double`. Also, because `Interval` is a built-in type of Schedule, you don't have to worry about it being polluting your namespace.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
Plan.every(1.second).do { }
|
let t1 = Plan.every(1.second).do { }
|
||||||
|
|
||||||
Plan.after(1.hour, repeating: 1.minute).do { }
|
let t2 = Plan.after(1.hour, repeating: 1.minute).do { }
|
||||||
|
|
||||||
Plan.of(1.second, 2.minutes, 3.hours).do { }
|
let t3 = Plan.of(1.second, 2.minutes, 3.hours).do { }
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Date-based Schedule
|
#### Date-based Schedule
|
||||||
|
|
||||||
Configuring date-based timing tasks is the same, Schedule defines all the commonly used date time types, trying to make your writing experience intuitive and smooth::
|
Configuring date-based `Plan` is the same, with the expressive Swift syntax, Schedule makes your code look like a fluent conversation.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
Plan.at(when).do { }
|
let t1 = Plan.at(date).do { }
|
||||||
|
|
||||||
Plan.every(.monday, .tuesday).at("9:00:00").do { }
|
let t2 = Plan.every(.monday, .tuesday).at("9:00:00").do { }
|
||||||
|
|
||||||
Plan.every(.september(30)).at(10, 30).do { }
|
let t3 = Plan.every(.september(30)).at(10, 30).do { }
|
||||||
|
|
||||||
Plan.every("one month and ten days").do { }
|
let t4 = Plan.every("one month and ten days").do { }
|
||||||
|
|
||||||
Plan.of(date0, date1, date2).do { }
|
let t5 = Plan.of(date0, date1, date2).do { }
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Natural Language Parse
|
#### Natural Language Parse
|
||||||
|
|
||||||
In addition, Schedule also supports basic natural language parsing, which greatly improves the readability of your code:
|
In addition, Schedule also supports simple natural language parsing.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
Plan.every("one hour and ten minutes").do { }
|
let t1 = Plan.every("one hour and ten minutes").do { }
|
||||||
|
|
||||||
Plan.every("1 hour, 5 minutes and 10 seconds").do { }
|
let t2 = Plan.every("1 hour, 5 minutes and 10 seconds").do { }
|
||||||
|
|
||||||
Plan.every(.friday).at("9:00 pm").do { }
|
let t3 = Plan.every(.firday).at("9:00 pm").do { }
|
||||||
|
|
||||||
// Extensions
|
|
||||||
Period.registerQuantifier("many", for: 100 * 1000)
|
Period.registerQuantifier("many", for: 100 * 1000)
|
||||||
Plan.every("many days").do { }
|
let t4 = Plan.every("many days").do { }
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Mixing Rules Schedule
|
#### Combined Plan Schedule
|
||||||
|
|
||||||
Schedule provides several collection operators, this means you can use them to customize your awesome rules:
|
Schedule provides several basic collection operators, which means you can use them to customize your own powerful plans.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
/// Concat
|
/// Concat
|
||||||
let p0 = Plan.at(birthdate)
|
let p0 = Plan.at(birthdate)
|
||||||
let p1 = Plan.every(1.year)
|
let p1 = Plan.every(1.year)
|
||||||
let birthday = p0.concat.p1
|
let birthday = p0.concat.p1
|
||||||
birthday.do {
|
let t1 = birthday.do {
|
||||||
print("Happy birthday")
|
print("Happy birthday")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +121,7 @@ birthday.do {
|
||||||
let p3 = Plan.every(.january(1)).at("8:00")
|
let p3 = Plan.every(.january(1)).at("8:00")
|
||||||
let p4 = Plan.every(.october(1)).at("9:00 AM")
|
let p4 = Plan.every(.october(1)).at("9:00 AM")
|
||||||
let holiday = p3.merge(p4)
|
let holiday = p3.merge(p4)
|
||||||
holiday.do {
|
let t2 = holiday.do {
|
||||||
print("Happy holiday")
|
print("Happy holiday")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,34 +134,11 @@ let p7 = P.every(.monday).at(11, 12)
|
||||||
let p8 = p7.until(date)
|
let p8 = p7.until(date)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Creation
|
### Management
|
||||||
|
|
||||||
#### Parasitism
|
|
||||||
|
|
||||||
Schedule provides a parasitic mechanism, that allows you to handle one of the most common scenarios in a more elegant way:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
Plan.every(1.second).do(host: self) {
|
|
||||||
// do something, and cancel the task when host is deallocated.
|
|
||||||
// this's very useful when you want to bind a task's lifetime to a controller.
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### RunLoop
|
|
||||||
|
|
||||||
The task will be executed on the current thread by default, and its implementation is based on RunLoop. So you need to ensure that the current thread has a RunLoop available. If the task is created on a child thread, you may need to run `RunLoop.current.run()`.
|
|
||||||
|
|
||||||
By default, Task will be added to `.common` mode, you can specify another mode when creating a task:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
Plan.every(1.second).do(mode: .default) {
|
|
||||||
print("on default mode...")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### DispatchQueue
|
#### DispatchQueue
|
||||||
|
|
||||||
You can use `queue` to specify which DispatchQueue the task will be dispatched to. In this case, the execution of the task is no longer dependent on RunLoop, you can use it safely on a child thread:
|
When calling `plan.do` to dispatch a timing task, you can use `queue` to specify which `DispatchQueue` the task will be dispatched to when the time is up. This operation does not rely on `RunLoop` like `Timer`, so you can call it on any thread.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
Plan.every(1.second).do(queue: .global()) {
|
Plan.every(1.second).do(queue: .global()) {
|
||||||
|
@ -175,29 +146,77 @@ Plan.every(1.second).do(queue: .global()) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Management
|
|
||||||
|
|
||||||
In schedule, every newly created task is automatically held by an internal global variable and will not be released until you cancel it actively. So you don't have to add variables to your controller and write nonsense like `weak var timer: Timer`, `self.timer = timer`:
|
#### RunLoop
|
||||||
|
|
||||||
|
If `queue` is not specified, Schedule will use `RunLoop` to dispatch the task, at which point the task will execute on the current thread. **Please note**, like `Timer`, which is also based on `RunLoop`, you need to ensure that the current thread has an **available** `RunLoop`. By default, the task will be added to `.common` mode, you can specify another mode when creating the task.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let task = Plan.every(1.second).do(mode: .default) {
|
||||||
|
print("on default mode...")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Timeline
|
||||||
|
|
||||||
|
You can observe the execution record of the task in real time using the following properties.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
task.creationDate
|
||||||
|
|
||||||
|
task.executionHistory
|
||||||
|
|
||||||
|
task.firstExecutionDate
|
||||||
|
task.lastExecutionDate
|
||||||
|
|
||||||
|
task.estimatedNextExecutionDate
|
||||||
|
```
|
||||||
|
|
||||||
|
#### TaskCenter & Tag
|
||||||
|
|
||||||
|
Tasks are automatically added to `TaskCenter.default` by default,you can organize them using tags and task center.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let plan = Plan.every(1.day)
|
||||||
|
let task0 = plan.do(queue: myTaskQueue) { }
|
||||||
|
let task1 = plan.do(queue: myTaskQueue) { }
|
||||||
|
|
||||||
|
TaskCenter.default.addTags(["database", "log"], to: task1)
|
||||||
|
TaskCenter.default.removeTag("log", from: task1)
|
||||||
|
|
||||||
|
TaskCenter.default.suspend(byTag: "log")
|
||||||
|
TaskCenter.default.resume(byTag: "log")
|
||||||
|
TaskCenter.default.cancel(byTag: "log")
|
||||||
|
|
||||||
|
TaskCenter.default.clear()
|
||||||
|
|
||||||
|
let myCenter = TaskCenter()
|
||||||
|
myCenter.add(task0)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Suspend,Resume, Cancel
|
||||||
|
|
||||||
|
You can `suspend`, `resume`, `cancel` a task.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let task = Plan.every(1.minute).do { }
|
let task = Plan.every(1.minute).do { }
|
||||||
|
|
||||||
// will increase task's suspensions
|
// will increase task's suspensionCount
|
||||||
task.suspend()
|
task.suspend()
|
||||||
|
|
||||||
// will decrease task's suspensions,
|
// will decrease task's suspensionCount,
|
||||||
// but don't worry about excessive resumptions, I will handle these for you~
|
// but don't worry about excessive resumptions, I will handle these for you~
|
||||||
task.resume()
|
task.resume()
|
||||||
|
|
||||||
// cancel task, this will remove task from the internal holder,
|
// will clear task's suspensionCount
|
||||||
// in other words, will reduce task's reference count,
|
// a canceled task can't do anything, event if it is set to a new plan.
|
||||||
// if there are no other holders, task will be released.
|
|
||||||
task.cancel()
|
task.cancel()
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Action
|
#### Action
|
||||||
|
|
||||||
You can add more actions to a task and delete them at any time you want:
|
You can add more actions to a task and remove them at any time you want:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let dailyTask = Plan.every(1.day)
|
let dailyTask = Plan.every(1.day)
|
||||||
|
@ -213,55 +232,6 @@ let key = dailyTask.addAction {
|
||||||
dailyTask.removeAction(byKey: key)
|
dailyTask.removeAction(byKey: key)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Tag
|
|
||||||
|
|
||||||
You can organize tasks with tags, and use queue to specify to where the task should be dispatched:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
let s = Plan.every(1.day)
|
|
||||||
let task0 = s.do(queue: myTaskQueue) { }
|
|
||||||
let task1 = s.do(queue: myTaskQueue) { }
|
|
||||||
|
|
||||||
task0.addTag("database")
|
|
||||||
task1.addTags("database", "log")
|
|
||||||
task1.removeTag("log")
|
|
||||||
|
|
||||||
Task.suspend(byTag: "log")
|
|
||||||
Task.resume(byTag: "log")
|
|
||||||
Task.cancel(byTag: "log")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Timeline
|
|
||||||
|
|
||||||
You can inspect the timeline of a task in real time:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
let timeline = task.timeline
|
|
||||||
print(timeline.initialization)
|
|
||||||
print(timeline.firstExecution)
|
|
||||||
print(timeline.lastExecution)
|
|
||||||
print(timeline.estimatedNextExecution)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Lifetime
|
|
||||||
|
|
||||||
And specify the lifetime of task:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
// will be cancelled after 10 hours.
|
|
||||||
task.setLifetime(10.hours)
|
|
||||||
|
|
||||||
// will add 1 hour to tasks lifetime
|
|
||||||
task.addLifetime(1.hour)
|
|
||||||
|
|
||||||
task.restOfLifetime == 11.hours
|
|
||||||
```
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
- iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
|
|
||||||
- Linux(Tested on Ubuntu 16.04)
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### CocoaPods
|
### CocoaPods
|
||||||
|
@ -271,44 +241,44 @@ task.restOfLifetime == 11.hours
|
||||||
use_frameworks!
|
use_frameworks!
|
||||||
|
|
||||||
target 'YOUR_TARGET_NAME' do
|
target 'YOUR_TARGET_NAME' do
|
||||||
pod 'Schedule', '~> 1.0'
|
pod 'Schedule', '~> 2.0'
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
### Carthage
|
### Carthage
|
||||||
|
|
||||||
```ruby
|
```
|
||||||
github "jianstm/Schedule" ~> 1.0
|
github "jianstm/Schedule" ~> 2.0
|
||||||
```
|
```
|
||||||
|
|
||||||
### Swift Package Manager
|
### Swift Package Manager
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/jianstm/Schedule", .upToNextMajor(from: "1.0.0"))
|
.package(
|
||||||
|
url: "https://github.com/jianstm/Schedule", .upToNextMajor(from: "2.0.0")
|
||||||
|
)
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Acknowledgement
|
|
||||||
|
|
||||||
Inspired by Dan Bader's [schedule](https://github.com/dbader/schedule)! Syntax design is heavily influenced by Ruby!
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Like **Schedule**? Thank you so much! At the same time, I need your help:
|
Like **Schedule**? Thanks!!!
|
||||||
|
|
||||||
|
At the same time, I need your help~
|
||||||
|
|
||||||
### Finding Bugs
|
### Finding Bugs
|
||||||
|
|
||||||
Schedule is just getting started, it is difficult to say how far the project is from bug free. If you could help the Schedule find or fix bugs that haven't been discovered yet, I would appreciate it!
|
Schedule is just getting started. If you could help the Schedule find or fix potential bugs, I would be grateful!
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
|
||||||
Any awesome ideas? Feel free to open an issue or submit your pull request directly!
|
Have some awesome ideas? Feel free to open an issue or submit your pull request directly!
|
||||||
|
|
||||||
### Documentation improvements.
|
### Documentation improvements.
|
||||||
|
|
||||||
Improvements to README and documentation are welcome at all times, whether typos or my lame English. For users, the documentation is sometimes much more important than the specific code implementation.
|
Improvements to README and documentation are welcome at all times, whether typos or my lame English, 🤣.
|
||||||
|
|
||||||
### Share
|
## Acknowledgement
|
||||||
|
|
||||||
The more users the project has, the more robust the project will become, so, star! fork! and tell your friends!
|
Inspired by Dan Bader's [schedule](https://github.com/dbader/schedule)!
|
244
README.zh_cn.md
244
README.zh_cn.md
|
@ -1,124 +1,119 @@
|
||||||
# Schedule
|
# Schedule
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
|
||||||
[](https://travis-ci.org/jianstm/Schedule)
|
|
||||||
[](https://codecov.io/gh/jianstm/Schedule)
|
|
||||||
<a href="https://github.com/jianstm/Schedule/releases">
|
<a href="https://github.com/jianstm/Schedule/releases">
|
||||||
<img src="https://img.shields.io/github/tag/jianstm/Schedule.svg">
|
<img src="https://img.shields.io/cocoapods/v/Schedule.svg">
|
||||||
</a>
|
</a>
|
||||||
|
<img src="https://img.shields.io/travis/jianstm/Schedule.svg">
|
||||||
|
<img src="https://img.shields.io/codecov/c/github/jianstm/Schedule.svg">
|
||||||
<img src="https://img.shields.io/badge/support-CocoaPods%20%7C%20Carthage%20%7C%20SwiftPM-brightgreen.svg">
|
<img src="https://img.shields.io/badge/support-CocoaPods%20%7C%20Carthage%20%7C%20SwiftPM-brightgreen.svg">
|
||||||
<img src="https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20tvOS%20%7C%20Linux-lightgrey.svg">
|
<img src="https://img.shields.io/cocoapods/p/Schedule.svg">
|
||||||
|
<img src="https://img.shields.io/github/license/jianstm/Schedule.svg">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Schedule 是一个轻量级的调度框架,它能让你用难以置信的友好语法执行定时任务。
|
Schedule 是一个用 Swift 编写的定时任务调度器,它能让你用优雅、直观的语法执行定时任务。
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://raw.githubusercontent.com/jianstm/Schedule/master/assets/demo.png" width="700">
|
<img src="assets/demo.png" width="700">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
|
|
||||||
- [x] 多种调度规则
|
- [x] 优雅,直观的 API
|
||||||
- [x] 暂停、继续、取消
|
- [x] 丰富的预置规则
|
||||||
- [x] 重置调度规则
|
- [x] 强大的管理机制
|
||||||
- [x] 基于 tag 的任务管理
|
- [x] 细致的执行记录
|
||||||
- [x] 添加、移除子动作
|
- [x] 线程安全
|
||||||
- [x] 自然语言解析
|
- [x] 完整的文档
|
||||||
- [x] 原子操作
|
- [x] ~100% 的测试覆盖
|
||||||
- [x] 对生命周期的完全控制
|
|
||||||
- [x] 95%+ 测试覆盖
|
|
||||||
- [x] 完善的文档(所有 public 类型和方法)
|
|
||||||
- [x] 支持 Linux(通过 Ubuntu 16.04 测试)
|
|
||||||
|
|
||||||
### 为什么你该用 Schedule
|
### 为什么你该使用 Schedule,而不是……
|
||||||
|
|
||||||
| 功能 | Timer | DispatchSourceTimer | Schedule |
|
| 功能 | Timer | DispatchSourceTimer | Schedule |
|
||||||
| --- | :---: | :---: | :---: |
|
| --- | :---: | :---: | :---: |
|
||||||
| ⏰ 基于时间间隔调度 | ✓ | ✓ | ✓ |
|
| ⏰ 基于时间间隔调度 | ✓ | ✓ | ✓ |
|
||||||
| 📆 基于日期调度 | ✓ | | ✓ |
|
| 📆 基于日期调度 | ✓ | | ✓ |
|
||||||
| 🌈 自定义规则调度 | | | ✓ |
|
| 🌈 组合计划调度 | | | ✓ |
|
||||||
|
| 🗣️ 自然语言解析 | | | ✓ |
|
||||||
|
| 🏷 批任务管理 | | | ✓ |
|
||||||
|
| 📝 执行记录 | | | ✓ |
|
||||||
|
| 🎡 规则重置 | | ✓ | ✓ |
|
||||||
| 🚦 暂停、继续、取消 | | ✓ | ✓ |
|
| 🚦 暂停、继续、取消 | | ✓ | ✓ |
|
||||||
| 🎡 重置规则 | | ✓ | ✓ |
|
| 🍰 子动作 | | | ✓ |
|
||||||
| 🏷 基于 tag 的任务管理 | | | ✓ |
|
|
||||||
| 🍰 添加、移除子动作 | | | ✓ |
|
|
||||||
| 📝 自然语言解析 | | | ✓ |
|
|
||||||
| 🚔 原子操作 | | | ✓ |
|
|
||||||
| 🕕 生命周期绑定 | | | ✓ |
|
|
||||||
| 🚀 实时观察时间线 | | | ✓ |
|
|
||||||
| 🏌 寿命设置 | | | ✓ |
|
|
||||||
|
|
||||||
## 用法
|
## 用法
|
||||||
|
|
||||||
### 一瞥
|
### 一瞥
|
||||||
|
|
||||||
调度一个定时任务从未如此简单直观,你要做的只有:
|
调度一个定时任务从未如此优雅、直观,你只需要:
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
// 1. 定义你的计划:
|
// 1. 定义你的计划:
|
||||||
let plan = Plan.after(3.seconds)
|
let plan = Plan.after(3.seconds)
|
||||||
|
|
||||||
// 2. 执行你的任务:
|
// 2. 执行你的任务:
|
||||||
plan.do {
|
let task = plan.do {
|
||||||
print("3 seconds passed!")
|
print("3 seconds passed!")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 规则
|
### 计划
|
||||||
|
|
||||||
#### 基于时间间隔调度
|
#### 基于时间间隔调度
|
||||||
|
|
||||||
Schedule 使用自定义的 `Interval` 类型来配置定时任务,你不必担心对内置类型的扩展会污染你的命名空间。流畅的构造方法让配置像一场舒服的对话:
|
Schedule 的机制基于 `Plan`,而 `Plan` 的本质是一系列 `Interval`。
|
||||||
|
|
||||||
|
Schedule 通过扩展 `Int` 和 `Double` 让 `Plan` 的定义更加优雅、直观。同时,因为 `Interval` 是 Schedule 的内置类型,所以你不用担心这会对你的命名空间产生污染。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
Plan.every(1.second).do { }
|
let t1 = Plan.every(1.second).do { }
|
||||||
|
|
||||||
Plan.after(1.hour, repeating: 1.minute).do { }
|
let t2 = Plan.after(1.hour, repeating: 1.minute).do { }
|
||||||
|
|
||||||
Plan.of(1.second, 2.minutes, 3.hours).do { }
|
let t3 = Plan.of(1.second, 2.minutes, 3.hours).do { }
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 基于日期调度
|
#### 基于日期调度
|
||||||
|
|
||||||
配置基于日期的调度同样如此,Schedule 定义了所有常用的日期类型,尽力让你的书写直观、流畅:
|
定制基于日期的 `Plan` 同样如此,配合富有表现力的 Swift 语法,Schedule 让你的代码看起来就像一场流畅的对话。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
Plan.at(when).do { }
|
let t1 = Plan.at(date).do { }
|
||||||
|
|
||||||
Plan.every(.monday, .tuesday).at("9:00:00").do { }
|
let t2 = Plan.every(.monday, .tuesday).at("9:00:00").do { }
|
||||||
|
|
||||||
Plan.every(.september(30)).at(10, 30).do { }
|
let t3 = Plan.every(.september(30)).at(10, 30).do { }
|
||||||
|
|
||||||
Plan.every("one month and ten days").do { }
|
let t4 = Plan.every("one month and ten days").do { }
|
||||||
|
|
||||||
Plan.of(date0, date1, date2).do { }
|
let t5 = Plan.of(date0, date1, date2).do { }
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 自然语言解析
|
#### 自然语言解析
|
||||||
|
|
||||||
除此之外,Schedule 还支持基础的自然语言解析,这大大增强了你的代码的可读性:
|
除此之外,Schedule 还支持简单的自然语言解析。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
Plan.every("one hour and ten minutes").do { }
|
let t1 = Plan.every("one hour and ten minutes").do { }
|
||||||
|
|
||||||
Plan.every("1 hour, 5 minutes and 10 seconds").do { }
|
let t2 = Plan.every("1 hour, 5 minutes and 10 seconds").do { }
|
||||||
|
|
||||||
Plan.every(.firday).at("9:00 pm").do { }
|
let t3 = Plan.every(.firday).at("9:00 pm").do { }
|
||||||
|
|
||||||
Period.registerQuantifier("many", for: 100 * 1000)
|
Period.registerQuantifier("many", for: 100 * 1000)
|
||||||
Plan.every("many days").do { }
|
let t4 = Plan.every("many days").do { }
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 自定义规则调度
|
#### 组合计划调度
|
||||||
|
|
||||||
Schedule 还提供了几个简单的集合操作符,这意味着你可以使用它们定制属于你的强大规则:
|
Schedule 提供了几个基本的集合操作符,这意味着,你可以使用它们自由组合,定制属于你的强大规则。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
/// Concat
|
/// Concat
|
||||||
let p0 = Plan.at(birthdate)
|
let p0 = Plan.at(birthdate)
|
||||||
let p1 = Plan.every(1.year)
|
let p1 = Plan.every(1.year)
|
||||||
let birthday = p0.concat.p1
|
let birthday = p0.concat.p1
|
||||||
birthday.do {
|
let t1 = birthday.do {
|
||||||
print("Happy birthday")
|
print("Happy birthday")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +121,7 @@ birthday.do {
|
||||||
let p3 = Plan.every(.january(1)).at("8:00")
|
let p3 = Plan.every(.january(1)).at("8:00")
|
||||||
let p4 = Plan.every(.october(1)).at("9:00 AM")
|
let p4 = Plan.every(.october(1)).at("9:00 AM")
|
||||||
let holiday = p3.merge(p4)
|
let holiday = p3.merge(p4)
|
||||||
holiday.do {
|
let t2 = holiday.do {
|
||||||
print("Happy holiday")
|
print("Happy holiday")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,42 +134,68 @@ let p7 = P.every(.monday).at(11, 12)
|
||||||
let p8 = p7.until(date)
|
let p8 = p7.until(date)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 创建
|
### 管理
|
||||||
|
|
||||||
#### 寄生
|
#### DispatchQueue
|
||||||
|
|
||||||
Schedule 提供了一种寄生机制,它让你可以以一种更优雅的方式处理 task 的生命周期:
|
调用 `plan.do` 来调度定时任务时,你可以使用 `queue` 来指定当时间到时,task 会被派发到哪个 `DispatchQueue` 上。这个操作不像 `Timer` 那样依赖 `RunLoop`,所以你可以在任意线程上使用它。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
Plan.every(1.second).do(host: self) {
|
let task = Plan.every(1.second).do(queue: .global()) {
|
||||||
// task 会在 host 被 deallocated 后自动被 cancel
|
print("On a globle queue")
|
||||||
// 这在你想要把一个 task 的生命周期绑定到控制器上时非常有用
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### RunLoop
|
#### RunLoop
|
||||||
|
|
||||||
Task 默认会在当前线程上执行,它的实现依赖于 RunLoop,所以你需要保证当前线程有一个可用的 RunLoop。如果 task 的创建在子线程上,你可能需要执行 `RunLoop.current.run()`。默认情况下, task 会被添加到 `.common` mode 上,你可以在创建 task 时指定其它 mode:
|
如果没有指定 `queue`,Schedule 会使用 `RunLoop` 来调度 task,这时,task 会在当前线程上执行。**要注意**,和同样基于 `RunLoop` 的 `Timer` 一样,你需要保证当前线程有一个**可用**的 `RunLoop`。默认情况下, task 会被添加到 `.common` mode 上,你可以在创建 task 时指定其它 mode。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
Plan.every(1.second).do(mode: .default) {
|
let task = Plan.every(1.second).do(mode: .default) {
|
||||||
print("on default mode...")
|
print("on default mode...")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### DispatchQueue
|
#### Timeline
|
||||||
|
|
||||||
你也可以使用 queue 来指定 task 会被派发到哪个 DispatchQueue 上,这时,task 的执行不再依赖于 RunLoop,意味着你可以放心地子线程上使用:
|
你可以使用以下属性实时地观察 task 的执行记录。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
Plan.every(1.second).do(queue: .global()) {
|
task.creationDate
|
||||||
print("On a globle queue")
|
|
||||||
}
|
task.executionHistory
|
||||||
|
|
||||||
|
task.firstExecutionDate
|
||||||
|
task.lastExecutionDate
|
||||||
|
|
||||||
|
task.estimatedNextExecutionDate
|
||||||
```
|
```
|
||||||
|
|
||||||
### 管理
|
#### TaskCenter 和 Tag
|
||||||
|
|
||||||
在 Schedule 里,每一个新创建的 task 都会被一个内部的全局变量自动持有,除非你显式地 cancel 它们,否则它们不会被提前释放。也就是说你不用再在控制器里写那些诸如 `weak var timer: Timer`, `self.timer = timer` 之类的啰唆代码了:
|
task 默认会被自动添加到 `TaskCenter.default` 上,你可以使用 tag 配合 taskCenter 来组织 tasks:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let plan = Plan.every(1.day)
|
||||||
|
let task0 = plan.do(queue: myTaskQueue) { }
|
||||||
|
let task1 = plan.do(queue: myTaskQueue) { }
|
||||||
|
|
||||||
|
TaskCenter.default.addTags(["database", "log"], to: task1)
|
||||||
|
TaskCenter.default.removeTag("log", from: task1)
|
||||||
|
|
||||||
|
TaskCenter.default.suspend(byTag: "log")
|
||||||
|
TaskCenter.default.resume(byTag: "log")
|
||||||
|
TaskCenter.default.cancel(byTag: "log")
|
||||||
|
|
||||||
|
TaskCenter.default.removeAll()
|
||||||
|
|
||||||
|
let myCenter = TaskCenter()
|
||||||
|
myCenter.add(task0)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Suspend、Resume、Cancel
|
||||||
|
|
||||||
|
你可以 suspend,resume,cancel 一个 task。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let task = Plan.every(1.minute).do { }
|
let task = Plan.every(1.minute).do { }
|
||||||
|
@ -182,19 +203,22 @@ let task = Plan.every(1.minute).do { }
|
||||||
// 会增加 task 的暂停计数
|
// 会增加 task 的暂停计数
|
||||||
task.suspend()
|
task.suspend()
|
||||||
|
|
||||||
// 会减少 task 的暂停计数,不过不用担心过度减少,
|
task.suspensions == 1
|
||||||
// 我会帮你处理好这些~
|
|
||||||
|
// 会减少 task 的暂停计数
|
||||||
|
// 不过不用担心过度减少,我会帮你处理好这些~
|
||||||
task.resume()
|
task.resume()
|
||||||
|
|
||||||
// 取消任务,这会把任务从内部持有者那儿移除
|
task.suspensions == 0
|
||||||
// 也就是说,会减少 task 的引用计数
|
|
||||||
// 如果没有其它持有者的话,这个任务就会被释放
|
// 会清零 task 的暂停计数
|
||||||
|
// 被 cancel 的 task 即使重新设置其它调度规则也不会有任何作用了
|
||||||
task.cancel()
|
task.cancel()
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 子动作
|
#### 子动作
|
||||||
|
|
||||||
你可以添加更多的 action 到一个 task 上去,并在任意时刻移除它们:
|
你可以添加更多的 action 到 task,并在任意时刻移除它们。
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
let dailyTask = Plan.every(1.day)
|
let dailyTask = Plan.every(1.day)
|
||||||
|
@ -210,55 +234,6 @@ let key = dailyTask.addAction {
|
||||||
dailyTask.removeAction(byKey: key)
|
dailyTask.removeAction(byKey: key)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 标签
|
|
||||||
|
|
||||||
你可以用 tag 来组织 tasks,用 queue 指定这个 task 派发到哪里:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
let s = Plan.every(1.day)
|
|
||||||
let task0 = s.do(queue: myTaskQueue) { }
|
|
||||||
let task1 = s.do(queue: myTaskQueue) { }
|
|
||||||
|
|
||||||
task0.addTag("database")
|
|
||||||
task1.addTags("database", "log")
|
|
||||||
task1.removeTag("log")
|
|
||||||
|
|
||||||
Task.suspend(byTag: "log")
|
|
||||||
Task.resume(byTag: "log")
|
|
||||||
Task.cancel(byTag: "log")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 时间线
|
|
||||||
|
|
||||||
你可以实时地观察 task 的当前时间线:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
let timeline = task.timeline
|
|
||||||
print(timeline.initialization)
|
|
||||||
print(timeline.firstExecution)
|
|
||||||
print(timeline.lastExecution)
|
|
||||||
print(timeline.estimatedNextExecution)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 寿命
|
|
||||||
|
|
||||||
也可以精确地设置 task 的寿命:
|
|
||||||
|
|
||||||
```swift
|
|
||||||
// 会再 10 小时后取消该 task
|
|
||||||
task.setLifetime(10.hours)
|
|
||||||
|
|
||||||
// 会给该 task 的寿命增加 1 小时
|
|
||||||
task.addLifetime(1.hour)
|
|
||||||
|
|
||||||
task.restOfLifetime == 11.hours
|
|
||||||
```
|
|
||||||
|
|
||||||
## 支持
|
|
||||||
|
|
||||||
- iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
|
|
||||||
- Linux(Tested on Ubuntu 16.04)
|
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
### CocoaPods
|
### CocoaPods
|
||||||
|
@ -268,44 +243,45 @@ task.restOfLifetime == 11.hours
|
||||||
use_frameworks!
|
use_frameworks!
|
||||||
|
|
||||||
target 'YOUR_TARGET_NAME' do
|
target 'YOUR_TARGET_NAME' do
|
||||||
pod 'Schedule', '~> 1.0'
|
pod 'Schedule', '~> 2.0'
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
### Carthage
|
### Carthage
|
||||||
|
|
||||||
```ruby
|
```
|
||||||
github "jianstm/Schedule" ~> 1.0
|
# Cartfile
|
||||||
|
github "jianstm/Schedule" ~> 2.0
|
||||||
```
|
```
|
||||||
|
|
||||||
### Swift Package Manager
|
### Swift Package Manager
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/jianstm/Schedule", .upToNextMajor("1.0.0"))
|
.package(
|
||||||
|
url: "https://github.com/jianstm/Schedule", .upToNextMajor(from: "2.0.0")
|
||||||
|
)
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
## 致谢
|
|
||||||
|
|
||||||
项目灵感来自于 Dan Bader 的 [schedule](https://github.com/dbader/schedule)!语法设计深受 Ruby 影响!
|
|
||||||
|
|
||||||
## 贡献
|
## 贡献
|
||||||
|
|
||||||
喜欢 **Schedule** 吗?谢谢!与此同时我需要你的帮助:
|
喜欢 **Schedule** 吗?谢谢!!!
|
||||||
|
|
||||||
|
与此同时如果你想参与进来的话,你可以:
|
||||||
|
|
||||||
### 找 Bugs
|
### 找 Bugs
|
||||||
|
|
||||||
Schedule 还是一个非常年轻的项目,很难说项目离 bug free 还有多远。如果你能帮 Schedule 找到或者解决还没被发现的 bug 的话,我将感激不尽!
|
Schedule 还是一个非常年轻的项目,如果你能帮 Schedule 找到甚至解决潜在的 bugs 的话,那就太感谢啦!
|
||||||
|
|
||||||
### 新功能
|
### 新功能
|
||||||
|
|
||||||
对项目有什么新的想法吗?尽管在 issue 里分享出来,或者你也可以直接提交你的 Pull Request!
|
有一些有趣的想法?尽管在 issue 里分享出来,或者直接提交你的 Pull Request!
|
||||||
|
|
||||||
### 改善文档
|
### 改善文档
|
||||||
|
|
||||||
对 README 或者文档注释的改善建议在任何时候都非常欢迎,无论是错别字还是纠正我的蹩脚英文。对使用者来说,有时文档要比具体的代码实现要重要得多。
|
任何时候都欢迎对 README 或者文档注释的修改建议,无论是错别字还是纠正我的蹩脚英文,🤣。
|
||||||
|
|
||||||
### 分享
|
## 致谢
|
||||||
|
|
||||||
无疑,用的人越多,项目就会变得越健壮,所以,star!fork!然后告诉你的朋友们吧!
|
项目灵感来自 Dan Bader 的 [schedule](https://github.com/dbader/schedule)!
|
|
@ -1,15 +1,15 @@
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = "Schedule"
|
s.name = "Schedule"
|
||||||
s.version = "2.0.0-beta.1"
|
s.version = "2.0.0"
|
||||||
s.license = { :type => "MIT" }
|
s.license = { :type => "MIT" }
|
||||||
s.homepage = "https://github.com/jianstm/Schedule"
|
s.homepage = "https://github.com/jianstm/Schedule"
|
||||||
s.author = { "Quentin Jin" => "jianstm@gmail.com" }
|
s.author = { "Quentin Jin" => "jianstm@gmail.com" }
|
||||||
s.summary = "Lightweight timing task scheduler"
|
s.summary = "Schedule timing task in Swift using a fluent API"
|
||||||
|
|
||||||
s.source = { :git => "https://github.com/jianstm/Schedule.git", :tag => "#{s.version}" }
|
s.source = { :git => "https://github.com/jianstm/Schedule.git", :tag => "#{s.version}" }
|
||||||
s.source_files = "Sources/Schedule/*.swift"
|
s.source_files = "Sources/Schedule/*.swift"
|
||||||
|
|
||||||
s.swift_version = "4.2"
|
s.swift_version = "5.0"
|
||||||
|
|
||||||
s.ios.deployment_target = "10.0"
|
s.ios.deployment_target = "10.0"
|
||||||
s.osx.deployment_target = "10.12"
|
s.osx.deployment_target = "10.12"
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>$(PRODUCT_NAME)</string>
|
<string>$(PRODUCT_NAME)</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>FMWK</string>
|
<string>FMWK</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.0.0-beta.1</string>
|
<string>1.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -9,11 +9,11 @@
|
||||||
/* Begin PBXAggregateTarget section */
|
/* Begin PBXAggregateTarget section */
|
||||||
"Schedule::SchedulePackageTests::ProductTarget" /* SchedulePackageTests */ = {
|
"Schedule::SchedulePackageTests::ProductTarget" /* SchedulePackageTests */ = {
|
||||||
isa = PBXAggregateTarget;
|
isa = PBXAggregateTarget;
|
||||||
buildConfigurationList = OBJ_83 /* Build configuration list for PBXAggregateTarget "SchedulePackageTests" */;
|
buildConfigurationList = OBJ_72 /* Build configuration list for PBXAggregateTarget "SchedulePackageTests" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
OBJ_86 /* PBXTargetDependency */,
|
OBJ_75 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = SchedulePackageTests;
|
name = SchedulePackageTests;
|
||||||
productName = SchedulePackageTests;
|
productName = SchedulePackageTests;
|
||||||
|
@ -21,54 +21,44 @@
|
||||||
/* End PBXAggregateTarget section */
|
/* End PBXAggregateTarget section */
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
OBJ_100 /* TaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_37 /* TaskTests.swift */; };
|
629FAA6F2255CC2700ED5D67 /* IntervalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629FAA6E2255CC2700ED5D67 /* IntervalTests.swift */; };
|
||||||
OBJ_101 /* XCTestManifests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_38 /* XCTestManifests.swift */; };
|
629FAA712255D14800ED5D67 /* WeekdayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629FAA702255D14800ED5D67 /* WeekdayTests.swift */; };
|
||||||
OBJ_103 /* Schedule.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "Schedule::Schedule::Product" /* Schedule.framework */; };
|
629FAA732255D1CA00ED5D67 /* PeriodTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629FAA722255D1CA00ED5D67 /* PeriodTests.swift */; };
|
||||||
OBJ_50 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* Atomic.swift */; };
|
629FAA752255D6B100ED5D67 /* MonthdayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629FAA742255D6B100ED5D67 /* MonthdayTests.swift */; };
|
||||||
OBJ_51 /* Cabinet.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* Cabinet.swift */; };
|
OBJ_49 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* Atomic.swift */; };
|
||||||
OBJ_52 /* DeinitObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* DeinitObserver.swift */; };
|
OBJ_50 /* Bag.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* Bag.swift */; };
|
||||||
OBJ_53 /* Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* Deprecated.swift */; };
|
OBJ_53 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* Extensions.swift */; };
|
||||||
OBJ_54 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* Extensions.swift */; };
|
OBJ_54 /* Interval.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* Interval.swift */; };
|
||||||
OBJ_55 /* Interval.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* Interval.swift */; };
|
OBJ_55 /* Monthday.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* Monthday.swift */; };
|
||||||
OBJ_56 /* Monthday.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* Monthday.swift */; };
|
OBJ_56 /* Period.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* Period.swift */; };
|
||||||
OBJ_57 /* Period.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* Period.swift */; };
|
OBJ_57 /* Plan.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* Plan.swift */; };
|
||||||
OBJ_58 /* Plan.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* Plan.swift */; };
|
OBJ_58 /* RunLoopTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* RunLoopTask.swift */; };
|
||||||
OBJ_59 /* RunLoopTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* RunLoopTask.swift */; };
|
OBJ_59 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* Task.swift */; };
|
||||||
OBJ_60 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* Task.swift */; };
|
OBJ_60 /* TaskCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* TaskCenter.swift */; };
|
||||||
OBJ_61 /* TaskCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* TaskCenter.swift */; };
|
OBJ_61 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* Time.swift */; };
|
||||||
OBJ_62 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* Time.swift */; };
|
OBJ_63 /* Weekday.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* Weekday.swift */; };
|
||||||
OBJ_63 /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* Timeline.swift */; };
|
OBJ_70 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
|
||||||
OBJ_64 /* Weekday.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* Weekday.swift */; };
|
OBJ_81 /* AtomicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* AtomicTests.swift */; };
|
||||||
OBJ_71 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* Log.swift */; };
|
OBJ_82 /* BagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* BagTests.swift */; };
|
||||||
OBJ_72 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* main.swift */; };
|
OBJ_83 /* TimeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* TimeTests.swift */; };
|
||||||
OBJ_74 /* Schedule.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "Schedule::Schedule::Product" /* Schedule.framework */; };
|
OBJ_85 /* ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* ExtensionsTests.swift */; };
|
||||||
OBJ_81 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
|
OBJ_86 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* Helpers.swift */; };
|
||||||
OBJ_92 /* AtomicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_29 /* AtomicTests.swift */; };
|
OBJ_87 /* PlanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_32 /* PlanTests.swift */; };
|
||||||
OBJ_93 /* CabinetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* CabinetTests.swift */; };
|
OBJ_88 /* TaskCenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* TaskCenterTests.swift */; };
|
||||||
OBJ_94 /* DateTimeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* DateTimeTests.swift */; };
|
OBJ_89 /* TaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* TaskTests.swift */; };
|
||||||
OBJ_95 /* DeinitObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_32 /* DeinitObserverTests.swift */; };
|
OBJ_90 /* XCTestManifests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_35 /* XCTestManifests.swift */; };
|
||||||
OBJ_96 /* ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* ExtensionsTests.swift */; };
|
OBJ_92 /* Schedule.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "Schedule::Schedule::Product" /* Schedule.framework */; };
|
||||||
OBJ_97 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* Helpers.swift */; };
|
|
||||||
OBJ_98 /* PlanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_35 /* PlanTests.swift */; };
|
|
||||||
OBJ_99 /* TaskCenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_36 /* TaskCenterTests.swift */; };
|
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
66C8798D2237FA2F00A95D60 /* PBXContainerItemProxy */ = {
|
623D582F2254B44800565925 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = OBJ_1 /* Project object */;
|
containerPortal = OBJ_1 /* Project object */;
|
||||||
proxyType = 1;
|
proxyType = 1;
|
||||||
remoteGlobalIDString = "Schedule::Schedule";
|
remoteGlobalIDString = "Schedule::Schedule";
|
||||||
remoteInfo = Schedule;
|
remoteInfo = Schedule;
|
||||||
};
|
};
|
||||||
66C8798E2237FA2F00A95D60 /* PBXContainerItemProxy */ = {
|
623D58302254B44900565925 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
|
||||||
containerPortal = OBJ_1 /* Project object */;
|
|
||||||
proxyType = 1;
|
|
||||||
remoteGlobalIDString = "Schedule::Schedule";
|
|
||||||
remoteInfo = Schedule;
|
|
||||||
};
|
|
||||||
66C8798F2237FA3000A95D60 /* PBXContainerItemProxy */ = {
|
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = OBJ_1 /* Project object */;
|
containerPortal = OBJ_1 /* Project object */;
|
||||||
proxyType = 1;
|
proxyType = 1;
|
||||||
|
@ -78,9 +68,11 @@
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
OBJ_10 /* Cabinet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cabinet.swift; sourceTree = "<group>"; };
|
629FAA6E2255CC2700ED5D67 /* IntervalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntervalTests.swift; sourceTree = "<group>"; };
|
||||||
OBJ_11 /* DeinitObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeinitObserver.swift; sourceTree = "<group>"; };
|
629FAA702255D14800ED5D67 /* WeekdayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekdayTests.swift; sourceTree = "<group>"; };
|
||||||
OBJ_12 /* Deprecated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deprecated.swift; sourceTree = "<group>"; };
|
629FAA722255D1CA00ED5D67 /* PeriodTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeriodTests.swift; sourceTree = "<group>"; };
|
||||||
|
629FAA742255D6B100ED5D67 /* MonthdayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthdayTests.swift; sourceTree = "<group>"; };
|
||||||
|
OBJ_10 /* Bag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bag.swift; sourceTree = "<group>"; };
|
||||||
OBJ_13 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
OBJ_13 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
||||||
OBJ_14 /* Interval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interval.swift; sourceTree = "<group>"; };
|
OBJ_14 /* Interval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interval.swift; sourceTree = "<group>"; };
|
||||||
OBJ_15 /* Monthday.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Monthday.swift; sourceTree = "<group>"; };
|
OBJ_15 /* Monthday.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Monthday.swift; sourceTree = "<group>"; };
|
||||||
|
@ -90,118 +82,78 @@
|
||||||
OBJ_19 /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = "<group>"; };
|
OBJ_19 /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = "<group>"; };
|
||||||
OBJ_20 /* TaskCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCenter.swift; sourceTree = "<group>"; };
|
OBJ_20 /* TaskCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCenter.swift; sourceTree = "<group>"; };
|
||||||
OBJ_21 /* Time.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = "<group>"; };
|
OBJ_21 /* Time.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = "<group>"; };
|
||||||
OBJ_22 /* Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = "<group>"; };
|
|
||||||
OBJ_23 /* Weekday.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weekday.swift; sourceTree = "<group>"; };
|
OBJ_23 /* Weekday.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weekday.swift; sourceTree = "<group>"; };
|
||||||
OBJ_25 /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
|
OBJ_26 /* AtomicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicTests.swift; sourceTree = "<group>"; };
|
||||||
OBJ_26 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
OBJ_27 /* BagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BagTests.swift; sourceTree = "<group>"; };
|
||||||
OBJ_29 /* AtomicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicTests.swift; sourceTree = "<group>"; };
|
OBJ_28 /* TimeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeTests.swift; sourceTree = "<group>"; };
|
||||||
OBJ_30 /* CabinetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CabinetTests.swift; sourceTree = "<group>"; };
|
OBJ_30 /* ExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsTests.swift; sourceTree = "<group>"; };
|
||||||
OBJ_31 /* DateTimeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeTests.swift; sourceTree = "<group>"; };
|
OBJ_31 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
|
||||||
OBJ_32 /* DeinitObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeinitObserverTests.swift; sourceTree = "<group>"; };
|
OBJ_32 /* PlanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanTests.swift; sourceTree = "<group>"; };
|
||||||
OBJ_33 /* ExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsTests.swift; sourceTree = "<group>"; };
|
OBJ_33 /* TaskCenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCenterTests.swift; sourceTree = "<group>"; };
|
||||||
OBJ_34 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
|
OBJ_34 /* TaskTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskTests.swift; sourceTree = "<group>"; };
|
||||||
OBJ_35 /* PlanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanTests.swift; sourceTree = "<group>"; };
|
OBJ_35 /* XCTestManifests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestManifests.swift; sourceTree = "<group>"; };
|
||||||
OBJ_36 /* TaskCenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCenterTests.swift; sourceTree = "<group>"; };
|
OBJ_39 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; };
|
||||||
OBJ_37 /* TaskTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskTests.swift; sourceTree = "<group>"; };
|
OBJ_40 /* Schedule.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = Schedule.podspec; sourceTree = "<group>"; };
|
||||||
OBJ_38 /* XCTestManifests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestManifests.swift; sourceTree = "<group>"; };
|
OBJ_41 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||||
OBJ_40 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; };
|
OBJ_42 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||||
|
OBJ_43 /* README.zh_cn.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.zh_cn.md; 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>"; };
|
||||||
OBJ_9 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = "<group>"; };
|
OBJ_9 /* Atomic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Atomic.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; };
|
||||||
"Schedule::ScheduleDemo::Product" /* ScheduleDemo */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; path = ScheduleDemo; sourceTree = BUILT_PRODUCTS_DIR; };
|
"Schedule::ScheduleTests::Product" /* ScheduleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = ScheduleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
"Schedule::ScheduleTests::Product" /* ScheduleTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = ScheduleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
OBJ_102 /* Frameworks */ = {
|
OBJ_64 /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 0;
|
|
||||||
files = (
|
|
||||||
OBJ_103 /* Schedule.framework in Frameworks */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
OBJ_65 /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 0;
|
buildActionMask = 0;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
OBJ_73 /* Frameworks */ = {
|
OBJ_91 /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 0;
|
buildActionMask = 0;
|
||||||
files = (
|
files = (
|
||||||
OBJ_74 /* Schedule.framework in Frameworks */,
|
OBJ_92 /* Schedule.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
66C87992223A90D300A95D60 /* Utils */ = {
|
OBJ_24 /* Tests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
OBJ_13 /* Extensions.swift */,
|
OBJ_25 /* ScheduleTests */,
|
||||||
OBJ_11 /* DeinitObserver.swift */,
|
|
||||||
OBJ_9 /* Atomic.swift */,
|
|
||||||
OBJ_10 /* Cabinet.swift */,
|
|
||||||
);
|
|
||||||
name = Utils;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
66C87993223A90EE00A95D60 /* DateTime */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
OBJ_14 /* Interval.swift */,
|
|
||||||
OBJ_15 /* Monthday.swift */,
|
|
||||||
OBJ_16 /* Period.swift */,
|
|
||||||
OBJ_21 /* Time.swift */,
|
|
||||||
OBJ_23 /* Weekday.swift */,
|
|
||||||
);
|
|
||||||
name = DateTime;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
OBJ_24 /* ScheduleDemo */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
OBJ_25 /* Log.swift */,
|
|
||||||
OBJ_26 /* main.swift */,
|
|
||||||
);
|
|
||||||
name = ScheduleDemo;
|
|
||||||
path = Sources/ScheduleDemo;
|
|
||||||
sourceTree = SOURCE_ROOT;
|
|
||||||
};
|
|
||||||
OBJ_27 /* Tests */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
OBJ_28 /* ScheduleTests */,
|
|
||||||
);
|
);
|
||||||
name = Tests;
|
name = Tests;
|
||||||
sourceTree = SOURCE_ROOT;
|
sourceTree = SOURCE_ROOT;
|
||||||
};
|
};
|
||||||
OBJ_28 /* ScheduleTests */ = {
|
OBJ_25 /* ScheduleTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
OBJ_29 /* AtomicTests.swift */,
|
OBJ_26 /* AtomicTests.swift */,
|
||||||
OBJ_30 /* CabinetTests.swift */,
|
OBJ_27 /* BagTests.swift */,
|
||||||
OBJ_31 /* DateTimeTests.swift */,
|
OBJ_30 /* ExtensionsTests.swift */,
|
||||||
OBJ_32 /* DeinitObserverTests.swift */,
|
OBJ_31 /* Helpers.swift */,
|
||||||
OBJ_33 /* ExtensionsTests.swift */,
|
629FAA6E2255CC2700ED5D67 /* IntervalTests.swift */,
|
||||||
OBJ_34 /* Helpers.swift */,
|
629FAA742255D6B100ED5D67 /* MonthdayTests.swift */,
|
||||||
OBJ_35 /* PlanTests.swift */,
|
629FAA722255D1CA00ED5D67 /* PeriodTests.swift */,
|
||||||
OBJ_36 /* TaskCenterTests.swift */,
|
OBJ_32 /* PlanTests.swift */,
|
||||||
OBJ_37 /* TaskTests.swift */,
|
OBJ_33 /* TaskCenterTests.swift */,
|
||||||
OBJ_38 /* XCTestManifests.swift */,
|
OBJ_34 /* TaskTests.swift */,
|
||||||
|
OBJ_28 /* TimeTests.swift */,
|
||||||
|
629FAA702255D14800ED5D67 /* WeekdayTests.swift */,
|
||||||
|
OBJ_35 /* XCTestManifests.swift */,
|
||||||
);
|
);
|
||||||
name = ScheduleTests;
|
name = ScheduleTests;
|
||||||
path = Tests/ScheduleTests;
|
path = Tests/ScheduleTests;
|
||||||
sourceTree = SOURCE_ROOT;
|
sourceTree = SOURCE_ROOT;
|
||||||
};
|
};
|
||||||
OBJ_41 /* Products */ = {
|
OBJ_36 /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
"Schedule::ScheduleDemo::Product" /* ScheduleDemo */,
|
|
||||||
"Schedule::Schedule::Product" /* Schedule.framework */,
|
"Schedule::Schedule::Product" /* Schedule.framework */,
|
||||||
"Schedule::ScheduleTests::Product" /* ScheduleTests.xctest */,
|
"Schedule::ScheduleTests::Product" /* ScheduleTests.xctest */,
|
||||||
);
|
);
|
||||||
|
@ -213,9 +165,13 @@
|
||||||
children = (
|
children = (
|
||||||
OBJ_6 /* Package.swift */,
|
OBJ_6 /* Package.swift */,
|
||||||
OBJ_7 /* Sources */,
|
OBJ_7 /* Sources */,
|
||||||
OBJ_27 /* Tests */,
|
OBJ_24 /* Tests */,
|
||||||
OBJ_40 /* assets */,
|
OBJ_36 /* Products */,
|
||||||
OBJ_41 /* Products */,
|
OBJ_39 /* assets */,
|
||||||
|
OBJ_40 /* Schedule.podspec */,
|
||||||
|
OBJ_41 /* LICENSE */,
|
||||||
|
OBJ_42 /* README.md */,
|
||||||
|
OBJ_43 /* README.zh_cn.md */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
@ -223,7 +179,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
OBJ_8 /* Schedule */,
|
OBJ_8 /* Schedule */,
|
||||||
OBJ_24 /* ScheduleDemo */,
|
|
||||||
);
|
);
|
||||||
name = Sources;
|
name = Sources;
|
||||||
sourceTree = SOURCE_ROOT;
|
sourceTree = SOURCE_ROOT;
|
||||||
|
@ -231,14 +186,18 @@
|
||||||
OBJ_8 /* Schedule */ = {
|
OBJ_8 /* Schedule */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
66C87993223A90EE00A95D60 /* DateTime */,
|
OBJ_9 /* Atomic.swift */,
|
||||||
66C87992223A90D300A95D60 /* Utils */,
|
OBJ_10 /* Bag.swift */,
|
||||||
OBJ_12 /* Deprecated.swift */,
|
OBJ_13 /* Extensions.swift */,
|
||||||
|
OBJ_14 /* Interval.swift */,
|
||||||
|
OBJ_15 /* Monthday.swift */,
|
||||||
|
OBJ_16 /* Period.swift */,
|
||||||
OBJ_17 /* Plan.swift */,
|
OBJ_17 /* Plan.swift */,
|
||||||
OBJ_18 /* RunLoopTask.swift */,
|
OBJ_18 /* RunLoopTask.swift */,
|
||||||
OBJ_19 /* Task.swift */,
|
OBJ_19 /* Task.swift */,
|
||||||
OBJ_20 /* TaskCenter.swift */,
|
OBJ_20 /* TaskCenter.swift */,
|
||||||
OBJ_22 /* Timeline.swift */,
|
OBJ_21 /* Time.swift */,
|
||||||
|
OBJ_23 /* Weekday.swift */,
|
||||||
);
|
);
|
||||||
name = Schedule;
|
name = Schedule;
|
||||||
path = Sources/Schedule;
|
path = Sources/Schedule;
|
||||||
|
@ -249,10 +208,10 @@
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
"Schedule::Schedule" /* Schedule */ = {
|
"Schedule::Schedule" /* Schedule */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = OBJ_46 /* Build configuration list for PBXNativeTarget "Schedule" */;
|
buildConfigurationList = OBJ_45 /* Build configuration list for PBXNativeTarget "Schedule" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
OBJ_49 /* Sources */,
|
OBJ_48 /* Sources */,
|
||||||
OBJ_65 /* Frameworks */,
|
OBJ_64 /* Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
@ -263,34 +222,17 @@
|
||||||
productReference = "Schedule::Schedule::Product" /* Schedule.framework */;
|
productReference = "Schedule::Schedule::Product" /* Schedule.framework */;
|
||||||
productType = "com.apple.product-type.framework";
|
productType = "com.apple.product-type.framework";
|
||||||
};
|
};
|
||||||
"Schedule::ScheduleDemo" /* ScheduleDemo */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = OBJ_67 /* Build configuration list for PBXNativeTarget "ScheduleDemo" */;
|
|
||||||
buildPhases = (
|
|
||||||
OBJ_70 /* Sources */,
|
|
||||||
OBJ_73 /* Frameworks */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
OBJ_75 /* PBXTargetDependency */,
|
|
||||||
);
|
|
||||||
name = ScheduleDemo;
|
|
||||||
productName = ScheduleDemo;
|
|
||||||
productReference = "Schedule::ScheduleDemo::Product" /* ScheduleDemo */;
|
|
||||||
productType = "com.apple.product-type.tool";
|
|
||||||
};
|
|
||||||
"Schedule::ScheduleTests" /* ScheduleTests */ = {
|
"Schedule::ScheduleTests" /* ScheduleTests */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = OBJ_88 /* Build configuration list for PBXNativeTarget "ScheduleTests" */;
|
buildConfigurationList = OBJ_77 /* Build configuration list for PBXNativeTarget "ScheduleTests" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
OBJ_91 /* Sources */,
|
OBJ_80 /* Sources */,
|
||||||
OBJ_102 /* Frameworks */,
|
OBJ_91 /* Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
OBJ_104 /* PBXTargetDependency */,
|
OBJ_93 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = ScheduleTests;
|
name = ScheduleTests;
|
||||||
productName = ScheduleTests;
|
productName = ScheduleTests;
|
||||||
|
@ -299,9 +241,9 @@
|
||||||
};
|
};
|
||||||
"Schedule::SwiftPMPackageDescription" /* SchedulePackageDescription */ = {
|
"Schedule::SwiftPMPackageDescription" /* SchedulePackageDescription */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = OBJ_77 /* Build configuration list for PBXNativeTarget "SchedulePackageDescription" */;
|
buildConfigurationList = OBJ_66 /* Build configuration list for PBXNativeTarget "SchedulePackageDescription" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
OBJ_80 /* Sources */,
|
OBJ_69 /* Sources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
@ -317,6 +259,7 @@
|
||||||
OBJ_1 /* Project object */ = {
|
OBJ_1 /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
|
LastSwiftMigration = 9999;
|
||||||
LastUpgradeCheck = 9999;
|
LastUpgradeCheck = 9999;
|
||||||
};
|
};
|
||||||
buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "Schedule" */;
|
buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "Schedule" */;
|
||||||
|
@ -324,15 +267,15 @@
|
||||||
developmentRegion = English;
|
developmentRegion = English;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
|
English,
|
||||||
en,
|
en,
|
||||||
);
|
);
|
||||||
mainGroup = OBJ_5;
|
mainGroup = OBJ_5;
|
||||||
productRefGroup = OBJ_41 /* Products */;
|
productRefGroup = OBJ_36 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
"Schedule::Schedule" /* Schedule */,
|
"Schedule::Schedule" /* Schedule */,
|
||||||
"Schedule::ScheduleDemo" /* ScheduleDemo */,
|
|
||||||
"Schedule::SwiftPMPackageDescription" /* SchedulePackageDescription */,
|
"Schedule::SwiftPMPackageDescription" /* SchedulePackageDescription */,
|
||||||
"Schedule::SchedulePackageTests::ProductTarget" /* SchedulePackageTests */,
|
"Schedule::SchedulePackageTests::ProductTarget" /* SchedulePackageTests */,
|
||||||
"Schedule::ScheduleTests" /* ScheduleTests */,
|
"Schedule::ScheduleTests" /* ScheduleTests */,
|
||||||
|
@ -341,34 +284,30 @@
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
OBJ_49 /* Sources */ = {
|
OBJ_48 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 0;
|
buildActionMask = 0;
|
||||||
files = (
|
files = (
|
||||||
OBJ_50 /* Atomic.swift in Sources */,
|
OBJ_49 /* Atomic.swift in Sources */,
|
||||||
OBJ_51 /* Cabinet.swift in Sources */,
|
OBJ_50 /* Bag.swift in Sources */,
|
||||||
OBJ_52 /* DeinitObserver.swift in Sources */,
|
OBJ_53 /* Extensions.swift in Sources */,
|
||||||
OBJ_53 /* Deprecated.swift in Sources */,
|
OBJ_54 /* Interval.swift in Sources */,
|
||||||
OBJ_54 /* Extensions.swift in Sources */,
|
OBJ_55 /* Monthday.swift in Sources */,
|
||||||
OBJ_55 /* Interval.swift in Sources */,
|
OBJ_56 /* Period.swift in Sources */,
|
||||||
OBJ_56 /* Monthday.swift in Sources */,
|
OBJ_57 /* Plan.swift in Sources */,
|
||||||
OBJ_57 /* Period.swift in Sources */,
|
OBJ_58 /* RunLoopTask.swift in Sources */,
|
||||||
OBJ_58 /* Plan.swift in Sources */,
|
OBJ_59 /* Task.swift in Sources */,
|
||||||
OBJ_59 /* RunLoopTask.swift in Sources */,
|
OBJ_60 /* TaskCenter.swift in Sources */,
|
||||||
OBJ_60 /* Task.swift in Sources */,
|
OBJ_61 /* Time.swift in Sources */,
|
||||||
OBJ_61 /* TaskCenter.swift in Sources */,
|
OBJ_63 /* Weekday.swift in Sources */,
|
||||||
OBJ_62 /* Time.swift in Sources */,
|
|
||||||
OBJ_63 /* Timeline.swift in Sources */,
|
|
||||||
OBJ_64 /* Weekday.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
OBJ_70 /* Sources */ = {
|
OBJ_69 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 0;
|
buildActionMask = 0;
|
||||||
files = (
|
files = (
|
||||||
OBJ_71 /* Log.swift in Sources */,
|
OBJ_70 /* Package.swift in Sources */,
|
||||||
OBJ_72 /* main.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -376,44 +315,34 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 0;
|
buildActionMask = 0;
|
||||||
files = (
|
files = (
|
||||||
OBJ_81 /* Package.swift in Sources */,
|
OBJ_81 /* AtomicTests.swift in Sources */,
|
||||||
);
|
OBJ_82 /* BagTests.swift in Sources */,
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
OBJ_83 /* TimeTests.swift in Sources */,
|
||||||
};
|
OBJ_85 /* ExtensionsTests.swift in Sources */,
|
||||||
OBJ_91 /* Sources */ = {
|
OBJ_86 /* Helpers.swift in Sources */,
|
||||||
isa = PBXSourcesBuildPhase;
|
629FAA712255D14800ED5D67 /* WeekdayTests.swift in Sources */,
|
||||||
buildActionMask = 0;
|
629FAA6F2255CC2700ED5D67 /* IntervalTests.swift in Sources */,
|
||||||
files = (
|
OBJ_87 /* PlanTests.swift in Sources */,
|
||||||
OBJ_92 /* AtomicTests.swift in Sources */,
|
629FAA732255D1CA00ED5D67 /* PeriodTests.swift in Sources */,
|
||||||
OBJ_93 /* CabinetTests.swift in Sources */,
|
OBJ_88 /* TaskCenterTests.swift in Sources */,
|
||||||
OBJ_94 /* DateTimeTests.swift in Sources */,
|
629FAA752255D6B100ED5D67 /* MonthdayTests.swift in Sources */,
|
||||||
OBJ_95 /* DeinitObserverTests.swift in Sources */,
|
OBJ_89 /* TaskTests.swift in Sources */,
|
||||||
OBJ_96 /* ExtensionsTests.swift in Sources */,
|
OBJ_90 /* XCTestManifests.swift in Sources */,
|
||||||
OBJ_97 /* Helpers.swift in Sources */,
|
|
||||||
OBJ_98 /* PlanTests.swift in Sources */,
|
|
||||||
OBJ_99 /* TaskCenterTests.swift in Sources */,
|
|
||||||
OBJ_100 /* TaskTests.swift in Sources */,
|
|
||||||
OBJ_101 /* XCTestManifests.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
OBJ_104 /* PBXTargetDependency */ = {
|
|
||||||
isa = PBXTargetDependency;
|
|
||||||
target = "Schedule::Schedule" /* Schedule */;
|
|
||||||
targetProxy = 66C8798E2237FA2F00A95D60 /* PBXContainerItemProxy */;
|
|
||||||
};
|
|
||||||
OBJ_75 /* PBXTargetDependency */ = {
|
OBJ_75 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
|
||||||
target = "Schedule::Schedule" /* Schedule */;
|
|
||||||
targetProxy = 66C8798D2237FA2F00A95D60 /* PBXContainerItemProxy */;
|
|
||||||
};
|
|
||||||
OBJ_86 /* PBXTargetDependency */ = {
|
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = "Schedule::ScheduleTests" /* ScheduleTests */;
|
target = "Schedule::ScheduleTests" /* ScheduleTests */;
|
||||||
targetProxy = 66C8798F2237FA3000A95D60 /* PBXContainerItemProxy */;
|
targetProxy = 623D58302254B44900565925 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
OBJ_93 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = "Schedule::Schedule" /* Schedule */;
|
||||||
|
targetProxy = 623D582F2254B44800565925 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
@ -429,8 +358,9 @@
|
||||||
ENABLE_NS_ASSERTIONS = YES;
|
ENABLE_NS_ASSERTIONS = YES;
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"DEBUG=1",
|
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
"SWIFT_PACKAGE=1",
|
||||||
|
"DEBUG=1",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
@ -438,7 +368,7 @@
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
|
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "SWIFT_PACKAGE DEBUG";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
USE_HEADERMAP = NO;
|
USE_HEADERMAP = NO;
|
||||||
};
|
};
|
||||||
|
@ -453,18 +383,22 @@
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
GCC_OPTIMIZATION_LEVEL = s;
|
GCC_OPTIMIZATION_LEVEL = s;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"SWIFT_PACKAGE=1",
|
||||||
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||||
OTHER_SWIFT_FLAGS = "-DXcode";
|
OTHER_SWIFT_FLAGS = "-DXcode";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
|
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
USE_HEADERMAP = NO;
|
USE_HEADERMAP = NO;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
OBJ_47 /* Debug */ = {
|
OBJ_46 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
|
@ -485,14 +419,14 @@
|
||||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||||
SWIFT_VERSION = 4.2;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGET_NAME = Schedule;
|
TARGET_NAME = Schedule;
|
||||||
TVOS_DEPLOYMENT_TARGET = 10.0;
|
TVOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
WATCHOS_DEPLOYMENT_TARGET = 3.0;
|
WATCHOS_DEPLOYMENT_TARGET = 3.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
OBJ_48 /* Release */ = {
|
OBJ_47 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
|
@ -513,88 +447,69 @@
|
||||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||||
SWIFT_VERSION = 4.2;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGET_NAME = Schedule;
|
TARGET_NAME = Schedule;
|
||||||
TVOS_DEPLOYMENT_TARGET = 10.0;
|
TVOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
WATCHOS_DEPLOYMENT_TARGET = 3.0;
|
WATCHOS_DEPLOYMENT_TARGET = 3.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
OBJ_68 /* Debug */ = {
|
OBJ_67 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
LD = /usr/bin/true;
|
||||||
"$(inherited)",
|
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk";
|
||||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
SWIFT_VERSION = 5.0;
|
||||||
);
|
|
||||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
|
||||||
INFOPLIST_FILE = Schedule.xcodeproj/ScheduleDemo_Info.plist;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx @executable_path";
|
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
|
||||||
OTHER_CFLAGS = "$(inherited)";
|
|
||||||
OTHER_LDFLAGS = "$(inherited)";
|
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
|
||||||
SWIFT_FORCE_DYNAMIC_LINK_STDLIB = YES;
|
|
||||||
SWIFT_FORCE_STATIC_LINK_STDLIB = NO;
|
|
||||||
SWIFT_VERSION = 4.2;
|
|
||||||
TARGET_NAME = ScheduleDemo;
|
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
OBJ_69 /* Release */ = {
|
OBJ_68 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
LD = /usr/bin/true;
|
||||||
|
OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
OBJ_73 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
OBJ_74 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
|
||||||
);
|
|
||||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
|
||||||
INFOPLIST_FILE = Schedule.xcodeproj/ScheduleDemo_Info.plist;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx @executable_path";
|
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
|
||||||
OTHER_CFLAGS = "$(inherited)";
|
|
||||||
OTHER_LDFLAGS = "$(inherited)";
|
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
|
||||||
SWIFT_FORCE_DYNAMIC_LINK_STDLIB = YES;
|
|
||||||
SWIFT_FORCE_STATIC_LINK_STDLIB = NO;
|
|
||||||
SWIFT_VERSION = 4.2;
|
|
||||||
TARGET_NAME = ScheduleDemo;
|
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
OBJ_78 /* Debug */ = {
|
OBJ_78 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
LD = /usr/bin/true;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
OTHER_SWIFT_FLAGS = "-swift-version 4.2 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk";
|
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
|
||||||
SWIFT_VERSION = 4.2;
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
||||||
|
);
|
||||||
|
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||||
|
INFOPLIST_FILE = Schedule.xcodeproj/ScheduleTests_Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||||
|
OTHER_CFLAGS = "$(inherited)";
|
||||||
|
OTHER_LDFLAGS = "$(inherited)";
|
||||||
|
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGET_NAME = ScheduleTests;
|
||||||
|
TVOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
WATCHOS_DEPLOYMENT_TARGET = 3.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
OBJ_79 /* Release */ = {
|
OBJ_79 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
LD = /usr/bin/true;
|
|
||||||
OTHER_SWIFT_FLAGS = "-swift-version 4.2 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk";
|
|
||||||
SWIFT_VERSION = 4.2;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
OBJ_84 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
OBJ_85 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
OBJ_89 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
@ -605,34 +520,17 @@
|
||||||
);
|
);
|
||||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||||
INFOPLIST_FILE = Schedule.xcodeproj/ScheduleTests_Info.plist;
|
INFOPLIST_FILE = Schedule.xcodeproj/ScheduleTests_Info.plist;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||||
OTHER_CFLAGS = "$(inherited)";
|
OTHER_CFLAGS = "$(inherited)";
|
||||||
OTHER_LDFLAGS = "$(inherited)";
|
OTHER_LDFLAGS = "$(inherited)";
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
||||||
SWIFT_VERSION = 4.2;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGET_NAME = ScheduleTests;
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
OBJ_90 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
CLANG_ENABLE_MODULES = YES;
|
|
||||||
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
|
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
|
||||||
);
|
|
||||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
|
||||||
INFOPLIST_FILE = Schedule.xcodeproj/ScheduleTests_Info.plist;
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks";
|
|
||||||
OTHER_CFLAGS = "$(inherited)";
|
|
||||||
OTHER_LDFLAGS = "$(inherited)";
|
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)";
|
|
||||||
SWIFT_VERSION = 4.2;
|
|
||||||
TARGET_NAME = ScheduleTests;
|
TARGET_NAME = ScheduleTests;
|
||||||
|
TVOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
WATCHOS_DEPLOYMENT_TARGET = 3.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
@ -648,25 +546,34 @@
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
OBJ_46 /* Build configuration list for PBXNativeTarget "Schedule" */ = {
|
OBJ_45 /* Build configuration list for PBXNativeTarget "Schedule" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
OBJ_47 /* Debug */,
|
OBJ_46 /* Debug */,
|
||||||
OBJ_48 /* Release */,
|
OBJ_47 /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
OBJ_67 /* Build configuration list for PBXNativeTarget "ScheduleDemo" */ = {
|
OBJ_66 /* Build configuration list for PBXNativeTarget "SchedulePackageDescription" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
OBJ_68 /* Debug */,
|
OBJ_67 /* Debug */,
|
||||||
OBJ_69 /* Release */,
|
OBJ_68 /* Release */,
|
||||||
);
|
);
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
OBJ_77 /* Build configuration list for PBXNativeTarget "SchedulePackageDescription" */ = {
|
OBJ_72 /* Build configuration list for PBXAggregateTarget "SchedulePackageTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
OBJ_73 /* Debug */,
|
||||||
|
OBJ_74 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
OBJ_77 /* Build configuration list for PBXNativeTarget "ScheduleTests" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
OBJ_78 /* Debug */,
|
OBJ_78 /* Debug */,
|
||||||
|
@ -675,24 +582,6 @@
|
||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
OBJ_83 /* Build configuration list for PBXAggregateTarget "SchedulePackageTests" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
OBJ_84 /* Debug */,
|
|
||||||
OBJ_85 /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
OBJ_88 /* Build configuration list for PBXNativeTarget "ScheduleTests" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
OBJ_89 /* Debug */,
|
|
||||||
OBJ_90 /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
};
|
};
|
||||||
rootObject = OBJ_1 /* Project object */;
|
rootObject = OBJ_1 /* Project object */;
|
||||||
|
|
|
@ -20,30 +20,18 @@
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
ReferencedContainer = "container:Schedule.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
<BuildActionEntry
|
|
||||||
buildForTesting = "YES"
|
|
||||||
buildForRunning = "YES"
|
|
||||||
buildForProfiling = "YES"
|
|
||||||
buildForArchiving = "YES"
|
|
||||||
buildForAnalyzing = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "Schedule::ScheduleDemo"
|
|
||||||
BuildableName = "ScheduleDemo"
|
|
||||||
BlueprintName = "ScheduleDemo"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildActionEntry>
|
|
||||||
</BuildActionEntries>
|
</BuildActionEntries>
|
||||||
</BuildAction>
|
</BuildAction>
|
||||||
<TestAction
|
<TestAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
codeCoverageEnabled = "YES"
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO">
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "Schedule::ScheduleTests"
|
BlueprintIdentifier = "Schedule::ScheduleTests"
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Scheme
|
|
||||||
LastUpgradeVersion = "9999"
|
|
||||||
version = "1.3">
|
|
||||||
<BuildAction
|
|
||||||
parallelizeBuildables = "YES"
|
|
||||||
buildImplicitDependencies = "YES">
|
|
||||||
<BuildActionEntries>
|
|
||||||
<BuildActionEntry
|
|
||||||
buildForTesting = "YES"
|
|
||||||
buildForRunning = "YES"
|
|
||||||
buildForProfiling = "YES"
|
|
||||||
buildForArchiving = "YES"
|
|
||||||
buildForAnalyzing = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "Schedule::ScheduleDemo"
|
|
||||||
BuildableName = "ScheduleDemo"
|
|
||||||
BlueprintName = "ScheduleDemo"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildActionEntry>
|
|
||||||
</BuildActionEntries>
|
|
||||||
</BuildAction>
|
|
||||||
<TestAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
||||||
<Testables>
|
|
||||||
<TestableReference
|
|
||||||
skipped = "NO">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "Schedule::ScheduleTests"
|
|
||||||
BuildableName = "ScheduleTests.xctest"
|
|
||||||
BlueprintName = "ScheduleTests"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</TestableReference>
|
|
||||||
</Testables>
|
|
||||||
<AdditionalOptions>
|
|
||||||
</AdditionalOptions>
|
|
||||||
</TestAction>
|
|
||||||
<LaunchAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
launchStyle = "0"
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
|
||||||
debugDocumentVersioning = "YES"
|
|
||||||
debugServiceExtension = "internal"
|
|
||||||
allowLocationSimulation = "YES">
|
|
||||||
<BuildableProductRunnable
|
|
||||||
runnableDebuggingMode = "0">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "Schedule::ScheduleDemo"
|
|
||||||
BuildableName = "ScheduleDemo"
|
|
||||||
BlueprintName = "ScheduleDemo"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildableProductRunnable>
|
|
||||||
<AdditionalOptions>
|
|
||||||
</AdditionalOptions>
|
|
||||||
</LaunchAction>
|
|
||||||
<ProfileAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
||||||
savedToolIdentifier = ""
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
debugDocumentVersioning = "YES">
|
|
||||||
</ProfileAction>
|
|
||||||
<AnalyzeAction
|
|
||||||
buildConfiguration = "Debug">
|
|
||||||
</AnalyzeAction>
|
|
||||||
<ArchiveAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
revealArchiveInOrganizer = "YES">
|
|
||||||
</ArchiveAction>
|
|
||||||
</Scheme>
|
|
|
@ -1,30 +1,38 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// A value box that can read and write the underlying value atomically.
|
/// An atomic box that can read and write the underlying value atomically.
|
||||||
final class Atomic<T> {
|
final class Atomic<T> {
|
||||||
|
|
||||||
private var v: T
|
private var val: T
|
||||||
private let lock = NSLock()
|
private let lock = NSLock()
|
||||||
|
|
||||||
init(_ value: T) {
|
/// Create an atomic box with the given initial value.
|
||||||
self.v = value
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a snapshot of the value nonatomically.
|
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
func snapshot() -> T {
|
init(_ value: T) {
|
||||||
return v
|
self.val = value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the value atomically.
|
/// Reads the current value atomically.
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
func read<U>(_ body: (T) -> U) -> U {
|
func read<U>(_ body: (T) -> U) -> U {
|
||||||
return lock.withLock { body(v) }
|
return lock.withLock { body(val) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the value atomically.
|
/// Reads the current value atomically.
|
||||||
|
@inline(__always)
|
||||||
|
func readVoid(_ body: (T) -> Void) {
|
||||||
|
lock.withLockVoid { body(val) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the current value atomically.
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
func write<U>(_ body: (inout T) -> U) -> U {
|
func write<U>(_ body: (inout T) -> U) -> U {
|
||||||
return lock.withLock { body(&v) }
|
return lock.withLock { body(&val) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the current value atomically.
|
||||||
|
@inline(__always)
|
||||||
|
func writeVoid(_ body: (inout T) -> Void) {
|
||||||
|
lock.withLockVoid { body(&val) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// A unique key for removing an element from a bag.
|
||||||
|
struct BagKey: Equatable {
|
||||||
|
|
||||||
|
fileprivate let i: UInt64
|
||||||
|
|
||||||
|
fileprivate init(underlying: UInt64) {
|
||||||
|
self.i = underlying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A generator that can generate a sequence of unique `BagKey`.
|
||||||
|
///
|
||||||
|
/// let k1 = gen.next()
|
||||||
|
/// let k2 = gen.next()
|
||||||
|
/// ...
|
||||||
|
struct BagKeyGenerator: Sequence, IteratorProtocol {
|
||||||
|
|
||||||
|
typealias Element = BagKey
|
||||||
|
|
||||||
|
private var k = BagKey(underlying: 0)
|
||||||
|
|
||||||
|
/// Advances to the next key and returns it, or nil if no next key exists.
|
||||||
|
mutating func next() -> BagKey? {
|
||||||
|
if k.i == UInt64.max { return nil }
|
||||||
|
defer { k = BagKey(underlying: k.i + 1) }
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An ordered sequence.
|
||||||
|
///
|
||||||
|
/// let k1 = bag.append(e1)
|
||||||
|
/// let k2 = bag.append(e2)
|
||||||
|
///
|
||||||
|
/// for e in bag {
|
||||||
|
/// // -> e1
|
||||||
|
/// // -> e2
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// bag.removeValue(for: k1)
|
||||||
|
struct Bag<Element> {
|
||||||
|
|
||||||
|
private typealias Entry = (key: BagKey, val: Element)
|
||||||
|
|
||||||
|
private var keyGen = BagKeyGenerator()
|
||||||
|
private var entries: [Entry] = []
|
||||||
|
|
||||||
|
/// Appends a new element at the end of this bag.
|
||||||
|
@discardableResult
|
||||||
|
mutating func append(_ new: Element) -> BagKey {
|
||||||
|
let key = keyGen.next()!
|
||||||
|
|
||||||
|
let entry = (key: key, val: new)
|
||||||
|
entries.append(entry)
|
||||||
|
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the element associated with a given key.
|
||||||
|
func value(for key: BagKey) -> Element? {
|
||||||
|
return entries.first(where: { $0.key == key })?.val
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the given key and its associated element from this bag.
|
||||||
|
@discardableResult
|
||||||
|
mutating func removeValue(for key: BagKey) -> Element? {
|
||||||
|
if let i = entries.firstIndex(where: { $0.key == key }) {
|
||||||
|
return entries.remove(at: i).val
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes all elements from this bag.
|
||||||
|
mutating func removeAll() {
|
||||||
|
entries.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of elements in this bag.
|
||||||
|
var count: Int {
|
||||||
|
return entries.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Bag: Sequence {
|
||||||
|
|
||||||
|
/// Returns an iterator over the elements of this bag.
|
||||||
|
@inline(__always)
|
||||||
|
func makeIterator() -> AnyIterator<Element> {
|
||||||
|
var iterator = entries.makeIterator()
|
||||||
|
return AnyIterator<Element> {
|
||||||
|
return iterator.next()?.val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,70 +0,0 @@
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct CabinetKey: Equatable {
|
|
||||||
|
|
||||||
private let i: UInt64
|
|
||||||
|
|
||||||
init(underlying: UInt64) {
|
|
||||||
self.i = underlying
|
|
||||||
}
|
|
||||||
|
|
||||||
func increased() -> CabinetKey {
|
|
||||||
return CabinetKey(underlying: i &+ 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func == (lhs: CabinetKey, rhs: CabinetKey) -> Bool {
|
|
||||||
return lhs.i == rhs.i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Cabinet<Element> {
|
|
||||||
|
|
||||||
private typealias Entry = (key: CabinetKey, element: Element)
|
|
||||||
|
|
||||||
private var key = CabinetKey(underlying: 0)
|
|
||||||
private var entries: [Entry] = []
|
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
mutating func append(_ new: Element) -> CabinetKey {
|
|
||||||
defer { key = key.increased() }
|
|
||||||
|
|
||||||
let entry = (key: key, element: new)
|
|
||||||
entries.append(entry)
|
|
||||||
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
func get(_ key: CabinetKey) -> Element? {
|
|
||||||
if let entry = entries.first(where: { $0.key == key }) {
|
|
||||||
return entry.element
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
mutating func delete(_ key: CabinetKey) -> Element? {
|
|
||||||
if let i = entries.firstIndex(where: { $0.key == key }) {
|
|
||||||
return entries.remove(at: i).element
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mutating func clear() {
|
|
||||||
entries.removeAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
var count: Int {
|
|
||||||
return entries.count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Cabinet: Sequence {
|
|
||||||
|
|
||||||
func makeIterator() -> AnyIterator<Element> {
|
|
||||||
var iterator = entries.makeIterator()
|
|
||||||
return AnyIterator<Element> {
|
|
||||||
return iterator.next()?.element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
import Foundation
|
|
||||||
|
|
||||||
#if canImport(ObjectiveC)
|
|
||||||
|
|
||||||
private var deinitObserverKey: Void = ()
|
|
||||||
|
|
||||||
class DeinitObserver {
|
|
||||||
|
|
||||||
private(set) weak var object: AnyObject?
|
|
||||||
|
|
||||||
private var action: (() -> Void)?
|
|
||||||
|
|
||||||
private init(_ action: @escaping () -> Void) {
|
|
||||||
self.action = action
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
static func observe(
|
|
||||||
_ object: AnyObject,
|
|
||||||
onDeinit action: @escaping () -> Void
|
|
||||||
) -> DeinitObserver {
|
|
||||||
let observer = DeinitObserver(action)
|
|
||||||
observer.object = object
|
|
||||||
|
|
||||||
objc_setAssociatedObject(object, &deinitObserverKey, observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
||||||
|
|
||||||
return observer
|
|
||||||
}
|
|
||||||
|
|
||||||
func invalidate() {
|
|
||||||
action = nil
|
|
||||||
if let o = object {
|
|
||||||
objc_setAssociatedObject(o, &deinitObserverKey, nil, .OBJC_ASSOCIATION_ASSIGN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
action?()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,59 +0,0 @@
|
||||||
//
|
|
||||||
// Deprecated.swift
|
|
||||||
// Schedule
|
|
||||||
//
|
|
||||||
// Created by Quentin Jin on 2019/3/4.
|
|
||||||
// Copyright © 2019 Schedule. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
extension Monthday {
|
|
||||||
|
|
||||||
/// A Boolean value indicating whether today is the monthday.
|
|
||||||
@available(*, deprecated, message: "Use date.is(_:Monthday)")
|
|
||||||
public var isToday: Bool {
|
|
||||||
let components = toDateComponents()
|
|
||||||
|
|
||||||
let m = Calendar.gregorian.component(.month, from: Date())
|
|
||||||
let d = Calendar.gregorian.component(.day, from: Date())
|
|
||||||
|
|
||||||
return m == components.month && d == components.day
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Weekday {
|
|
||||||
|
|
||||||
/// A Boolean value indicating whether today is the weekday.
|
|
||||||
@available(*, deprecated, message: "Use date.is(_:Weekday)")
|
|
||||||
public var isToday: Bool {
|
|
||||||
return Calendar.gregorian.component(.weekday, from: Date()) == rawValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Time {
|
|
||||||
|
|
||||||
/// The interval between this time and zero o'clock.
|
|
||||||
@available(*, deprecated, renamed: "intervalSinceStartOfDay")
|
|
||||||
public var intervalSinceZeroClock: Interval {
|
|
||||||
return hour.hours + minute.minutes + second.seconds + nanosecond.nanoseconds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Plan {
|
|
||||||
|
|
||||||
/// Creates a plan from an interval sequence.
|
|
||||||
/// The task will be executed after each interval in the sequence.
|
|
||||||
@available(*, deprecated, message: "Use Plan.of")
|
|
||||||
public static func from<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Interval {
|
|
||||||
return Plan.of(sequence)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a plan from a date sequence.
|
|
||||||
/// The task will be executed at each date in the sequence.
|
|
||||||
/// - Note: Returns `Plan.never` if given no parameters.
|
|
||||||
@available(*, deprecated, message: "Use Plan.of")
|
|
||||||
public static func from<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Date {
|
|
||||||
return Plan.make(sequence.makeIterator)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,6 +2,7 @@ import Foundation
|
||||||
|
|
||||||
extension Double {
|
extension Double {
|
||||||
|
|
||||||
|
/// Returns a value of this number clamped to `Int.min...Int.max`.
|
||||||
func clampedToInt() -> Int {
|
func clampedToInt() -> Int {
|
||||||
if self >= Double(Int.max) { return Int.max }
|
if self >= Double(Int.max) { return Int.max }
|
||||||
if self <= Double(Int.min) { return Int.min }
|
if self <= Double(Int.min) { return Int.min }
|
||||||
|
@ -11,26 +12,31 @@ extension Double {
|
||||||
|
|
||||||
extension Int {
|
extension Int {
|
||||||
|
|
||||||
|
/// Returns the sum of the two given values, in case of any overflow,
|
||||||
|
/// the result will be clamped to int.
|
||||||
func clampedAdding(_ other: Int) -> Int {
|
func clampedAdding(_ other: Int) -> Int {
|
||||||
return (Double(self) + Double(other)).clampedToInt()
|
return (Double(self) + Double(other)).clampedToInt()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func clampedSubtracting(_ other: Int) -> Int {
|
extension Locale {
|
||||||
return clampedAdding(-other)
|
|
||||||
}
|
static let posix = Locale(identifier: "en_US_POSIX")
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Calendar {
|
extension Calendar {
|
||||||
|
|
||||||
|
/// The gregorian calendar with `en_US_POSIX` locale.
|
||||||
static let gregorian: Calendar = {
|
static let gregorian: Calendar = {
|
||||||
var cal = Calendar(identifier: .gregorian)
|
var cal = Calendar(identifier: .gregorian)
|
||||||
cal.locale = Locale(identifier: "en_US_POSIX")
|
cal.locale = Locale.posix
|
||||||
return cal
|
return cal
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Date {
|
extension Date {
|
||||||
|
|
||||||
|
/// Zero o'clock in the morning.
|
||||||
var startOfToday: Date {
|
var startOfToday: Date {
|
||||||
return Calendar.gregorian.startOfDay(for: self)
|
return Calendar.gregorian.startOfDay(for: self)
|
||||||
}
|
}
|
||||||
|
@ -38,10 +44,17 @@ extension Date {
|
||||||
|
|
||||||
extension NSLocking {
|
extension NSLocking {
|
||||||
|
|
||||||
|
/// Executes a closure returning a value while acquiring the lock.
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
func withLock<T>(_ body: () throws -> T) rethrows -> T {
|
func withLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||||
lock()
|
lock(); defer { unlock() }
|
||||||
defer { unlock() }
|
|
||||||
return try body()
|
return try body()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes a closure while acquiring the lock.
|
||||||
|
@inline(__always)
|
||||||
|
func withLockVoid(_ body: () throws -> Void) rethrows {
|
||||||
|
lock(); defer { unlock() }
|
||||||
|
try body()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// `Interval` represents a length of time.
|
/// Type used to represent a time-based amount of time, such as '34.5 seconds'.
|
||||||
public struct Interval {
|
public struct Interval {
|
||||||
|
|
||||||
/// The length of this interval in nanoseconds.
|
/// The length of this interval in nanoseconds.
|
||||||
|
@ -12,50 +12,27 @@ public struct Interval {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Describing
|
extension Interval: Hashable { }
|
||||||
|
|
||||||
extension Interval {
|
extension Interval {
|
||||||
|
|
||||||
/// A boolean value indicating whether this interval is less than zero.
|
/// A Boolean value indicating whether this interval is less than zero.
|
||||||
///
|
///
|
||||||
/// An interval can be negative:
|
|
||||||
///
|
///
|
||||||
/// - The interval from 6:00 to 7:00 is `1.hour`,
|
/// An Interval represents a directed distance between two points
|
||||||
/// but the interval from 7:00 to 6:00 is `-1.hour`.
|
/// on the time-line and can therefore be positive, zero or negative.
|
||||||
/// In this case, `-1.hour` means **one hour ago**.
|
|
||||||
///
|
|
||||||
/// - The interval comparing `3.hour` to `1.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**
|
|
||||||
public var isNegative: Bool {
|
public var isNegative: Bool {
|
||||||
return nanoseconds < 0
|
return nanoseconds < 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See `isNegative`.
|
/// A copy of this duration with a positive length.
|
||||||
public var isPositive: Bool {
|
public var abs: Interval {
|
||||||
return nanoseconds > 0
|
return Interval(nanoseconds: Swift.abs(nanoseconds))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The absolute value of the length of this interval in nanoseconds.
|
/// A copy of this interval with the length negated.
|
||||||
public var magnitude: Double {
|
public var negated: Interval {
|
||||||
return nanoseconds.magnitude
|
return Interval(nanoseconds: -nanoseconds)
|
||||||
}
|
|
||||||
|
|
||||||
/// The additive inverse of this interval.
|
|
||||||
public var opposite: Interval {
|
|
||||||
return (-nanoseconds).nanoseconds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Interval: Hashable {
|
|
||||||
|
|
||||||
/// The hashValue of this interval.
|
|
||||||
public var hashValue: Int {
|
|
||||||
return nanoseconds.hashValue
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a boolean value indicating whether two intervals are equal.
|
|
||||||
public static func == (lhs: Interval, rhs: Interval) -> Bool {
|
|
||||||
return lhs.nanoseconds == rhs.nanoseconds
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,9 +40,9 @@ extension Interval: CustomStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this interval.
|
/// A textual representation of this interval.
|
||||||
///
|
///
|
||||||
/// Interval: 1000 nanoseconds
|
/// "Interval: 1000 nanoseconds"
|
||||||
public var description: String {
|
public var description: String {
|
||||||
return "Interval: \(nanoseconds.clampedToInt()) nanoseconds"
|
return "Interval: \(nanoseconds.clampedToInt()) nanosecond(s)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,24 +50,29 @@ extension Interval: CustomDebugStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this interval for debugging.
|
/// A textual representation of this interval for debugging.
|
||||||
///
|
///
|
||||||
/// Interval: 1000 nanoseconds
|
/// "Interval: 1000 nanoseconds"
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
return description
|
return description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Comparing
|
// MARK: - Comparing
|
||||||
|
|
||||||
extension Interval: Comparable {
|
extension Interval: Comparable {
|
||||||
|
|
||||||
/// Compares two intervals.
|
/// Compares two intervals and returns a comparison result value
|
||||||
|
/// that indicates the sort order of two intervals.
|
||||||
///
|
///
|
||||||
/// A positive interval is always ordered ascending to a negative interval.
|
/// A positive interval is always ordered ascending to a negative interval.
|
||||||
public func compare(_ other: Interval) -> ComparisonResult {
|
public func compare(_ other: Interval) -> ComparisonResult {
|
||||||
let now = Date()
|
let d = nanoseconds - other.nanoseconds
|
||||||
return now.adding(self).compare(now.adding(other))
|
|
||||||
|
if d < 0 { return .orderedAscending }
|
||||||
|
if d > 0 { return .orderedDescending }
|
||||||
|
return .orderedSame
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a boolean value indicating whether the first interval is
|
/// Returns a Boolean value indicating whether the first interval is
|
||||||
/// less than the second interval.
|
/// less than the second interval.
|
||||||
///
|
///
|
||||||
/// A negative interval is always less than a positive interval.
|
/// A negative interval is always less than a positive interval.
|
||||||
|
@ -98,47 +80,23 @@ extension Interval: Comparable {
|
||||||
return lhs.compare(rhs) == .orderedAscending
|
return lhs.compare(rhs) == .orderedAscending
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a boolean value indicating whether this interval is longer
|
/// Returns a Boolean value indicating whether this interval is longer
|
||||||
/// than the given value.
|
/// than the given interval.
|
||||||
public func isLonger(than other: Interval) -> Bool {
|
public func isLonger(than other: Interval) -> Bool {
|
||||||
return magnitude > other.magnitude
|
return abs > other.abs
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a boolean value indicating whether this interval is shorter
|
/// Returns a Boolean value indicating whether this interval is shorter
|
||||||
/// than the given value.
|
/// than the given interval.
|
||||||
public func isShorter(than other: Interval) -> Bool {
|
public func isShorter(than other: Interval) -> Bool {
|
||||||
return magnitude < other.magnitude
|
return abs < other.abs
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the longest interval of the given values.
|
|
||||||
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given no parameters.
|
|
||||||
public static func longest(_ intervals: Interval...) -> Interval {
|
|
||||||
return longest(intervals)!
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the longest interval of the given values.
|
|
||||||
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given an empty array.
|
|
||||||
public static func longest(_ intervals: [Interval]) -> Interval? {
|
|
||||||
guard !intervals.isEmpty else { return nil }
|
|
||||||
return intervals.sorted(by: { $0.magnitude > $1.magnitude })[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the shortest interval of the given values.
|
|
||||||
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given no parameters.
|
|
||||||
public static func shortest(_ intervals: Interval...) -> Interval {
|
|
||||||
return shortest(intervals)!
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the shortest interval of the given values.
|
|
||||||
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given an empty array.
|
|
||||||
public static func shortest(_ intervals: [Interval]) -> Interval? {
|
|
||||||
guard !intervals.isEmpty else { return nil }
|
|
||||||
return intervals.sorted(by: { $0.magnitude < $1.magnitude })[0]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Adding & Subtracting
|
// MARK: - Adding & Subtracting
|
||||||
|
|
||||||
extension Interval {
|
extension Interval {
|
||||||
|
|
||||||
/// Returns a new interval by multipling this interval by the given number.
|
/// Returns a new interval by multipling this interval by the given number.
|
||||||
///
|
///
|
||||||
/// 1.hour * 2 == 2.hours
|
/// 1.hour * 2 == 2.hours
|
||||||
|
@ -152,13 +110,6 @@ extension Interval {
|
||||||
public func adding(_ other: Interval) -> Interval {
|
public func adding(_ other: Interval) -> Interval {
|
||||||
return Interval(nanoseconds: nanoseconds + other.nanoseconds)
|
return Interval(nanoseconds: nanoseconds + other.nanoseconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new interval by subtracting the given interval from this interval.
|
|
||||||
///
|
|
||||||
/// 2.hours - 1.hour == 1.hour
|
|
||||||
public func subtracting(_ other: Interval) -> Interval {
|
|
||||||
return Interval(nanoseconds: nanoseconds - other.nanoseconds)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Operators
|
// MARK: - Operators
|
||||||
|
@ -182,27 +133,23 @@ extension Interval {
|
||||||
///
|
///
|
||||||
/// 2.hours - 1.hour == 1.hour
|
/// 2.hours - 1.hour == 1.hour
|
||||||
public static func - (lhs: Interval, rhs: Interval) -> Interval {
|
public static func - (lhs: Interval, rhs: Interval) -> Interval {
|
||||||
return lhs.subtracting(rhs)
|
return lhs.adding(rhs.negated)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds two intervals and stores the result in the first interval.
|
/// Adds two intervals and stores the result in the left interval.
|
||||||
public static func += (lhs: inout Interval, rhs: Interval) {
|
public static func += (lhs: inout Interval, rhs: Interval) {
|
||||||
lhs = lhs.adding(rhs)
|
lhs = lhs.adding(rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the additive inverse of the specified interval.
|
/// Returns the additive inverse of the specified interval.
|
||||||
public prefix static func - (interval: Interval) -> Interval {
|
public prefix static func - (interval: Interval) -> Interval {
|
||||||
return interval.opposite
|
return interval.negated
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Sugars
|
// MARK: - Sugars
|
||||||
extension Interval {
|
|
||||||
|
|
||||||
/// Creates an interval from the given number of seconds.
|
extension Interval {
|
||||||
public init(seconds: Double) {
|
|
||||||
self.init(nanoseconds: seconds * pow(10, 9))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The length of this interval in nanoseconds.
|
/// The length of this interval in nanoseconds.
|
||||||
public func asNanoseconds() -> Double {
|
public func asNanoseconds() -> Double {
|
||||||
|
@ -253,6 +200,7 @@ public protocol IntervalConvertible {
|
||||||
|
|
||||||
extension Int: IntervalConvertible {
|
extension Int: IntervalConvertible {
|
||||||
|
|
||||||
|
/// Creates an interval from this amount of nanoseconds.
|
||||||
public var nanoseconds: Interval {
|
public var nanoseconds: Interval {
|
||||||
return Interval(nanoseconds: Double(self))
|
return Interval(nanoseconds: Double(self))
|
||||||
}
|
}
|
||||||
|
@ -260,6 +208,7 @@ extension Int: IntervalConvertible {
|
||||||
|
|
||||||
extension Double: IntervalConvertible {
|
extension Double: IntervalConvertible {
|
||||||
|
|
||||||
|
/// Creates an interval from this amount of nanoseconds.
|
||||||
public var nanoseconds: Interval {
|
public var nanoseconds: Interval {
|
||||||
return Interval(nanoseconds: self)
|
return Interval(nanoseconds: self)
|
||||||
}
|
}
|
||||||
|
@ -267,80 +216,96 @@ extension Double: IntervalConvertible {
|
||||||
|
|
||||||
extension IntervalConvertible {
|
extension IntervalConvertible {
|
||||||
|
|
||||||
|
// Alias for `nanoseconds`.
|
||||||
public var nanosecond: Interval {
|
public var nanosecond: Interval {
|
||||||
return nanoseconds
|
return nanoseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alias for `microseconds`.
|
||||||
public var microsecond: Interval {
|
public var microsecond: Interval {
|
||||||
return microseconds
|
return microseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an interval from this amount of microseconds.
|
||||||
public var microseconds: Interval {
|
public var microseconds: Interval {
|
||||||
return nanoseconds * pow(10, 3)
|
return nanoseconds * pow(10, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Alias for `milliseconds`.
|
||||||
public var millisecond: Interval {
|
public var millisecond: Interval {
|
||||||
return milliseconds
|
return milliseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an interval from this amount of milliseconds.
|
||||||
public var milliseconds: Interval {
|
public var milliseconds: Interval {
|
||||||
return nanoseconds * pow(10, 6)
|
return microseconds * pow(10, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Alias for `second`.
|
||||||
public var second: Interval {
|
public var second: Interval {
|
||||||
return seconds
|
return seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an interval from this amount of seconds.
|
||||||
public var seconds: Interval {
|
public var seconds: Interval {
|
||||||
return nanoseconds * pow(10, 9)
|
return milliseconds * pow(10, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Alias for `minute`.
|
||||||
public var minute: Interval {
|
public var minute: Interval {
|
||||||
return minutes
|
return minutes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an interval from this amount of minutes.
|
||||||
public var minutes: Interval {
|
public var minutes: Interval {
|
||||||
return seconds * 60
|
return seconds * 60
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Alias for `hours`.
|
||||||
public var hour: Interval {
|
public var hour: Interval {
|
||||||
return hours
|
return hours
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an interval from this amount of hours.
|
||||||
public var hours: Interval {
|
public var hours: Interval {
|
||||||
return minutes * 60
|
return minutes * 60
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Alias for `days`.
|
||||||
public var day: Interval {
|
public var day: Interval {
|
||||||
return days
|
return days
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an interval from this amount of days.
|
||||||
public var days: Interval {
|
public var days: Interval {
|
||||||
return hours * 24
|
return hours * 24
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Alias for `weeks`.
|
||||||
public var week: Interval {
|
public var week: Interval {
|
||||||
return weeks
|
return weeks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an interval from this amount of weeks.
|
||||||
public var weeks: Interval {
|
public var weeks: Interval {
|
||||||
return days * 7
|
return days * 7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Date
|
// MARK: - Date
|
||||||
|
|
||||||
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.
|
||||||
///
|
///
|
||||||
/// If this date is earlier than now, this interval will be negative.
|
/// If this date is earlier than now, the interval will be negative.
|
||||||
public var intervalSinceNow: Interval {
|
public var intervalSinceNow: Interval {
|
||||||
return timeIntervalSinceNow.seconds
|
return timeIntervalSinceNow.seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the interval between this date and the given date.
|
/// Returns the interval between this date and the given date.
|
||||||
///
|
///
|
||||||
/// If this date is earlier than the given date, this interval will be negative.
|
/// If this date is earlier than the given date, the interval will be negative.
|
||||||
public func interval(since date: Date) -> Interval {
|
public func interval(since date: Date) -> Interval {
|
||||||
return timeIntervalSince(date).seconds
|
return timeIntervalSince(date).seconds
|
||||||
}
|
}
|
||||||
|
@ -350,17 +315,19 @@ extension Date {
|
||||||
return addingTimeInterval(interval.asSeconds())
|
return addingTimeInterval(interval.asSeconds())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a date with an interval added to it.
|
/// Returns a new date by adding an interval to the date.
|
||||||
public static func + (lhs: Date, rhs: Interval) -> Date {
|
public static func + (lhs: Date, rhs: Interval) -> Date {
|
||||||
return lhs.adding(rhs)
|
return lhs.adding(rhs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - DispatchSourceTimer
|
// MARK: - DispatchSourceTimer
|
||||||
|
|
||||||
extension DispatchSourceTimer {
|
extension DispatchSourceTimer {
|
||||||
|
|
||||||
|
/// Schedule this timer after the given interval.
|
||||||
func schedule(after timeout: Interval) {
|
func schedule(after timeout: Interval) {
|
||||||
guard !timeout.isNegative else { return }
|
if timeout.isNegative { return }
|
||||||
let ns = timeout.nanoseconds.clampedToInt()
|
let ns = timeout.nanoseconds.clampedToInt()
|
||||||
schedule(wallDeadline: .now() + DispatchTimeInterval.nanoseconds(ns))
|
schedule(wallDeadline: .now() + DispatchTimeInterval.nanoseconds(ns))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// `Monthday` represents a day of a month.
|
/// `Monthday` represents the combination of a month and day-of-month.
|
||||||
public enum Monthday {
|
public enum Monthday {
|
||||||
|
|
||||||
case january(Int)
|
case january(Int)
|
||||||
|
@ -27,9 +27,9 @@ public enum Monthday {
|
||||||
|
|
||||||
case december(Int)
|
case december(Int)
|
||||||
|
|
||||||
/// Returns a dateComponenets of the monthday, using gregorian calender and
|
/// Returns a dateComponenets of this monthday, using gregorian calender and
|
||||||
/// current time zone.
|
/// current time zone.
|
||||||
public func toDateComponents() -> DateComponents {
|
public func asDateComponents(_ timeZone: TimeZone = .current) -> DateComponents {
|
||||||
var month, day: Int
|
var month, day: Int
|
||||||
switch self {
|
switch self {
|
||||||
case .january(let n): month = 1; day = n
|
case .january(let n): month = 1; day = n
|
||||||
|
@ -47,18 +47,17 @@ public enum Monthday {
|
||||||
}
|
}
|
||||||
return DateComponents(
|
return DateComponents(
|
||||||
calendar: Calendar.gregorian,
|
calendar: Calendar.gregorian,
|
||||||
timeZone: TimeZone.current,
|
timeZone: timeZone,
|
||||||
month: month,
|
month: month,
|
||||||
day: day
|
day: day)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Date {
|
extension Date {
|
||||||
|
|
||||||
/// Returns a boolean value indicating whether today is the monthday.
|
/// Returns a Boolean value indicating whether this date is the monthday in current time zone..
|
||||||
public func `is`(_ monthday: Monthday) -> Bool {
|
public func `is`(_ monthday: Monthday, in timeZone: TimeZone = .current) -> Bool {
|
||||||
let components = monthday.toDateComponents()
|
let components = monthday.asDateComponents(timeZone)
|
||||||
|
|
||||||
let m = Calendar.gregorian.component(.month, from: self)
|
let m = Calendar.gregorian.component(.month, from: self)
|
||||||
let d = Calendar.gregorian.component(.day, from: self)
|
let d = Calendar.gregorian.component(.day, from: self)
|
||||||
|
@ -72,7 +71,7 @@ extension Monthday: CustomStringConvertible {
|
||||||
///
|
///
|
||||||
/// "Monthday: May 1st"
|
/// "Monthday: May 1st"
|
||||||
public var description: String {
|
public var description: String {
|
||||||
let components = toDateComponents()
|
let components = asDateComponents()
|
||||||
|
|
||||||
let m = components.month!
|
let m = components.month!
|
||||||
let d = components.day!
|
let d = components.day!
|
||||||
|
@ -80,6 +79,7 @@ extension Monthday: CustomStringConvertible {
|
||||||
let ms = Calendar.gregorian.monthSymbols[m - 1]
|
let ms = Calendar.gregorian.monthSymbols[m - 1]
|
||||||
|
|
||||||
let fmt = NumberFormatter()
|
let fmt = NumberFormatter()
|
||||||
|
fmt.locale = Locale.posix
|
||||||
fmt.numberStyle = .ordinal
|
fmt.numberStyle = .ordinal
|
||||||
let ds = fmt.string(from: NSNumber(value: d))!
|
let ds = fmt.string(from: NSNumber(value: d))!
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// `Period` represents a period of time defined in terms of fields.
|
/// Type used to represent a date-based amount of time in the ISO-8601 calendar system,
|
||||||
|
/// such as '2 years, 3 months and 4 days'.
|
||||||
///
|
///
|
||||||
/// It's a little different from `Interval`:
|
/// It's a little different from `Interval`:
|
||||||
///
|
///
|
||||||
|
@ -28,6 +29,7 @@ public struct Period {
|
||||||
|
|
||||||
public private(set) var nanoseconds: Int
|
public private(set) var nanoseconds: Int
|
||||||
|
|
||||||
|
/// Initializes a period value, optional sepcifying values for its fields.
|
||||||
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,
|
||||||
nanoseconds: Int = 0) {
|
nanoseconds: Int = 0) {
|
||||||
|
@ -50,104 +52,124 @@ public struct Period {
|
||||||
/// Period.registerQuantifier("fifty", for: 15)
|
/// Period.registerQuantifier("fifty", for: 15)
|
||||||
/// let period = Period("fifty minutes")
|
/// let period = Period("fifty minutes")
|
||||||
public static func registerQuantifier(_ word: String, for number: Int) {
|
public static func registerQuantifier(_ word: String, for number: Int) {
|
||||||
quantifiers.write { $0[word] = number }
|
quantifiers.writeVoid { $0[word] = number }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a period from a natural expression.
|
/// Initializes a period from a natural expression.
|
||||||
///
|
///
|
||||||
/// Period("one second") => Period(seconds: 1)
|
/// Period("one second") -> Period(seconds: 1)
|
||||||
/// Period("two hours and ten minutes") => Period(hours: 2, minutes: 10)
|
/// 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)
|
/// Period("1 year, 2 months and 3 days") -> Period(years: 1, months: 2, days: 3)
|
||||||
public init?(_ string: String) {
|
public init?(_ string: String) {
|
||||||
var period = string
|
var str = string
|
||||||
for (word, number) in Period.quantifiers.read({ $0 }) {
|
for (word, number) in Period.quantifiers.read({ $0 }) {
|
||||||
period = period.replacingOccurrences(of: word, with: "\(number)")
|
str = str.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
|
// swiftlint:disable force_try
|
||||||
for pair in period.split(separator: "$").map({ $0.split(separator: " ") }) {
|
let regexp = try! NSRegularExpression(pattern: "( and |, )")
|
||||||
guard pair.count == 2 else { return nil }
|
|
||||||
guard let number = Int(pair[0]) else { return nil }
|
let mark: Character = "秋"
|
||||||
var unit = String(pair[1])
|
str = regexp.stringByReplacingMatches(
|
||||||
|
in: str,
|
||||||
|
range: NSRange(str.startIndex..., in: str),
|
||||||
|
withTemplate: String(mark)
|
||||||
|
)
|
||||||
|
|
||||||
|
var period = 0.year
|
||||||
|
for pair in str.split(separator: mark).map({ $0.split(separator: " ") }) {
|
||||||
|
guard
|
||||||
|
pair.count == 2,
|
||||||
|
let number = Int(pair[0])
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var unit = pair[1]
|
||||||
if unit.last == "s" { unit.removeLast() }
|
if unit.last == "s" { unit.removeLast() }
|
||||||
switch unit {
|
switch unit {
|
||||||
case "year": result = result + number.years
|
case "year": period = period + number.years
|
||||||
case "month": result = result + number.months
|
case "month": period = period + number.months
|
||||||
case "day": result = result + number.days
|
case "day": period = period + number.days
|
||||||
case "week": result = result + (number * 7).days
|
case "week": period = period + (number * 7).days
|
||||||
case "hour": result = result + number.hours
|
case "hour": period = period + number.hours
|
||||||
case "minute": result = result + number.minutes
|
case "minute": period = period + number.minutes
|
||||||
case "second": result = result + number.second
|
case "second": period = period + number.second
|
||||||
case "nanosecond": result = result + number.nanosecond
|
case "nanosecond": period = period + number.nanosecond
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self = result.tidied(to: .day)
|
self = period
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new period by adding a period to this period.
|
/// Returns a new period by adding the given period to this period.
|
||||||
public func adding(_ other: Period) -> Period {
|
public func adding(_ other: Period) -> Period {
|
||||||
return Period(years: years.clampedAdding(other.years),
|
return Period(
|
||||||
months: months.clampedAdding(other.months),
|
years: years.clampedAdding(other.years),
|
||||||
days: days.clampedAdding(other.days),
|
months: months.clampedAdding(other.months),
|
||||||
hours: hours.clampedAdding(other.hours),
|
days: days.clampedAdding(other.days),
|
||||||
minutes: minutes.clampedAdding(other.minutes),
|
hours: hours.clampedAdding(other.hours),
|
||||||
seconds: seconds.clampedAdding(other.seconds),
|
minutes: minutes.clampedAdding(other.minutes),
|
||||||
nanoseconds: nanoseconds.clampedAdding(other.nanoseconds))
|
seconds: seconds.clampedAdding(other.seconds),
|
||||||
|
nanoseconds: nanoseconds.clampedAdding(other.nanoseconds))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new period by adding an interval to this period.
|
/// Returns a new period by adding an interval to the period.
|
||||||
|
///
|
||||||
|
/// The return value will be tidied to `day` aotumatically.
|
||||||
public func adding(_ interval: Interval) -> Period {
|
public func adding(_ interval: Interval) -> Period {
|
||||||
return Period(
|
return Period(
|
||||||
years: years, months: months, days: days,
|
years: years, months: months, days: days,
|
||||||
hours: hours, minutes: minutes, seconds: seconds,
|
hours: hours, minutes: minutes, seconds: seconds,
|
||||||
nanoseconds: nanoseconds.clampedAdding(interval.nanoseconds.clampedToInt())
|
nanoseconds: nanoseconds.clampedAdding(interval.nanoseconds.clampedToInt()))
|
||||||
).tidied(to: .day)
|
.tidied(to: .day)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds two periods and produces their sum.
|
/// Returns a new period by adding the right period to the left period.
|
||||||
|
///
|
||||||
|
/// Period(days: 1) + Period(days: 1) -> Period(days: 2)
|
||||||
public static func + (lhs: Period, rhs: Period) -> Period {
|
public static func + (lhs: Period, rhs: Period) -> Period {
|
||||||
return lhs.adding(rhs)
|
return lhs.adding(rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a period with an interval added to it.
|
/// Returns a new period by adding an interval to the period.
|
||||||
|
///
|
||||||
|
/// The return value will be tidied to `day` aotumatically.
|
||||||
public static func + (lhs: Period, rhs: Interval) -> Period {
|
public static func + (lhs: Period, rhs: Interval) -> Period {
|
||||||
return lhs.adding(rhs)
|
return lhs.adding(rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type to be used as tidying parameter.
|
/// Represents the tidy level.
|
||||||
public enum Unit {
|
public enum TideLevel {
|
||||||
case day, hour, minute, second, nanosecond
|
case day, hour, minute, second, nanosecond
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a tidied period.
|
/// Returns the tidied period.
|
||||||
///
|
///
|
||||||
/// Period(hours: 25).tidied(to .day) => Period(days: 1, hours: 1)
|
/// Period(hours: 25).tidied(to .day) => Period(days: 1, hours: 1)
|
||||||
public func tidied(to unit: Unit) -> Period {
|
public func tidied(to level: TideLevel) -> Period {
|
||||||
var period = self
|
var period = self
|
||||||
if case .nanosecond = unit { return period }
|
|
||||||
|
if case .nanosecond = level { return period }
|
||||||
|
|
||||||
if period.nanoseconds.magnitude >= UInt(1.second.nanoseconds) {
|
if period.nanoseconds.magnitude >= UInt(1.second.nanoseconds) {
|
||||||
period.seconds += period.nanoseconds / Int(1.second.nanoseconds)
|
period.seconds += period.nanoseconds / Int(1.second.nanoseconds)
|
||||||
period.nanoseconds %= Int(1.second.nanoseconds)
|
period.nanoseconds %= Int(1.second.nanoseconds)
|
||||||
}
|
}
|
||||||
|
if case .second = level { return period }
|
||||||
|
|
||||||
if case .second = unit { return period }
|
|
||||||
if period.seconds.magnitude >= 60 {
|
if period.seconds.magnitude >= 60 {
|
||||||
period.minutes += period.seconds / 60
|
period.minutes += period.seconds / 60
|
||||||
period.seconds %= 60
|
period.seconds %= 60
|
||||||
}
|
}
|
||||||
|
if case .minute = level { return period }
|
||||||
|
|
||||||
if case .minute = unit { return period }
|
|
||||||
if period.minutes.magnitude >= 60 {
|
if period.minutes.magnitude >= 60 {
|
||||||
period.hours += period.minutes / 60
|
period.hours += period.minutes / 60
|
||||||
period.minutes %= 60
|
period.minutes %= 60
|
||||||
}
|
}
|
||||||
|
if case .hour = level { return period }
|
||||||
|
|
||||||
if case .hour = unit { return period }
|
|
||||||
if period.hours.magnitude >= 24 {
|
if period.hours.magnitude >= 24 {
|
||||||
period.days += period.hours / 24
|
period.days += period.hours / 24
|
||||||
period.hours %= 24
|
period.hours %= 24
|
||||||
|
@ -155,12 +177,20 @@ public struct Period {
|
||||||
return period
|
return period
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a dateComponenets of the period, using gregorian calender and
|
/// Returns a dateComponenets of this period, using gregorian calender and
|
||||||
/// current time zone.
|
/// current time zone.
|
||||||
public func toDateComponents() -> DateComponents {
|
public func asDateComponents(_ timeZone: TimeZone = .current) -> DateComponents {
|
||||||
return DateComponents(year: years, month: months, day: days,
|
return DateComponents(
|
||||||
hour: hours, minute: minutes, second: seconds,
|
calendar: Calendar.gregorian,
|
||||||
nanosecond: nanoseconds)
|
timeZone: timeZone,
|
||||||
|
year: years,
|
||||||
|
month: months,
|
||||||
|
day: days,
|
||||||
|
hour: hours,
|
||||||
|
minute: minutes,
|
||||||
|
second: seconds,
|
||||||
|
nanosecond: nanoseconds
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,10 +198,10 @@ extension Date {
|
||||||
|
|
||||||
/// Returns a new date by adding a period to this date.
|
/// Returns a new date by adding a period to this date.
|
||||||
public func adding(_ period: Period) -> Date {
|
public func adding(_ period: Period) -> Date {
|
||||||
return Calendar.gregorian.date(byAdding: period.toDateComponents(), to: self) ?? .distantFuture
|
return Calendar.gregorian.date(byAdding: period.asDateComponents(), to: self) ?? .distantFuture
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a date with a period added to it.
|
/// Returns a new date by adding a period to this date.
|
||||||
public static func + (lhs: Date, rhs: Period) -> Date {
|
public static func + (lhs: Date, rhs: Period) -> Date {
|
||||||
return lhs.adding(rhs)
|
return lhs.adding(rhs)
|
||||||
}
|
}
|
||||||
|
@ -179,18 +209,22 @@ extension Date {
|
||||||
|
|
||||||
extension Int {
|
extension Int {
|
||||||
|
|
||||||
|
/// Creates a period from this amount of years.
|
||||||
public var years: Period {
|
public var years: Period {
|
||||||
return Period(years: self)
|
return Period(years: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Alias for `years`.
|
||||||
public var year: Period {
|
public var year: Period {
|
||||||
return years
|
return years
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a period from this amount of month.
|
||||||
public var months: Period {
|
public var months: Period {
|
||||||
return Period(months: self)
|
return Period(months: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Alias for `month`.
|
||||||
public var month: Period {
|
public var month: Period {
|
||||||
return months
|
return months
|
||||||
}
|
}
|
||||||
|
@ -200,7 +234,7 @@ extension Period: CustomStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this period.
|
/// A textual representation of this period.
|
||||||
///
|
///
|
||||||
/// Period: 1 year(s) 2 month(s) 3 day(s)
|
/// "Period: 1 year(s) 2 month(s) 3 day(s)"
|
||||||
public var description: String {
|
public var description: String {
|
||||||
let period = tidied(to: .day)
|
let period = tidied(to: .day)
|
||||||
var desc = "Period:"
|
var desc = "Period:"
|
||||||
|
@ -219,7 +253,7 @@ extension Period: CustomDebugStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this period for debugging.
|
/// A textual representation of this period for debugging.
|
||||||
///
|
///
|
||||||
/// Period: 1 year(s) 2 month(s) 3 day(s)
|
/// "Period: 1 year(s) 2 month(s) 3 day(s)"
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
return description
|
return description
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +1,54 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// `Plan` represents a plan that gives time at which a task should be
|
/// `Plan` represents a sequence of times at which a task should be
|
||||||
/// executed.
|
/// executed.
|
||||||
///
|
///
|
||||||
/// `Plan` is `Interval` based.
|
/// `Plan` is `Interval` based.
|
||||||
public struct Plan {
|
public struct Plan: Sequence {
|
||||||
|
|
||||||
private var iSeq: AnySequence<Interval>
|
private var seq: AnySequence<Interval>
|
||||||
|
|
||||||
private init<S>(_ sequence: S) where S: Sequence, S.Element == Interval {
|
private init<S>(_ sequence: S) where S: Sequence, S.Element == Interval {
|
||||||
iSeq = AnySequence(sequence)
|
seq = AnySequence(sequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeIterator() -> AnyIterator<Interval> {
|
/// Returns an iterator over the interval of this sequence.
|
||||||
return iSeq.makeIterator()
|
public func makeIterator() -> AnyIterator<Interval> {
|
||||||
|
return seq.makeIterator()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Schedules a task with this plan.
|
/// Schedules a task with this plan.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - queue: The queue to which the task will be dispatched.
|
/// - queue: The dispatch queue to which the action should be dispatched.
|
||||||
/// - onElapse: The action to do when time is out.
|
/// - action: A block to be executed when time is up.
|
||||||
/// - Returns: The task just created.
|
/// - Returns: The task just created.
|
||||||
public func `do`(queue: DispatchQueue,
|
public func `do`(
|
||||||
onElapse: @escaping (Task) -> Void) -> Task {
|
queue: DispatchQueue,
|
||||||
return Task(plan: self, queue: queue, onElapse: onElapse)
|
action: @escaping (Task) -> Void
|
||||||
|
) -> Task {
|
||||||
|
return Task(plan: self, queue: queue, action: action)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Schedules a task with this plan.
|
/// Schedules a task with this plan.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - queue: The queue to which the task will be dispatched.
|
/// - queue: The dispatch queue to which the action should be dispatched.
|
||||||
/// - onElapse: The action to do when time is out.
|
/// - action: A block to be executed when time is up.
|
||||||
/// - Returns: The task just created.
|
/// - Returns: The task just created.
|
||||||
public func `do`(queue: DispatchQueue,
|
public func `do`(
|
||||||
onElapse: @escaping () -> Void) -> Task {
|
queue: DispatchQueue,
|
||||||
return self.do(queue: queue, onElapse: { (_) in onElapse() })
|
action: @escaping () -> Void
|
||||||
|
) -> Task {
|
||||||
|
return self.do(queue: queue, action: { (_) in action() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Plan {
|
extension Plan {
|
||||||
|
|
||||||
/// Creates a plan from a `makeUnderlyingIterator()` method.
|
/// Creates a plan whose `makeIterator()` method forwards to makeUnderlyingIterator.
|
||||||
///
|
///
|
||||||
/// The task will be executed after each interval produced by the iterator
|
/// The task will be executed after each interval.
|
||||||
/// that `makeUnderlyingIterator` returns.
|
|
||||||
///
|
///
|
||||||
/// For example:
|
/// For example:
|
||||||
///
|
///
|
||||||
|
@ -52,11 +56,11 @@ extension Plan {
|
||||||
/// var i = 0
|
/// var i = 0
|
||||||
/// return AnyIterator {
|
/// return AnyIterator {
|
||||||
/// i += 1
|
/// i += 1
|
||||||
/// return i
|
/// return i // 1, 2, 3, ...
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// plan.do {
|
/// plan.do {
|
||||||
/// print(Date())
|
/// logTimestamp()
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// > "2001-01-01 00:00:00"
|
/// > "2001-01-01 00:00:00"
|
||||||
|
@ -64,20 +68,22 @@ extension Plan {
|
||||||
/// > "2001-01-01 00:00:03"
|
/// > "2001-01-01 00:00:03"
|
||||||
/// > "2001-01-01 00:00:06"
|
/// > "2001-01-01 00:00:06"
|
||||||
/// ...
|
/// ...
|
||||||
public static func make<I>(_ makeUnderlyingIterator: @escaping () -> I) -> Plan where I: IteratorProtocol, I.Element == Interval {
|
public static func make<I>(
|
||||||
|
_ makeUnderlyingIterator: @escaping () -> I
|
||||||
|
) -> Plan where I: IteratorProtocol, I.Element == Interval {
|
||||||
return Plan(AnySequence(makeUnderlyingIterator))
|
return Plan(AnySequence(makeUnderlyingIterator))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan from a list of intervals.
|
/// Creates a plan from a list of intervals.
|
||||||
|
///
|
||||||
/// The task will be executed after each interval in the array.
|
/// The task will be executed after each interval in the array.
|
||||||
/// - Note: Returns `Plan.never` if given no parameters.
|
|
||||||
public static func of(_ intervals: Interval...) -> Plan {
|
public static func of(_ intervals: Interval...) -> Plan {
|
||||||
return Plan.of(intervals)
|
return Plan.of(intervals)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan from a list of intervals.
|
/// Creates a plan from a list of intervals.
|
||||||
|
///
|
||||||
/// The task will be executed after each interval in the array.
|
/// The task will be executed after each interval in the array.
|
||||||
/// - Note: Returns `Plan.never` if given an empty array.
|
|
||||||
public static func of<S>(_ intervals: S) -> Plan where S: Sequence, S.Element == Interval {
|
public static func of<S>(_ intervals: S) -> Plan where S: Sequence, S.Element == Interval {
|
||||||
return Plan(intervals)
|
return Plan(intervals)
|
||||||
}
|
}
|
||||||
|
@ -85,10 +91,9 @@ extension Plan {
|
||||||
|
|
||||||
extension Plan {
|
extension Plan {
|
||||||
|
|
||||||
/// Creates a plan from a `makeUnderlyingIterator()` method.
|
/// Creates a plan whose `makeIterator()` method forwards to makeUnderlyingIterator.
|
||||||
///
|
///
|
||||||
/// The task will be executed at each date
|
/// The task will be executed at each date.
|
||||||
/// produced by the iterator that `makeUnderlyingIterator` returns.
|
|
||||||
///
|
///
|
||||||
/// For example:
|
/// For example:
|
||||||
///
|
///
|
||||||
|
@ -97,40 +102,44 @@ extension Plan {
|
||||||
/// return Date().addingTimeInterval(3)
|
/// return Date().addingTimeInterval(3)
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// print("now:", Date())
|
///
|
||||||
/// plan.do {
|
/// plan.do {
|
||||||
/// print("task", Date())
|
/// logTimestamp()
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// > "now: 2001-01-01 00:00:00"
|
/// > "2001-01-01 00:00:00"
|
||||||
/// > "task: 2001-01-01 00:00:03"
|
/// > "2001-01-01 00:00:03"
|
||||||
|
/// > "2001-01-01 00:00:06"
|
||||||
|
/// > "2001-01-01 00:00:09"
|
||||||
/// ...
|
/// ...
|
||||||
///
|
///
|
||||||
/// You are not supposed to return `Date()` in making interator.
|
/// You should not return `Date()` in making iterator.
|
||||||
/// If you want to execute a task immediately,
|
/// If you want to execute a task immediately, use `Plan.now`.
|
||||||
/// use `Plan.now` then `concat` another plan instead.
|
public static func make<I>(
|
||||||
public static func make<I>(_ makeUnderlyingIterator: @escaping () -> I) -> Plan where I: IteratorProtocol, I.Element == Date {
|
_ makeUnderlyingIterator: @escaping () -> I
|
||||||
|
) -> Plan where I: IteratorProtocol, I.Element == Date {
|
||||||
return Plan.make { () -> AnyIterator<Interval> in
|
return Plan.make { () -> AnyIterator<Interval> in
|
||||||
var iterator = makeUnderlyingIterator()
|
var iterator = makeUnderlyingIterator()
|
||||||
var last: Date!
|
var prev: Date!
|
||||||
return AnyIterator {
|
return AnyIterator {
|
||||||
last = last ?? Date()
|
prev = prev ?? Date()
|
||||||
guard let next = iterator.next() else { return nil }
|
guard let next = iterator.next() else { return nil }
|
||||||
defer { last = next }
|
defer { prev = next }
|
||||||
return next.interval(since: last)
|
return next.interval(since: prev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan from a list of dates.
|
/// Creates a plan from a list of dates.
|
||||||
|
///
|
||||||
/// The task will be executed at each date in the array.
|
/// The task will be executed at each date in the array.
|
||||||
public static func of(_ dates: Date...) -> Plan {
|
public static func of(_ dates: Date...) -> Plan {
|
||||||
return Plan.of(dates)
|
return Plan.of(dates)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan from a list of dates.
|
/// Creates a plan from a list of dates.
|
||||||
|
///
|
||||||
/// The task will be executed at each date in the array.
|
/// The task will be executed at each date in the array.
|
||||||
/// - Note: Returns `Plan.never` if given no parameters.
|
|
||||||
public static func of<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Date {
|
public static func of<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Date {
|
||||||
return Plan.make(sequence.makeIterator)
|
return Plan.make(sequence.makeIterator)
|
||||||
}
|
}
|
||||||
|
@ -139,13 +148,13 @@ extension Plan {
|
||||||
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 last: Date!
|
var prev: Date!
|
||||||
return AnyIterator {
|
return AnyIterator {
|
||||||
last = last ?? Date()
|
prev = prev ?? Date()
|
||||||
guard let interval = iterator.next() else { return nil }
|
guard let interval = iterator.next() else { return nil }
|
||||||
// swiftlint:disable shorthand_operator
|
// swiftlint:disable shorthand_operator
|
||||||
last = last + interval
|
prev = prev + interval
|
||||||
return last
|
return prev
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,27 +162,27 @@ extension Plan {
|
||||||
|
|
||||||
extension Plan {
|
extension Plan {
|
||||||
|
|
||||||
/// A plan with a distant past date.
|
/// A plan of a distant past date.
|
||||||
public static var distantPast: Plan {
|
public static var distantPast: Plan {
|
||||||
return Plan.of(Date.distantPast)
|
return Plan.of(Date.distantPast)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A plan with a distant future date.
|
/// A plan of a distant future date.
|
||||||
public static var distantFuture: Plan {
|
public static var distantFuture: Plan {
|
||||||
return Plan.of(Date.distantFuture)
|
return Plan.of(Date.distantFuture)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A plan that is never going to happen.
|
/// A plan that will never happen.
|
||||||
public static var never: Plan {
|
public static var never: Plan {
|
||||||
return Plan.make {
|
return Plan.make {
|
||||||
AnyIterator<Date> { nil }
|
AnyIterator<Interval> { nil }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Plan {
|
extension Plan {
|
||||||
|
|
||||||
/// Returns a new plan by concatenating a plan to this plan.
|
/// Returns a new plan by concatenating the given plan to this plan.
|
||||||
///
|
///
|
||||||
/// For example:
|
/// For example:
|
||||||
///
|
///
|
||||||
|
@ -194,40 +203,44 @@ extension Plan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new plan by merging a plan to this plan.
|
/// Returns a new plan by merging the given plan to this plan.
|
||||||
///
|
///
|
||||||
/// For example:
|
/// For example:
|
||||||
///
|
///
|
||||||
/// let s0 = Plan.of(1.second, 3.seconds, 5.seconds)
|
/// let s0 = Plan.of(1.second, 3.seconds, 5.seconds)
|
||||||
/// let s1 = Plan.of(2.seconds, 4.seconds, 6.seconds)
|
/// let s1 = Plan.of(2.seconds, 4.seconds, 6.seconds)
|
||||||
/// let s2 = s0.concat(s1)
|
/// let s2 = s0.merge(s1)
|
||||||
/// > s2
|
/// > s2
|
||||||
/// > 1.second, 1.seconds, 2.seconds, 2.seconds, 3.seconds, 3.seconds
|
/// > 1.second, 1.seconds, 2.seconds, 2.seconds, 3.seconds, 3.seconds
|
||||||
public func merge(_ plan: Plan) -> Plan {
|
public func merge(_ plan: Plan) -> Plan {
|
||||||
return Plan.make { () -> AnyIterator<Date> in
|
return Plan.make { () -> AnyIterator<Date> in
|
||||||
let i0 = self.dates.makeIterator()
|
let i0 = self.dates.makeIterator()
|
||||||
let i1 = plan.dates.makeIterator()
|
let i1 = plan.dates.makeIterator()
|
||||||
var buffer0: Date!
|
|
||||||
var buffer1: Date!
|
var buf0: Date!
|
||||||
|
var buf1: Date!
|
||||||
|
|
||||||
return AnyIterator<Date> {
|
return AnyIterator<Date> {
|
||||||
if buffer0 == nil { buffer0 = i0.next() }
|
if buf0 == nil { buf0 = i0.next() }
|
||||||
if buffer1 == nil { buffer1 = i1.next() }
|
if buf1 == nil { buf1 = i1.next() }
|
||||||
|
|
||||||
var d: Date!
|
var d: Date!
|
||||||
if let d0 = buffer0, let d1 = buffer1 {
|
if let d0 = buf0, let d1 = buf1 {
|
||||||
d = Swift.min(d0, d1)
|
d = Swift.min(d0, d1)
|
||||||
} else {
|
} else {
|
||||||
d = buffer0 ?? buffer1
|
d = buf0 ?? buf1
|
||||||
}
|
}
|
||||||
|
|
||||||
if d == buffer0 { buffer0 = nil }
|
if d == nil { return d }
|
||||||
if d == buffer1 { buffer1 = nil }
|
|
||||||
|
if d == buf0 { buf0 = nil; return d }
|
||||||
|
if d == buf1 { buf1 = nil }
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new plan by only taking the first specific number of this plan.
|
/// Returns a new plan by taking the first specific number of intervals from this plan.
|
||||||
///
|
///
|
||||||
/// For example:
|
/// For example:
|
||||||
///
|
///
|
||||||
|
@ -247,70 +260,69 @@ extension Plan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new plan by only taking the part before the date.
|
/// Returns a new plan by taking the part before the given date.
|
||||||
public func until(_ date: Date) -> Plan {
|
public func until(_ date: Date) -> Plan {
|
||||||
return Plan.make { () -> AnyIterator<Date> in
|
return Plan.make { () -> AnyIterator<Date> in
|
||||||
let iterator = self.dates.makeIterator()
|
let iterator = self.dates.makeIterator()
|
||||||
return AnyIterator {
|
return AnyIterator {
|
||||||
guard let next = iterator.next(), next < date else {
|
guard let next = iterator.next(), next < date else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extension Plan {
|
|
||||||
|
|
||||||
/// Creates a plan that executes the task immediately.
|
/// Creates a plan that executes the task immediately.
|
||||||
public static var now: Plan {
|
public static var now: Plan {
|
||||||
return Plan.of(0.nanosecond)
|
return Plan.of(0.nanosecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan that executes the task after delay.
|
/// Creates a plan that executes the task after the given interval.
|
||||||
public static func after(_ delay: Interval) -> Plan {
|
public static func after(_ delay: Interval) -> Plan {
|
||||||
return Plan.of(delay)
|
return Plan.of(delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan that executes the task every interval.
|
/// Creates a plan that executes the task after the given interval then repeat the execution.
|
||||||
|
public static func after(_ delay: Interval, repeating interval: Interval) -> Plan {
|
||||||
|
return Plan.after(delay).concat(Plan.every(interval))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a plan that executes the task at the given date.
|
||||||
|
public static func at(_ date: Date) -> Plan {
|
||||||
|
return Plan.of(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a plan that executes the task every given interval.
|
||||||
public static func every(_ interval: Interval) -> Plan {
|
public static func every(_ interval: Interval) -> Plan {
|
||||||
return Plan.make {
|
return Plan.make {
|
||||||
AnyIterator { interval }
|
AnyIterator { interval }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan that executes the task after delay then repeat
|
/// Creates a plan that executes the task every given period.
|
||||||
/// every interval.
|
|
||||||
public static func after(_ delay: Interval, repeating interval: Interval) -> Plan {
|
|
||||||
return Plan.after(delay).concat(Plan.every(interval))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a plan that executes the task at the specific date.
|
|
||||||
public static func at(_ date: Date) -> Plan {
|
|
||||||
return Plan.of(date)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a plan that executes the task every period.
|
|
||||||
public static func every(_ period: Period) -> Plan {
|
public static func every(_ period: Period) -> Plan {
|
||||||
return Plan.make { () -> AnyIterator<Interval> in
|
return Plan.make { () -> AnyIterator<Interval> in
|
||||||
let calendar = Calendar.gregorian
|
let calendar = Calendar.gregorian
|
||||||
var last: Date!
|
var prev: Date!
|
||||||
return AnyIterator {
|
return AnyIterator {
|
||||||
last = last ?? Date()
|
prev = prev ?? Date()
|
||||||
guard let next = calendar.date(byAdding: period.toDateComponents(),
|
guard
|
||||||
to: last) else {
|
let next = calendar.date(
|
||||||
|
byAdding: period.asDateComponents(),
|
||||||
|
to: prev)
|
||||||
|
else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
defer { last = next }
|
defer { prev = next }
|
||||||
return next.interval(since: last)
|
return next.interval(since: prev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan that executes the task every period.
|
/// Creates a plan that executes the task every period.
|
||||||
///
|
///
|
||||||
/// See Period's constructor
|
/// See Period's constructor: `init?(_ string: String)`.
|
||||||
public static func every(_ period: String) -> Plan {
|
public static func every(_ period: String) -> Plan {
|
||||||
guard let p = Period(period) else {
|
guard let p = Period(period) else {
|
||||||
return Plan.never
|
return Plan.never
|
||||||
|
@ -322,16 +334,16 @@ extension Plan {
|
||||||
extension Plan {
|
extension Plan {
|
||||||
|
|
||||||
/// `DateMiddleware` represents a middleware that wraps a plan
|
/// `DateMiddleware` represents a middleware that wraps a plan
|
||||||
/// which was only specified date without time.
|
/// which was only specified with date without time.
|
||||||
///
|
///
|
||||||
/// You should call `at` method to get the plan with time specified.
|
/// You should call `at` method to specified time of the plan.
|
||||||
public struct DateMiddleware {
|
public struct DateMiddleware {
|
||||||
|
|
||||||
fileprivate let plan: Plan
|
fileprivate let plan: Plan
|
||||||
|
|
||||||
/// Returns a plan at the specific time.
|
/// Creates a plan with time specified.
|
||||||
public func at(_ time: Time) -> Plan {
|
public func at(_ time: Time) -> Plan {
|
||||||
guard !self.plan.isNever() else { return .never }
|
if plan.isNever() { return .never }
|
||||||
|
|
||||||
var interval = time.intervalSinceStartOfDay
|
var interval = time.intervalSinceStartOfDay
|
||||||
return Plan.make { () -> AnyIterator<Interval> in
|
return Plan.make { () -> AnyIterator<Interval> in
|
||||||
|
@ -346,56 +358,54 @@ extension Plan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a plan at the specific time.
|
/// Creates a plan with time specified.
|
||||||
///
|
///
|
||||||
/// See Time's constructor
|
/// See Time's constructor: `init?(_ string: String)`.
|
||||||
public func at(_ time: String) -> Plan {
|
public func at(_ time: String) -> Plan {
|
||||||
guard
|
if plan.isNever() { return .never }
|
||||||
!self.plan.isNever(),
|
guard let time = Time(time) else {
|
||||||
let time = Time(time)
|
|
||||||
else {
|
|
||||||
return .never
|
return .never
|
||||||
}
|
}
|
||||||
|
|
||||||
return at(time)
|
return at(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a plan at the specific time.
|
/// Creates a plan with time specified.
|
||||||
///
|
///
|
||||||
/// .at(1) => 01
|
/// .at(1) => 01
|
||||||
/// .at(1, 2) => 01:02
|
/// .at(1, 2) => 01:02
|
||||||
/// .at(1, 2, 3) => 01:02:03
|
/// .at(1, 2, 3) => 01:02:03
|
||||||
/// .at(1, 2, 3, 456) => 01:02:03.456
|
/// .at(1, 2, 3, 456) => 01:02:03.456
|
||||||
///
|
|
||||||
/// - Note: Returns `Plan.never` if given no parameters.
|
|
||||||
public func at(_ time: Int...) -> Plan {
|
public func at(_ time: Int...) -> Plan {
|
||||||
return self.at(time)
|
return self.at(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a plan at the specific time.
|
/// Creates a plan with time specified.
|
||||||
///
|
///
|
||||||
/// .at([1]) => 01
|
/// .at([1]) => 01
|
||||||
/// .at([1, 2]) => 01:02
|
/// .at([1, 2]) => 01:02
|
||||||
/// .at([1, 2, 3]) => 01:02:03
|
/// .at([1, 2, 3]) => 01:02:03
|
||||||
/// .at([1, 2, 3, 456]) => 01:02:03.456
|
/// .at([1, 2, 3, 456]) => 01:02:03.456
|
||||||
///
|
|
||||||
/// - Note: Returns `Plan.never` if given an empty array.
|
|
||||||
public func at(_ time: [Int]) -> Plan {
|
public func at(_ time: [Int]) -> Plan {
|
||||||
guard !time.isEmpty, !self.plan.isNever() else { return .never }
|
if plan.isNever() || time.isEmpty { return .never }
|
||||||
|
|
||||||
let hour = time[0]
|
let hour = time[0]
|
||||||
let minute = time.count > 1 ? time[1] : 0
|
let minute = time.count > 1 ? time[1] : 0
|
||||||
let second = time.count > 2 ? time[2] : 0
|
let second = time.count > 2 ? time[2] : 0
|
||||||
let nanosecond = time.count > 3 ? time[3]: 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 Plan.never
|
return Plan.never
|
||||||
}
|
}
|
||||||
return at(time)
|
return at(time)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan that executes the task every specific weekday.
|
/// Creates a date middleware that executes the task on every specific week day.
|
||||||
public static func every(_ weekday: Weekday) -> DateMiddleware {
|
public static func every(_ weekday: Weekday) -> DateMiddleware {
|
||||||
let plan = Plan.make { () -> AnyIterator<Date> in
|
let plan = Plan.make { () -> AnyIterator<Date> in
|
||||||
let calendar = Calendar.gregorian
|
let calendar = Calendar.gregorian
|
||||||
|
@ -406,7 +416,7 @@ extension Plan {
|
||||||
} else if Date().is(weekday) {
|
} else if Date().is(weekday) {
|
||||||
date = Date().startOfToday
|
date = Date().startOfToday
|
||||||
} else {
|
} else {
|
||||||
let components = weekday.toDateComponents()
|
let components = weekday.asDateComponents()
|
||||||
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
|
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
|
@ -415,27 +425,23 @@ extension Plan {
|
||||||
return DateMiddleware(plan: plan)
|
return DateMiddleware(plan: plan)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan that executes the task every specific weekdays.
|
/// Creates a date middleware that executes the task on every specific week day.
|
||||||
/// - Note: Returns initialized with `Plan.never` if given no parameters.
|
|
||||||
public static func every(_ weekdays: Weekday...) -> DateMiddleware {
|
public static func every(_ weekdays: Weekday...) -> DateMiddleware {
|
||||||
return Plan.every(weekdays)
|
return Plan.every(weekdays)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan that executes the task every specific weekdays.
|
/// Creates a date middleware that executes the task on every specific week day.
|
||||||
/// - Note: Returns initialized with `Plan.never` if given an empty array.
|
|
||||||
public static func every(_ weekdays: [Weekday]) -> DateMiddleware {
|
public static func every(_ weekdays: [Weekday]) -> DateMiddleware {
|
||||||
guard !weekdays.isEmpty else { return .init(plan: .never) }
|
guard !weekdays.isEmpty else { return .init(plan: .never) }
|
||||||
|
|
||||||
var plan = every(weekdays[0]).plan
|
var plan = every(weekdays[0]).plan
|
||||||
if weekdays.count > 1 {
|
for weekday in weekdays.dropFirst() {
|
||||||
for i in 1..<weekdays.count {
|
plan = plan.merge(Plan.every(weekday).plan)
|
||||||
plan = plan.merge(Plan.every(weekdays[i]).plan)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return DateMiddleware(plan: plan)
|
return DateMiddleware(plan: plan)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan that executes the task every specific day in the month.
|
/// Creates a date middleware that executes the task on every specific month day.
|
||||||
public static func every(_ monthday: Monthday) -> DateMiddleware {
|
public static func every(_ monthday: Monthday) -> DateMiddleware {
|
||||||
let plan = Plan.make { () -> AnyIterator<Date> in
|
let plan = Plan.make { () -> AnyIterator<Date> in
|
||||||
let calendar = Calendar.gregorian
|
let calendar = Calendar.gregorian
|
||||||
|
@ -445,8 +451,8 @@ extension Plan {
|
||||||
date = calendar.date(byAdding: .year, value: 1, to: d)
|
date = calendar.date(byAdding: .year, value: 1, to: d)
|
||||||
} else if Date().is(monthday) {
|
} else if Date().is(monthday) {
|
||||||
date = Date().startOfToday
|
date = Date().startOfToday
|
||||||
} else {
|
} else {
|
||||||
let components = monthday.toDateComponents()
|
let components = monthday.asDateComponents()
|
||||||
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
|
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
|
@ -455,34 +461,33 @@ extension Plan {
|
||||||
return DateMiddleware(plan: plan)
|
return DateMiddleware(plan: plan)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan that executes the task every specific days in the months.
|
/// Creates a date middleware that executes the task on every specific month day.
|
||||||
/// - Note: Returns initialized with `Plan.never` if given no parameters.
|
|
||||||
public static func every(_ mondays: Monthday...) -> DateMiddleware {
|
public static func every(_ mondays: Monthday...) -> DateMiddleware {
|
||||||
return Plan.every(mondays)
|
return Plan.every(mondays)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan that executes the task every specific days in the months.
|
/// Creates a date middleware that executes the task on every specific month day.
|
||||||
/// - Note: Returns initialized with `Plan.never` if given an empty array.
|
|
||||||
public static func every(_ mondays: [Monthday]) -> DateMiddleware {
|
public static func every(_ mondays: [Monthday]) -> DateMiddleware {
|
||||||
guard !mondays.isEmpty else { return .init(plan: .never) }
|
guard !mondays.isEmpty else { return .init(plan: .never) }
|
||||||
|
|
||||||
var plan = every(mondays[0]).plan
|
var plan = every(mondays[0]).plan
|
||||||
if mondays.count > 1 {
|
for monday in mondays.dropFirst() {
|
||||||
for i in 1..<mondays.count {
|
plan = plan.merge(Plan.every(monday).plan)
|
||||||
plan = plan.merge(Plan.every(mondays[i]).plan)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return DateMiddleware(plan: plan)
|
return DateMiddleware(plan: plan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Plan {
|
extension Plan {
|
||||||
|
|
||||||
|
/// Returns a Boolean value indicating whether this plan is empty.
|
||||||
public func isNever() -> Bool {
|
public func isNever() -> Bool {
|
||||||
return self.iSeq.makeIterator().next() == nil
|
return seq.makeIterator().next() == nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Plan {
|
extension Plan {
|
||||||
|
|
||||||
/// Creates a new plan that is offset by the specified interval in the
|
/// Creates a new plan that is offset by the specified interval in the
|
||||||
/// closure body.
|
/// closure body.
|
||||||
///
|
///
|
||||||
|
@ -491,23 +496,15 @@ extension Plan {
|
||||||
///
|
///
|
||||||
/// If the returned interval offset is `nil`, then no offset is added
|
/// If the returned interval offset is `nil`, then no offset is added
|
||||||
/// to that next-run date.
|
/// to that next-run date.
|
||||||
public func offset(by intervalOffset: @escaping () -> Interval?) -> Plan {
|
public func offset(by interval: @autoclosure @escaping () -> Interval?) -> Plan {
|
||||||
return Plan.make { () -> AnyIterator<Interval> in
|
return Plan.make { () -> AnyIterator<Interval> in
|
||||||
let it = self.makeIterator()
|
let it = self.makeIterator()
|
||||||
return AnyIterator {
|
return AnyIterator {
|
||||||
if let next = it.next() {
|
if let next = it.next() {
|
||||||
return next + (intervalOffset() ?? 0.second)
|
return next + (interval() ?? 0.second)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new plan that is offset by the specified interval.
|
|
||||||
///
|
|
||||||
/// If the specified interval offset is `nil`, then no offset is
|
|
||||||
/// added to the plan (ie. it stays the same).
|
|
||||||
public func offset(by intervalOffset: Interval?) -> Plan {
|
|
||||||
return self.offset(by: { intervalOffset })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,38 +5,42 @@ extension Plan {
|
||||||
/// Schedules a task with this plan.
|
/// Schedules a task with this plan.
|
||||||
///
|
///
|
||||||
/// When time is up, the task will be executed on current thread. It behaves
|
/// When time is up, the task will be executed on current thread. It behaves
|
||||||
/// like a `Timer`, so you have to make sure the current thread has a
|
/// like a `Timer`, so you need to make sure that the current thread has a
|
||||||
/// runloop available.
|
/// available runloop.
|
||||||
///
|
///
|
||||||
/// Since this method relies on run loop, it is recommended to use
|
/// Since this method relies on run loop, it is remove recommended to use
|
||||||
/// `do(queue: _, onElapse: _)`.
|
/// `do(queue: _, onElapse: _)`.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - mode: The mode in which to add the task.
|
/// - mode: The mode to which the action should be added.
|
||||||
/// - onElapse: The action to do when time is out.
|
/// - action: A block to be executed when time is up.
|
||||||
/// - Returns: The task just created.
|
/// - Returns: The task just created.
|
||||||
public func `do`(mode: RunLoop.Mode = .common,
|
public func `do`(
|
||||||
onElapse: @escaping (Task) -> Void) -> Task {
|
mode: RunLoop.Mode = .common,
|
||||||
return RunLoopTask(plan: self, mode: mode, onElapse: onElapse)
|
action: @escaping (Task) -> Void
|
||||||
|
) -> Task {
|
||||||
|
return RunLoopTask(plan: self, mode: mode, action: action)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Schedules a task with this plan.
|
/// Schedules a task with this plan.
|
||||||
///
|
///
|
||||||
/// When time is up, the task will be executed on current thread. It behaves
|
/// When time is up, the task will be executed on current thread. It behaves
|
||||||
/// like a `Timer`, so you have to make sure the current thread has a
|
/// like a `Timer`, so you need to make sure that the current thread has a
|
||||||
/// runloop available.
|
/// available runloop.
|
||||||
///
|
///
|
||||||
/// Since this method relies on run loop, it is recommended to use
|
/// Since this method relies on run loop, it is remove recommended to use
|
||||||
/// `do(queue: _, onElapse: _)`.
|
/// `do(queue: _, onElapse: _)`.
|
||||||
///
|
///
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - mode: The mode in which to add the task.
|
/// - mode: The mode to which the action should be added.
|
||||||
/// - onElapse: The action to do when time is out.
|
/// - action: A block to be executed when time is up.
|
||||||
/// - Returns: The task just created.
|
/// - Returns: The task just created.
|
||||||
public func `do`(mode: RunLoop.Mode = .common,
|
public func `do`(
|
||||||
onElapse: @escaping () -> Void) -> Task {
|
mode: RunLoop.Mode = .common,
|
||||||
return self.do(mode: mode) { (_) in
|
action: @escaping () -> Void
|
||||||
onElapse()
|
) -> Task {
|
||||||
|
return self.do(mode: mode) { _ in
|
||||||
|
action()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,24 +49,26 @@ private final class RunLoopTask: Task {
|
||||||
|
|
||||||
var timer: Timer!
|
var timer: Timer!
|
||||||
|
|
||||||
init(plan: Plan, mode: RunLoop.Mode, onElapse: @escaping (Task) -> Void) {
|
init(
|
||||||
|
plan: Plan,
|
||||||
|
mode: RunLoop.Mode,
|
||||||
|
action: @escaping (Task) -> Void
|
||||||
|
) {
|
||||||
|
super.init(plan: plan, queue: nil) { (task) in
|
||||||
|
guard let task = task as? RunLoopTask, let timer = task.timer else { return }
|
||||||
|
timer.fireDate = Date()
|
||||||
|
}
|
||||||
|
|
||||||
weak var this: Task?
|
timer = Timer(
|
||||||
|
fire: Date.distantFuture,
|
||||||
let distant = Date.distantFuture.timeIntervalSinceReferenceDate
|
interval: .greatestFiniteMagnitude,
|
||||||
timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, distant, distant, 0, 0) { _ in
|
repeats: false
|
||||||
guard let task = this else { return }
|
) { [weak self] _ in
|
||||||
onElapse(task)
|
guard let self = self else { return }
|
||||||
|
action(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
RunLoop.current.add(timer, forMode: mode)
|
RunLoop.current.add(timer, forMode: mode)
|
||||||
|
|
||||||
super.init(plan: plan, queue: nil) { (task) in
|
|
||||||
guard let task = task as? RunLoopTask else { return }
|
|
||||||
task.timer.fireDate = Date()
|
|
||||||
}
|
|
||||||
|
|
||||||
this = self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
|
|
@ -3,56 +3,145 @@ import Foundation
|
||||||
/// `ActionKey` represents a token that can be used to remove the action.
|
/// `ActionKey` represents a token that can be used to remove the action.
|
||||||
public struct ActionKey {
|
public struct ActionKey {
|
||||||
|
|
||||||
fileprivate let cabinetKey: CabinetKey
|
fileprivate let bagKey: BagKey
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CabinetKey {
|
extension BagKey {
|
||||||
|
|
||||||
func asActionKey() -> ActionKey {
|
fileprivate func asActionKey() -> ActionKey {
|
||||||
return ActionKey(cabinetKey: self)
|
return ActionKey(bagKey: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Task` represents a timed task.
|
/// `Task` represents a timing task.
|
||||||
open class Task {
|
open class Task {
|
||||||
|
|
||||||
|
// MARK: - Private properties
|
||||||
|
|
||||||
|
private let _lock = NSRecursiveLock()
|
||||||
|
|
||||||
|
private var _iterator: AnyIterator<Interval>
|
||||||
|
private let _timer: DispatchSourceTimer
|
||||||
|
|
||||||
|
private lazy var _actions = Bag<Action>()
|
||||||
|
|
||||||
|
private lazy var _suspensionCount: Int = 0
|
||||||
|
private lazy var _executionCount: Int = 0
|
||||||
|
|
||||||
|
private lazy var _executionDates: [Date]? = nil
|
||||||
|
private lazy var _estimatedNextExecutionDate: Date? = nil
|
||||||
|
|
||||||
|
private weak var _taskCenter: TaskCenter?
|
||||||
|
private let _taskCenterLock = NSRecursiveLock()
|
||||||
|
|
||||||
|
private var associateKey = 1
|
||||||
|
|
||||||
|
// MARK: - Public properties
|
||||||
|
|
||||||
|
/// The unique id of this task.
|
||||||
|
public let id = UUID()
|
||||||
|
|
||||||
public typealias Action = (Task) -> Void
|
public typealias Action = (Task) -> Void
|
||||||
|
|
||||||
private let _mutex = NSRecursiveLock()
|
/// The date of creation.
|
||||||
|
public let creationDate = Date()
|
||||||
|
|
||||||
private var _iterator: AnyIterator<Interval>
|
/// The date of first execution.
|
||||||
private var _timer: DispatchSourceTimer
|
open var firstExecutionDate: Date? {
|
||||||
|
return _lock.withLock { _executionDates?.first }
|
||||||
|
}
|
||||||
|
|
||||||
private lazy var _onElapseActions = Cabinet<Action>()
|
/// The date of last execution.
|
||||||
private lazy var _onDeinitActions = Cabinet<Action>()
|
open var lastExecutionDate: Date? {
|
||||||
|
return _lock.withLock { _executionDates?.last }
|
||||||
|
}
|
||||||
|
|
||||||
private lazy var _suspensions: UInt64 = 0
|
/// Histories of executions.
|
||||||
private lazy var _timeline = Timeline()
|
open var executionDates: [Date]? {
|
||||||
|
return _lock.withLock { _executionDates }
|
||||||
|
}
|
||||||
|
|
||||||
private lazy var _countOfExecutions: Int = 0
|
/// The date of estimated next execution.
|
||||||
|
open var estimatedNextExecutionDate: Date? {
|
||||||
|
return _lock.withLock { _estimatedNextExecutionDate }
|
||||||
|
}
|
||||||
|
|
||||||
private lazy var _lifetime: Interval = Int.max.seconds
|
/// The number of task executions.
|
||||||
private lazy var _lifetimeTimer: DispatchSourceTimer = {
|
public var executionCount: Int {
|
||||||
let timer = DispatchSource.makeTimerSource()
|
return _lock.withLock {
|
||||||
timer.setEventHandler {
|
_executionCount
|
||||||
self.cancel()
|
|
||||||
}
|
}
|
||||||
timer.schedule(after: _lifetime)
|
}
|
||||||
timer.resume()
|
|
||||||
return timer
|
|
||||||
}()
|
|
||||||
|
|
||||||
open internal(set) weak var taskCenter: TaskCenter?
|
/// The number of task suspensions.
|
||||||
let taskCenterMutex = NSRecursiveLock()
|
public var suspensionCount: Int {
|
||||||
|
return _lock.withLock {
|
||||||
|
_suspensionCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init(plan: Plan,
|
/// The number of actions in this task.
|
||||||
queue: DispatchQueue?,
|
public var actionCount: Int {
|
||||||
onElapse: @escaping (Task) -> Void) {
|
return _lock.withLock {
|
||||||
|
_actions.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Boolean indicating whether the task was canceled.
|
||||||
|
public var isCancelled: Bool {
|
||||||
|
return _lock.withLock {
|
||||||
|
_timer.isCancelled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The task center to which this task currently belongs.
|
||||||
|
open var taskCenter: TaskCenter? {
|
||||||
|
return _lock.withLock { _taskCenter }
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Task center
|
||||||
|
|
||||||
|
/// Adds this task to the given task center.
|
||||||
|
func addToTaskCenter(_ center: TaskCenter) {
|
||||||
|
_taskCenterLock.lock()
|
||||||
|
defer { _taskCenterLock.unlock() }
|
||||||
|
|
||||||
|
if _taskCenter === center { return }
|
||||||
|
|
||||||
|
let c = _taskCenter
|
||||||
|
_taskCenter = center
|
||||||
|
|
||||||
|
c?.remove(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes this task from the given task center.
|
||||||
|
func removeFromTaskCenter(_ center: TaskCenter) {
|
||||||
|
_taskCenterLock.lock()
|
||||||
|
defer { _taskCenterLock.unlock() }
|
||||||
|
|
||||||
|
if _taskCenter !== center { return }
|
||||||
|
|
||||||
|
_taskCenter = nil
|
||||||
|
center.remove(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
|
||||||
|
/// Initializes a timing task.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - plan: The plan.
|
||||||
|
/// - queue: The dispatch queue to which the action should be dispatched.
|
||||||
|
/// - action: A block to be executed when time is up.
|
||||||
|
init(
|
||||||
|
plan: Plan,
|
||||||
|
queue: DispatchQueue?,
|
||||||
|
action: @escaping (Task) -> Void
|
||||||
|
) {
|
||||||
_iterator = plan.makeIterator()
|
_iterator = plan.makeIterator()
|
||||||
_timer = DispatchSource.makeTimerSource(queue: queue)
|
_timer = DispatchSource.makeTimerSource(queue: queue)
|
||||||
|
|
||||||
_onElapseActions.append(onElapse)
|
_actions.append(action)
|
||||||
|
|
||||||
_timer.setEventHandler { [weak self] in
|
_timer.setEventHandler { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
@ -61,254 +150,133 @@ open class Task {
|
||||||
|
|
||||||
if let interval = _iterator.next(), !interval.isNegative {
|
if let interval = _iterator.next(), !interval.isNegative {
|
||||||
_timer.schedule(after: interval)
|
_timer.schedule(after: interval)
|
||||||
_timeline.estimatedNextExecution = Date().adding(interval)
|
_estimatedNextExecutionDate = Date().adding(interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
_timer.resume()
|
_timer.resume()
|
||||||
|
|
||||||
TaskCenter.default.add(self)
|
TaskCenter.default.add(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
for action in _onDeinitActions {
|
print("deinit")
|
||||||
action(self)
|
while _suspensionCount > 0 {
|
||||||
}
|
|
||||||
|
|
||||||
while _suspensions > 0 {
|
|
||||||
_timer.resume()
|
_timer.resume()
|
||||||
_suspensions -= 1
|
_suspensionCount -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
taskCenter?.remove(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func scheduleNext() {
|
private func elapse() {
|
||||||
_mutex.withLock {
|
scheduleNextExecution()
|
||||||
|
executeNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func scheduleNextExecution() {
|
||||||
|
_lock.withLockVoid {
|
||||||
let now = Date()
|
let now = Date()
|
||||||
var estimated = _timeline.estimatedNextExecution ?? now
|
var estimated = _estimatedNextExecutionDate ?? now
|
||||||
repeat {
|
repeat {
|
||||||
guard let interval = _iterator.next(), !interval.isNegative else {
|
guard let interval = _iterator.next(), !interval.isNegative else {
|
||||||
_timeline.estimatedNextExecution = nil
|
_estimatedNextExecutionDate = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
estimated = estimated.adding(interval)
|
estimated = estimated.adding(interval)
|
||||||
} while (estimated < now)
|
} while (estimated < now)
|
||||||
|
|
||||||
_timeline.estimatedNextExecution = estimated
|
_estimatedNextExecutionDate = estimated
|
||||||
_timer.schedule(after: _timeline.estimatedNextExecution!.interval(since: now))
|
_timer.schedule(after: _estimatedNextExecutionDate!.interval(since: now))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute this task now, without disrupting its plan.
|
/// Execute this task now, without interrupting its plan.
|
||||||
public func execute() {
|
open func executeNow() {
|
||||||
let actions = _mutex.withLock { () -> Cabinet<Task.Action> in
|
let actions = _lock.withLock { () -> Bag<Task.Action> in
|
||||||
let now = Date()
|
let now = Date()
|
||||||
if _timeline.firstExecution == nil {
|
if _executionDates == nil {
|
||||||
_timeline.firstExecution = now
|
_executionDates = [now]
|
||||||
|
} else {
|
||||||
|
_executionDates?.append(now)
|
||||||
}
|
}
|
||||||
_timeline.lastExecution = now
|
_executionCount += 1
|
||||||
_countOfExecutions += 1
|
return _actions
|
||||||
return _onElapseActions
|
|
||||||
}
|
}
|
||||||
actions.forEach { $0(self) }
|
actions.forEach { $0(self) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private func elapse() {
|
// MARK: - Features
|
||||||
scheduleNext()
|
|
||||||
execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
#if canImport(ObjectiveC)
|
|
||||||
open func host(on target: AnyObject) {
|
|
||||||
DeinitObserver.observe(target) { [weak self] in
|
|
||||||
self?.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// The number of times the task has been executed.
|
|
||||||
public var countOfExecutions: Int {
|
|
||||||
return _mutex.withLock {
|
|
||||||
_countOfExecutions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A Boolean indicating whether the task was canceled.
|
|
||||||
public var isCancelled: Bool {
|
|
||||||
return _mutex.withLock {
|
|
||||||
_timer.isCancelled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Manage
|
|
||||||
|
|
||||||
/// Reschedules this task with the new plan.
|
/// Reschedules this task with the new plan.
|
||||||
public func reschedule(_ new: Plan) {
|
public func reschedule(_ new: Plan) {
|
||||||
_mutex.withLock {
|
_lock.withLockVoid {
|
||||||
|
if _timer.isCancelled { return }
|
||||||
|
|
||||||
_iterator = new.makeIterator()
|
_iterator = new.makeIterator()
|
||||||
}
|
}
|
||||||
scheduleNext()
|
scheduleNextExecution()
|
||||||
}
|
|
||||||
|
|
||||||
/// Suspensions of this task.
|
|
||||||
public var suspensions: UInt64 {
|
|
||||||
return _mutex.withLock {
|
|
||||||
_suspensions
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Suspends this task.
|
/// Suspends this task.
|
||||||
public func suspend() {
|
public func suspend() {
|
||||||
_mutex.withLock {
|
_lock.withLockVoid {
|
||||||
if _suspensions < UInt64.max {
|
if _timer.isCancelled { return }
|
||||||
|
|
||||||
|
if _suspensionCount < UInt64.max {
|
||||||
_timer.suspend()
|
_timer.suspend()
|
||||||
_suspensions += 1
|
_suspensionCount += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resumes this task.
|
/// Resumes this task.
|
||||||
public func resume() {
|
public func resume() {
|
||||||
_mutex.withLock {
|
_lock.withLockVoid {
|
||||||
if _suspensions > 0 {
|
if _timer.isCancelled { return }
|
||||||
|
|
||||||
|
if _suspensionCount > 0 {
|
||||||
_timer.resume()
|
_timer.resume()
|
||||||
_suspensions -= 1
|
_suspensionCount -= 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cancels this task.
|
/// Cancels this task.
|
||||||
public func cancel() {
|
public func cancel() {
|
||||||
_mutex.withLock {
|
_lock.withLockVoid {
|
||||||
_timer.cancel()
|
_timer.cancel()
|
||||||
}
|
_suspensionCount = 0
|
||||||
TaskCenter.default.remove(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
open func onDeinit(_ body: @escaping Action) -> ActionKey {
|
|
||||||
return _mutex.withLock {
|
|
||||||
return _onDeinitActions.append(body).asActionKey()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Lifecycle
|
|
||||||
|
|
||||||
/// The snapshot timeline of this task.
|
|
||||||
public var timeline: Timeline {
|
|
||||||
return _mutex.withLock {
|
|
||||||
_timeline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The lifetime of this task.
|
|
||||||
public var lifetime: Interval {
|
|
||||||
return _mutex.withLock {
|
|
||||||
_lifetime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The rest of lifetime.
|
|
||||||
public var restOfLifetime: Interval {
|
|
||||||
return _mutex.withLock {
|
|
||||||
_lifetime - Date().interval(since: _timeline.initialization)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a new lifetime for this task.
|
|
||||||
///
|
|
||||||
/// If this task has already ended its lifetime, setting will fail,
|
|
||||||
/// if new lifetime is shorter than its age, setting will fail, too.
|
|
||||||
///
|
|
||||||
/// - Returns: `true` if set successfully, `false` if not.
|
|
||||||
@discardableResult
|
|
||||||
public func setLifetime(_ interval: Interval) -> Bool {
|
|
||||||
guard restOfLifetime.isPositive else { return false }
|
|
||||||
|
|
||||||
_mutex.lock()
|
|
||||||
let age = Date().interval(since: _timeline.initialization)
|
|
||||||
guard age.isShorter(than: interval) else {
|
|
||||||
_mutex.unlock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
_lifetime = interval
|
|
||||||
_lifetimeTimer.schedule(after: interval - age)
|
|
||||||
_mutex.unlock()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add an interval to this task's lifetime.
|
|
||||||
///
|
|
||||||
/// If this task has already ended its lifetime, adding will fail,
|
|
||||||
/// if new lifetime is shorter than its age, adding will fail, too.
|
|
||||||
///
|
|
||||||
/// - Returns: `true` if set successfully, `false` if not.
|
|
||||||
@discardableResult
|
|
||||||
public func addLifetime(_ interval: Interval) -> Bool {
|
|
||||||
var rest = restOfLifetime
|
|
||||||
guard rest.isPositive else { return false }
|
|
||||||
rest += interval
|
|
||||||
guard rest.isPositive else { return false }
|
|
||||||
_mutex.withLock {
|
|
||||||
_lifetime += interval
|
|
||||||
_lifetimeTimer.schedule(after: rest)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Subtract an interval to this task's lifetime.
|
|
||||||
///
|
|
||||||
/// If this task has already ended its lifetime, subtracting will fail,
|
|
||||||
/// if new lifetime is shorter than its age, subtracting will fail, too.
|
|
||||||
///
|
|
||||||
/// - Returns: `true` if set successfully, `false` if not.
|
|
||||||
@discardableResult
|
|
||||||
public func subtractLifetime(_ interval: Interval) -> Bool {
|
|
||||||
return addLifetime(interval.opposite)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Action
|
|
||||||
|
|
||||||
/// The number of actions in this task.
|
|
||||||
public var countOfActions: Int {
|
|
||||||
return _mutex.withLock {
|
|
||||||
_onElapseActions.count
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds action to this task.
|
/// Adds action to this task.
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public func addAction(_ action: @escaping (Task) -> Void) -> ActionKey {
|
public func addAction(_ action: @escaping (Task) -> Void) -> ActionKey {
|
||||||
return _mutex.withLock {
|
return _lock.withLock {
|
||||||
return _onElapseActions.append(action).asActionKey()
|
return _actions.append(action).asActionKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes action by key from this task.
|
/// Removes action by key from this task.
|
||||||
public func removeAction(byKey key: ActionKey) {
|
public func removeAction(byKey key: ActionKey) {
|
||||||
_mutex.withLock {
|
_lock.withLockVoid {
|
||||||
_ = _onElapseActions.delete(key.cabinetKey)
|
_ = _actions.removeValue(for: key.bagKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes all actions from this task.
|
/// Removes all actions from this task.
|
||||||
public func removeAllActions() {
|
public func removeAllActions() {
|
||||||
_mutex.withLock {
|
_lock.withLockVoid {
|
||||||
_onElapseActions.clear()
|
_actions.removeAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Tag
|
|
||||||
open func add(to: TaskCenter) {
|
|
||||||
_mutex.lock()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Task: Hashable {
|
extension Task: Hashable {
|
||||||
|
|
||||||
/// The task's hash value.
|
/// Hashes the essential components of this value by feeding them into the given hasher.
|
||||||
public var hashValue: Int {
|
public func hash(into hasher: inout Hasher) {
|
||||||
return ObjectIdentifier(self).hashValue
|
hasher.combine(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a boolean value indicating whether two tasks are equal.
|
/// Returns a boolean value indicating whether two tasks are equal.
|
||||||
|
@ -316,24 +284,3 @@ extension Task: Hashable {
|
||||||
return lhs === rhs
|
return lhs === rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Task: CustomStringConvertible {
|
|
||||||
|
|
||||||
/// A textual representation of this task.
|
|
||||||
public var description: String {
|
|
||||||
return "Task: { " +
|
|
||||||
"\"isCancelled\": \(_timer.isCancelled), " +
|
|
||||||
"\"countOfActions\": \(_onElapseActions.count), " +
|
|
||||||
"\"countOfExecutions\": \(_countOfExecutions), " +
|
|
||||||
"\"timeline\": \(_timeline)" +
|
|
||||||
" }"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Task: CustomDebugStringConvertible {
|
|
||||||
|
|
||||||
/// A textual representation of this task for debugging.
|
|
||||||
public var debugDescription: String {
|
|
||||||
return description
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,157 +8,168 @@ extension TaskCenter {
|
||||||
|
|
||||||
weak var task: Task?
|
weak var task: Task?
|
||||||
|
|
||||||
// To find slot
|
// Used to find slot in dictionary/set
|
||||||
let hashValue: Int
|
let hash: Int
|
||||||
|
|
||||||
init(_ task: Task) {
|
init(_ task: Task) {
|
||||||
self.task = task
|
self.task = task
|
||||||
self.hashValue = task.hashValue
|
self.hash = task.hashValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// To find task
|
func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to find task in a slot in dictionary/set
|
||||||
static func == (lhs: TaskBox, rhs: TaskBox) -> Bool {
|
static func == (lhs: TaskBox, rhs: TaskBox) -> Bool {
|
||||||
return lhs.task == rhs.task
|
return lhs.task == rhs.task
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A task center that enables batch operation.
|
||||||
open class TaskCenter {
|
open class TaskCenter {
|
||||||
|
|
||||||
private let mutex = NSRecursiveLock()
|
private let lock = NSLock()
|
||||||
|
|
||||||
private var taskMap: [String: Set<TaskBox>] = [:]
|
private var tags: [String: Set<TaskBox>] = [:]
|
||||||
private var tagMap: [TaskBox: Set<String>] = [:]
|
private var tasks: [TaskBox: Set<String>] = [:]
|
||||||
|
|
||||||
|
/// Default task center.
|
||||||
open class var `default`: TaskCenter {
|
open class var `default`: TaskCenter {
|
||||||
return _default
|
return _default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds the given task to this center.
|
||||||
open func add(_ task: Task) {
|
open func add(_ task: Task) {
|
||||||
task.taskCenterMutex.lock()
|
task.addToTaskCenter(self)
|
||||||
defer {
|
|
||||||
task.taskCenterMutex.unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if let center = task.taskCenter {
|
lock.withLockVoid {
|
||||||
if center === self { return }
|
|
||||||
center.remove(task)
|
|
||||||
}
|
|
||||||
task.taskCenter = self
|
|
||||||
|
|
||||||
mutex.withLock {
|
|
||||||
let box = TaskBox(task)
|
let box = TaskBox(task)
|
||||||
tagMap[box] = []
|
self.tasks[box] = []
|
||||||
}
|
|
||||||
|
|
||||||
task.onDeinit { [weak self] (t) in
|
|
||||||
guard let self = self else { return }
|
|
||||||
self.remove(t)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the given task from this center.
|
||||||
open func remove(_ task: Task) {
|
open func remove(_ task: Task) {
|
||||||
task.taskCenterMutex.lock()
|
task.removeFromTaskCenter(self)
|
||||||
defer {
|
|
||||||
task.taskCenterMutex.unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
guard task.taskCenter === self else {
|
lock.withLockVoid {
|
||||||
return
|
|
||||||
}
|
|
||||||
task.taskCenter = nil
|
|
||||||
|
|
||||||
mutex.withLock {
|
|
||||||
let box = TaskBox(task)
|
let box = TaskBox(task)
|
||||||
if let tags = self.tagMap[box] {
|
if let tags = self.tasks[box] {
|
||||||
for tag in tags {
|
for tag in tags {
|
||||||
self.taskMap[tag]?.remove(box)
|
self.tags[tag]?.remove(box)
|
||||||
|
if self.tags[tag]?.count == 0 {
|
||||||
|
self.tags[tag] = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.tagMap[box] = nil
|
self.tasks[box] = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a tag to the task.
|
||||||
|
///
|
||||||
|
/// If the task is not in this center, do nothing.
|
||||||
open func addTag(_ tag: String, to task: Task) {
|
open func addTag(_ tag: String, to task: Task) {
|
||||||
addTags([tag], to: task)
|
addTags([tag], to: task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds tags to the task.
|
||||||
|
///
|
||||||
|
/// If the task is not in this center, do nothing.
|
||||||
open func addTags(_ tags: [String], to task: Task) {
|
open func addTags(_ tags: [String], to task: Task) {
|
||||||
guard task.taskCenter === self else { return }
|
guard task.taskCenter === self else { return }
|
||||||
|
|
||||||
mutex.withLock {
|
lock.withLockVoid {
|
||||||
let box = TaskBox(task)
|
let box = TaskBox(task)
|
||||||
if tagMap[box] == nil {
|
|
||||||
tagMap[box] = []
|
|
||||||
}
|
|
||||||
for tag in tags {
|
for tag in tags {
|
||||||
tagMap[box]?.insert(tag)
|
tasks[box]?.insert(tag)
|
||||||
if taskMap[tag] == nil {
|
if self.tags[tag] == nil {
|
||||||
taskMap[tag] = []
|
self.tags[tag] = []
|
||||||
}
|
}
|
||||||
taskMap[tag]?.insert(box)
|
self.tags[tag]?.insert(box)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes a tag from the task.
|
||||||
|
///
|
||||||
|
/// If the task is not in this center, do nothing.
|
||||||
open func removeTag(_ tag: String, from task: Task) {
|
open func removeTag(_ tag: String, from task: Task) {
|
||||||
removeTags([tag], from: task)
|
removeTags([tag], from: task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes tags from the task.
|
||||||
|
///
|
||||||
|
/// If the task is not in this center, do nothing.
|
||||||
open func removeTags(_ tags: [String], from task: Task) {
|
open func removeTags(_ tags: [String], from task: Task) {
|
||||||
guard task.taskCenter === self else { return }
|
guard task.taskCenter === self else { return }
|
||||||
|
|
||||||
mutex.withLock {
|
lock.withLockVoid {
|
||||||
let box = TaskBox(task)
|
let box = TaskBox(task)
|
||||||
for tag in tags {
|
for tag in tags {
|
||||||
tagMap[box]?.remove(tag)
|
self.tasks[box]?.remove(tag)
|
||||||
taskMap[tag]?.remove(box)
|
self.tags[tag]?.remove(box)
|
||||||
|
if self.tags[tag]?.count == 0 {
|
||||||
|
self.tags[tag] = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open func tagsForTask(_ task: Task) -> [String] {
|
/// Returns all tags for the task.
|
||||||
|
///
|
||||||
|
/// If the task is not in this center, return an empty array.
|
||||||
|
open func tags(forTask task: Task) -> [String] {
|
||||||
guard task.taskCenter === self else { return [] }
|
guard task.taskCenter === self else { return [] }
|
||||||
|
|
||||||
return mutex.withLock {
|
return lock.withLock {
|
||||||
Array(tagMap[TaskBox(task)] ?? [])
|
Array(tasks[TaskBox(task)] ?? [])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open func tasksForTag(_ tag: String) -> [Task] {
|
/// Returns all tasks for the tag.
|
||||||
return mutex.withLock {
|
open func tasks(forTag tag: String) -> [Task] {
|
||||||
taskMap[tag]?.compactMap { $0.task } ?? []
|
return lock.withLock {
|
||||||
|
tags[tag]?.compactMap { $0.task } ?? []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all tasks in this center.
|
||||||
open var allTasks: [Task] {
|
open var allTasks: [Task] {
|
||||||
return mutex.withLock {
|
return lock.withLock {
|
||||||
tagMap.compactMap { $0.key.task }
|
tasks.compactMap { $0.key.task }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all tags in this center.
|
||||||
open var allTags: [String] {
|
open var allTags: [String] {
|
||||||
return mutex.withLock {
|
return lock.withLock {
|
||||||
taskMap.map { $0.key }
|
tags.map { $0.key }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open func clear() {
|
/// Removes all tasks from this center.
|
||||||
mutex.withLock {
|
open func removeAll() {
|
||||||
tagMap = [:]
|
lock.withLockVoid {
|
||||||
taskMap = [:]
|
tasks = [:]
|
||||||
|
tags = [:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open func suspendByTag(_ tag: String) {
|
/// Suspends all tasks by tag.
|
||||||
tasksForTag(tag).forEach { $0.suspend() }
|
open func suspend(byTag tag: String) {
|
||||||
|
tasks(forTag: tag).forEach { $0.suspend() }
|
||||||
}
|
}
|
||||||
|
|
||||||
open func resumeByTag(_ tag: String) {
|
/// Resumes all tasks by tag.
|
||||||
tasksForTag(tag).forEach { $0.resume() }
|
open func resume(byTag tag: String) {
|
||||||
|
tasks(forTag: tag).forEach { $0.resume() }
|
||||||
}
|
}
|
||||||
|
|
||||||
open func cancelByTag(_ tag: String) {
|
/// Cancels all tasks by tag.
|
||||||
tasksForTag(tag).forEach { $0.cancel() }
|
open func cancel(byTag tag: String) {
|
||||||
|
tasks(forTag: tag).forEach { $0.cancel() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,7 @@ public struct Time {
|
||||||
/// Nanosecond of second.
|
/// Nanosecond of second.
|
||||||
public let nanosecond: Int
|
public let nanosecond: Int
|
||||||
|
|
||||||
/// Creates a time with `hour`, `minute`, `second` and `nanosecond` fields.
|
/// Initializes a time with `hour`, `minute`, `second` and `nanosecond`.
|
||||||
///
|
|
||||||
/// If any parameter is illegal, return nil.
|
|
||||||
///
|
///
|
||||||
/// Time(hour: 11, minute: 11) => "11:11:00.000"
|
/// Time(hour: 11, minute: 11) => "11:11:00.000"
|
||||||
/// Time(hour: 25) => nil
|
/// Time(hour: 25) => nil
|
||||||
|
@ -36,36 +34,40 @@ public struct Time {
|
||||||
self.nanosecond = nanosecond
|
self.nanosecond = nanosecond
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a time with a string.
|
/// Initializes a time with a string.
|
||||||
///
|
///
|
||||||
/// If the parameter is illegal, return nil.
|
/// Time("11") -> Time(hour: 11)
|
||||||
///
|
/// Time("11:12") -> Time(hour: 11, minute: 12)
|
||||||
/// Time("11") == Time(hour: 11)
|
/// Time("11:12:13") -> Time(hour: 11, minute: 12, second: 13)
|
||||||
/// Time("11:12") == Time(hour: 11, minute: 12)
|
/// Time("11:12:13.123") -> Time(hour: 11, minute: 12, second: 13, nanosecond: 123000000)
|
||||||
/// Time("11:12:13") == Time(hour: 11, minute: 12, second: 13)
|
|
||||||
/// Time("11:12:13.123") == Time(hour: 11, minute: 12, second: 13, nanosecond: 123000000)
|
|
||||||
///
|
///
|
||||||
/// Time("-1.0") == nil
|
/// Time("-1.0") == nil
|
||||||
///
|
///
|
||||||
/// Any of the previous examples can have a period suffix("am", "AM", "pm", "PM"),
|
|
||||||
/// separated by a space.
|
|
||||||
///
|
|
||||||
/// Time("11 pm") == Time(hour: 23)
|
/// Time("11 pm") == Time(hour: 23)
|
||||||
/// Time("11:12:13 PM") == Time(hour: 23, minute: 12, second: 13)
|
/// Time("11:12:13 PM") == Time(hour: 23, minute: 12, second: 13)
|
||||||
public init?(_ string: String) {
|
public init?(_ string: String) {
|
||||||
let pattern = "^(\\d{1,2})(:(\\d{1,2})(:(\\d{1,2})(.(\\d{1,3}))?)?)?( (am|AM|pm|PM))?$"
|
let pattern = "^(\\d{1,2})(:(\\d{1,2})(:(\\d{1,2})(.(\\d{1,3}))?)?)?( (am|AM|pm|PM))?$"
|
||||||
|
|
||||||
guard let regexp = try? NSRegularExpression(pattern: pattern, options: []) else { return nil }
|
// swiftlint:disable force_try
|
||||||
guard let result = regexp.matches(in: string, options: [], range: NSRange(location: 0, length: string.count)).first else { return nil }
|
let regexp = try! NSRegularExpression(pattern: pattern, options: [])
|
||||||
|
let nsString = NSString(string: string)
|
||||||
|
guard let matches = regexp.matches(
|
||||||
|
in: string,
|
||||||
|
options: [],
|
||||||
|
range: NSRange(location: 0, length: nsString.length)).first
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var hasAM = false
|
var hasAM = false
|
||||||
var hasPM = false
|
var hasPM = false
|
||||||
var values: [Int] = []
|
var values: [Int] = []
|
||||||
|
values.reserveCapacity(matches.numberOfRanges)
|
||||||
|
|
||||||
for i in 0..<result.numberOfRanges {
|
for i in 0..<matches.numberOfRanges {
|
||||||
let range = result.range(at: i)
|
let range = matches.range(at: i)
|
||||||
if range.length == 0 { continue }
|
if range.length == 0 { continue }
|
||||||
let captured = NSString(string: string).substring(with: range)
|
let captured = nsString.substring(with: range)
|
||||||
hasAM = ["am", "AM"].contains(captured)
|
hasAM = ["am", "AM"].contains(captured)
|
||||||
hasPM = ["pm", "PM"].contains(captured)
|
hasPM = ["pm", "PM"].contains(captured)
|
||||||
if let value = Int(captured) {
|
if let value = Int(captured) {
|
||||||
|
@ -94,9 +96,9 @@ public struct Time {
|
||||||
|
|
||||||
/// Returns a dateComponenets of the time, using gregorian calender and
|
/// Returns a dateComponenets of the time, using gregorian calender and
|
||||||
/// current time zone.
|
/// current time zone.
|
||||||
public func toDateComponents() -> DateComponents {
|
public func asDateComponents(_ timeZone: TimeZone = .current) -> DateComponents {
|
||||||
return DateComponents(calendar: Calendar.gregorian,
|
return DateComponents(calendar: Calendar.gregorian,
|
||||||
timeZone: TimeZone.current,
|
timeZone: timeZone,
|
||||||
hour: hour,
|
hour: hour,
|
||||||
minute: minute,
|
minute: minute,
|
||||||
second: second,
|
second: second,
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
import Foundation
|
|
||||||
|
|
||||||
/// `Timeline` records a task's lifecycle.
|
|
||||||
public struct Timeline {
|
|
||||||
|
|
||||||
/// The time of initialization.
|
|
||||||
public let initialization = Date()
|
|
||||||
|
|
||||||
/// The time of first execution.
|
|
||||||
public internal(set) var firstExecution: Date?
|
|
||||||
|
|
||||||
/// The time of last execution.
|
|
||||||
public internal(set) var lastExecution: Date?
|
|
||||||
|
|
||||||
/// The time of estimated next execution.
|
|
||||||
public internal(set) var estimatedNextExecution: Date?
|
|
||||||
|
|
||||||
init() { }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Timeline: CustomStringConvertible {
|
|
||||||
|
|
||||||
/// A textual representation of this timeline.
|
|
||||||
public var description: String {
|
|
||||||
let f = DateFormatter()
|
|
||||||
f.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
|
|
||||||
|
|
||||||
let desc = { (d: Date?) -> String in
|
|
||||||
guard let d = d else { return "nil" }
|
|
||||||
return f.string(from: d)
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Timeline: { " +
|
|
||||||
"\"initialization\": \(desc(initialization))" +
|
|
||||||
"\"firstExecution\": \(desc(firstExecution)), " +
|
|
||||||
"\"lastExecution\": \(desc(lastExecution)), " +
|
|
||||||
"\"estimatedNextExecution\": \(desc(estimatedNextExecution))" +
|
|
||||||
" }"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Timeline: CustomDebugStringConvertible {
|
|
||||||
|
|
||||||
/// A textual representation of this timeline for debugging.
|
|
||||||
public var debugDescription: String {
|
|
||||||
return description
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,20 +7,21 @@ public enum Weekday: Int {
|
||||||
|
|
||||||
/// Returns dateComponenets of the weekday, using gregorian calender and
|
/// Returns dateComponenets of the weekday, using gregorian calender and
|
||||||
/// current time zone.
|
/// current time zone.
|
||||||
public func toDateComponents() -> DateComponents {
|
public func asDateComponents(_ timeZone: TimeZone = .current) -> DateComponents {
|
||||||
return DateComponents(
|
return DateComponents(
|
||||||
calendar: Calendar.gregorian,
|
calendar: Calendar.gregorian,
|
||||||
timeZone: TimeZone.current,
|
timeZone: timeZone,
|
||||||
weekday: rawValue
|
weekday: rawValue)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Date {
|
extension Date {
|
||||||
|
|
||||||
/// Returns a boolean value indicating whether this day is the weekday.
|
/// Returns a Boolean value indicating whether this date is the weekday in current time zone.
|
||||||
public func `is`(_ weekday: Weekday) -> Bool {
|
public func `is`(_ weekday: Weekday, in timeZone: TimeZone = .current) -> Bool {
|
||||||
return Calendar.gregorian.component(.weekday, from: self) == weekday.rawValue
|
var cal = Calendar.gregorian
|
||||||
|
cal.timeZone = timeZone
|
||||||
|
return cal.component(.weekday, from: self) == weekday.rawValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ extension Weekday: CustomStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this weekday.
|
/// A textual representation of this weekday.
|
||||||
///
|
///
|
||||||
/// Weekday: Friday
|
/// "Weekday: Friday"
|
||||||
public var description: String {
|
public var description: String {
|
||||||
return "Weekday: \(Calendar.gregorian.weekdaySymbols[rawValue - 1])"
|
return "Weekday: \(Calendar.gregorian.weekdaySymbols[rawValue - 1])"
|
||||||
}
|
}
|
||||||
|
@ -38,7 +39,7 @@ extension Weekday: CustomDebugStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this weekday for debugging.
|
/// A textual representation of this weekday for debugging.
|
||||||
///
|
///
|
||||||
/// Weekday: Friday
|
/// "Weekday: Friday"
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
return description
|
return description
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
//
|
|
||||||
// Log.swift
|
|
||||||
// Schedule
|
|
||||||
//
|
|
||||||
// Created by Quentin Jin on 2019/3/12.
|
|
||||||
// Copyright © 2019 Schedule. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
let fmt = ISO8601DateFormatter()
|
|
||||||
|
|
||||||
func Log(_ t: Any) {
|
|
||||||
|
|
||||||
let now = fmt.string(from: Date())
|
|
||||||
let thread = Thread.isMainThread ? "main" : "background"
|
|
||||||
|
|
||||||
print("\(now) [\(thread)] -> \(t)")
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
//
|
|
||||||
// main.swift
|
|
||||||
// Schedule
|
|
||||||
//
|
|
||||||
// Created by Quentin Jin on 2019/3/12.
|
|
||||||
// Copyright © 2019 Schedule. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Schedule
|
|
||||||
|
|
||||||
Log("Wake up")
|
|
||||||
|
|
||||||
let t1 = Plan.after(1.second).do {
|
|
||||||
Log("1 second passed!")
|
|
||||||
}
|
|
||||||
|
|
||||||
let t2 = Plan.after(1.minute, repeating: 0.5.seconds).do {
|
|
||||||
Log("Ping!")
|
|
||||||
}
|
|
||||||
|
|
||||||
let t3 = Plan.every("one minute and ten seconds").do {
|
|
||||||
Log("One minute and ten seconds have elapsed!")
|
|
||||||
}
|
|
||||||
|
|
||||||
let t4 = Plan.every(.monday, .tuesday, .wednesday, .thursday, .friday).at(6, 50).do {
|
|
||||||
Log("Get up!")
|
|
||||||
}
|
|
||||||
|
|
||||||
let t5 = Plan.every(.june(14)).at("08:59:59 am").do {
|
|
||||||
Log("Happy birthday!")
|
|
||||||
}
|
|
||||||
|
|
||||||
RunLoop.current.run()
|
|
|
@ -3,29 +3,42 @@ import XCTest
|
||||||
|
|
||||||
final class AtomicTests: XCTestCase {
|
final class AtomicTests: XCTestCase {
|
||||||
|
|
||||||
func testSnapshot() {
|
|
||||||
let i = Atomic<Int>(1)
|
|
||||||
XCTAssertEqual(i.snapshot(), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testRead() {
|
func testRead() {
|
||||||
let i = Atomic<Int>(1)
|
let i = Atomic<Int>(1)
|
||||||
i.read {
|
let val = i.read { $0 }
|
||||||
XCTAssertEqual($0, 1)
|
XCTAssertEqual(val, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testReadVoid() {
|
||||||
|
let i = Atomic<Int>(1)
|
||||||
|
var val = 0
|
||||||
|
i.readVoid { val = $0 }
|
||||||
|
XCTAssertEqual(val, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testWrite() {
|
func testWrite() {
|
||||||
let i = Atomic<Int>(1)
|
let i = Atomic<Int>(1)
|
||||||
i.write {
|
let val = i.write { v -> Int in
|
||||||
$0 += 1
|
v += 1
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
XCTAssertEqual(i.snapshot(), 2)
|
XCTAssertEqual(i.read { $0 }, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testWriteVoid() {
|
||||||
|
let i = Atomic<Int>(1)
|
||||||
|
var val = 0
|
||||||
|
i.writeVoid {
|
||||||
|
$0 += 1
|
||||||
|
val = $0
|
||||||
|
}
|
||||||
|
XCTAssertEqual(i.read { $0 }, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
static var allTests = [
|
static var allTests = [
|
||||||
("testSnapshot", testSnapshot),
|
|
||||||
("testRead", testRead),
|
("testRead", testRead),
|
||||||
("testWrite", testWrite)
|
("testReadVoid", testReadVoid),
|
||||||
|
("testWrite", testWrite),
|
||||||
|
("testWriteVoid", testWriteVoid)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
import XCTest
|
||||||
|
@testable import Schedule
|
||||||
|
|
||||||
|
final class BagTests: XCTestCase {
|
||||||
|
|
||||||
|
typealias Fn = () -> Int
|
||||||
|
|
||||||
|
func testBagKey() {
|
||||||
|
var g = BagKeyGenerator()
|
||||||
|
let k1 = g.next()
|
||||||
|
let k2 = g.next()
|
||||||
|
XCTAssertNotNil(k1)
|
||||||
|
XCTAssertNotNil(k2)
|
||||||
|
XCTAssertNotEqual(k1, k2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAppend() {
|
||||||
|
var bag = Bag<Fn>()
|
||||||
|
bag.append { 1 }
|
||||||
|
bag.append { 2 }
|
||||||
|
|
||||||
|
XCTAssertEqual(bag.count, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testValueForKey() {
|
||||||
|
var bag = Bag<Fn>()
|
||||||
|
let k1 = bag.append { 1 }
|
||||||
|
let k2 = bag.append { 2 }
|
||||||
|
|
||||||
|
let fn1 = bag.value(for: k1)
|
||||||
|
XCTAssertNotNil(fn1)
|
||||||
|
|
||||||
|
let fn2 = bag.value(for: k2)
|
||||||
|
XCTAssertNotNil(fn2)
|
||||||
|
|
||||||
|
guard let _fn1 = fn1, let _fn2 = fn2 else { return }
|
||||||
|
|
||||||
|
XCTAssertEqual(_fn1(), 1)
|
||||||
|
XCTAssertEqual(_fn2(), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRemoveValueForKey() {
|
||||||
|
var bag = Bag<Fn>()
|
||||||
|
|
||||||
|
let k1 = bag.append { 1 }
|
||||||
|
let k2 = bag.append { 2 }
|
||||||
|
|
||||||
|
let fn1 = bag.removeValue(for: k1)
|
||||||
|
XCTAssertNotNil(fn1)
|
||||||
|
XCTAssertNil(bag.value(for: k1))
|
||||||
|
|
||||||
|
let fn2 = bag.removeValue(for: k2)
|
||||||
|
XCTAssertNotNil(fn2)
|
||||||
|
XCTAssertNil(bag.removeValue(for: k2))
|
||||||
|
|
||||||
|
guard let _fn1 = fn1, let _fn2 = fn2 else { return }
|
||||||
|
|
||||||
|
XCTAssertEqual(_fn1(), 1)
|
||||||
|
XCTAssertEqual(_fn2(), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCount() {
|
||||||
|
var bag = Bag<Fn>()
|
||||||
|
|
||||||
|
let k1 = bag.append { 1 }
|
||||||
|
let k2 = bag.append { 2 }
|
||||||
|
|
||||||
|
XCTAssertEqual(bag.count, 2)
|
||||||
|
|
||||||
|
bag.removeValue(for: k1)
|
||||||
|
bag.removeValue(for: k2)
|
||||||
|
|
||||||
|
XCTAssertEqual(bag.count, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testRemoveAll() {
|
||||||
|
var bag = Bag<Fn>()
|
||||||
|
|
||||||
|
bag.append { 1 }
|
||||||
|
bag.append { 2 }
|
||||||
|
|
||||||
|
bag.removeAll()
|
||||||
|
XCTAssertEqual(bag.count, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSequence() {
|
||||||
|
var bag = Bag<Fn>()
|
||||||
|
bag.append { 0 }
|
||||||
|
bag.append { 1 }
|
||||||
|
bag.append { 2 }
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
for fn in bag {
|
||||||
|
XCTAssertEqual(fn(), i)
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var allTests = [
|
||||||
|
("testBagKey", testBagKey),
|
||||||
|
("testAppend", testAppend),
|
||||||
|
("testValueForKey", testValueForKey),
|
||||||
|
("testRemoveValueForKey", testRemoveValueForKey),
|
||||||
|
("testCount", testCount),
|
||||||
|
("testRemoveAll", testRemoveAll),
|
||||||
|
("testSequence", testSequence)
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,92 +0,0 @@
|
||||||
import XCTest
|
|
||||||
@testable import Schedule
|
|
||||||
|
|
||||||
final class CabinetTests: XCTestCase {
|
|
||||||
|
|
||||||
typealias Fn = () -> Int
|
|
||||||
|
|
||||||
func testCabinetKey() {
|
|
||||||
let key = CabinetKey(underlying: 0)
|
|
||||||
XCTAssertEqual(key.increased(), CabinetKey(underlying: 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAppend() {
|
|
||||||
var cabinet = Cabinet<Fn>()
|
|
||||||
let k1 = cabinet.append { 1 }
|
|
||||||
let k2 = cabinet.append { 2 }
|
|
||||||
|
|
||||||
XCTAssertEqual(k1.increased(), k2)
|
|
||||||
XCTAssertEqual(cabinet.count, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testGet() {
|
|
||||||
var cabinet = Cabinet<Fn>()
|
|
||||||
let k1 = cabinet.append { 1 }
|
|
||||||
let k2 = cabinet.append { 2 }
|
|
||||||
|
|
||||||
guard
|
|
||||||
let fn1 = cabinet.get(k1),
|
|
||||||
let fn2 = cabinet.get(k2)
|
|
||||||
else {
|
|
||||||
XCTFail()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
XCTAssertEqual(fn1(), 1)
|
|
||||||
XCTAssertEqual(fn2(), 2)
|
|
||||||
|
|
||||||
XCTAssertNil(cabinet.get(k2.increased()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDelete() {
|
|
||||||
var cabinet = Cabinet<Fn>()
|
|
||||||
|
|
||||||
let k1 = cabinet.append { 1 }
|
|
||||||
let k2 = cabinet.append { 2 }
|
|
||||||
|
|
||||||
XCTAssertEqual(cabinet.count, 2)
|
|
||||||
|
|
||||||
let fn1 = cabinet.delete(k1)
|
|
||||||
XCTAssertNotNil(fn1)
|
|
||||||
|
|
||||||
let fn2 = cabinet.delete(k2)
|
|
||||||
XCTAssertNotNil(fn2)
|
|
||||||
|
|
||||||
XCTAssertEqual(cabinet.count, 0)
|
|
||||||
|
|
||||||
XCTAssertNil(cabinet.delete(k2.increased()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testClear() {
|
|
||||||
var cabinet = Cabinet<Fn>()
|
|
||||||
|
|
||||||
cabinet.append { 1 }
|
|
||||||
cabinet.append { 2 }
|
|
||||||
|
|
||||||
XCTAssertEqual(cabinet.count, 2)
|
|
||||||
|
|
||||||
cabinet.clear()
|
|
||||||
XCTAssertEqual(cabinet.count, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSequence() {
|
|
||||||
var cabinet = Cabinet<Fn>()
|
|
||||||
cabinet.append { 0 }
|
|
||||||
cabinet.append { 1 }
|
|
||||||
cabinet.append { 2 }
|
|
||||||
|
|
||||||
var i = 0
|
|
||||||
for fn in cabinet {
|
|
||||||
XCTAssertEqual(fn(), i)
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static var allTests = [
|
|
||||||
("testCabinetKey", testCabinetKey),
|
|
||||||
("testAppend", testAppend),
|
|
||||||
("testGet", testGet),
|
|
||||||
("testDelete", testDelete),
|
|
||||||
("testClear", testClear),
|
|
||||||
("testSequence", testSequence)
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,153 +0,0 @@
|
||||||
import XCTest
|
|
||||||
@testable import Schedule
|
|
||||||
|
|
||||||
final class DateTimeTests: XCTestCase {
|
|
||||||
|
|
||||||
func testInterval() {
|
|
||||||
|
|
||||||
XCTAssertTrue((-1).second.isNegative)
|
|
||||||
XCTAssertTrue(1.second.isPositive)
|
|
||||||
XCTAssertEqual(1.1.second.magnitude, 1.1.second.nanoseconds)
|
|
||||||
XCTAssertEqual(1.second.opposite, (-1).second)
|
|
||||||
|
|
||||||
XCTAssertEqual(7.day.hashValue, 1.week.hashValue)
|
|
||||||
XCTAssertEqual(7.day, 1.week)
|
|
||||||
|
|
||||||
XCTAssertEqual((-2).seconds.compare(1.second), .orderedAscending)
|
|
||||||
XCTAssertTrue(1.1.second > 1.0.second)
|
|
||||||
XCTAssertTrue(3.days < 1.week)
|
|
||||||
XCTAssertTrue(4.day >= 4.days)
|
|
||||||
XCTAssertTrue(-2.seconds < 1.seconds)
|
|
||||||
|
|
||||||
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.longest([]), nil)
|
|
||||||
XCTAssertEqual(Interval.shortest(1.hour, 59.minutes, 2999.seconds), 2999.seconds)
|
|
||||||
XCTAssertEqual(Interval.shortest([]), nil)
|
|
||||||
|
|
||||||
XCTAssertEqual(1.second * 60, 1.minute)
|
|
||||||
XCTAssertEqual(59.minutes + 60.seconds, 1.hour)
|
|
||||||
XCTAssertEqual(1.week - 24.hours, 6.days)
|
|
||||||
var i0 = 1.day
|
|
||||||
i0 += 1.day
|
|
||||||
XCTAssertEqual(i0, 2.days)
|
|
||||||
XCTAssertEqual(-(1.second), (-1).second)
|
|
||||||
|
|
||||||
let i1 = Interval(seconds: 24 * 60 * 60)
|
|
||||||
XCTAssertEqual(1.nanosecond * i1.asNanoseconds(), 1.day)
|
|
||||||
XCTAssertEqual(2.microsecond * i1.asMicroseconds(), 2.days)
|
|
||||||
XCTAssertEqual(3.millisecond * i1.asMilliseconds(), 3.days)
|
|
||||||
XCTAssertEqual(4.second * i1.asSeconds(), 4.days)
|
|
||||||
XCTAssertEqual(5.1.minute * i1.asMinutes(), 5.1.days)
|
|
||||||
XCTAssertEqual(6.2.hour * i1.asHours(), 6.2.days)
|
|
||||||
XCTAssertEqual(7.3.day * i1.asDays(), 7.3.days)
|
|
||||||
XCTAssertEqual(1.week * i1.asWeeks(), 1.days)
|
|
||||||
|
|
||||||
let date0 = Date()
|
|
||||||
let date1 = date0.addingTimeInterval(100)
|
|
||||||
XCTAssertEqual(date0.interval(since: date1), date0.timeIntervalSince(date1).seconds)
|
|
||||||
XCTAssertEqual(date0.adding(1.seconds), date0.addingTimeInterval(1))
|
|
||||||
XCTAssertEqual(date0 + 1.seconds, date0.addingTimeInterval(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMonthday() {
|
|
||||||
let d = Date(year: 2019, month: 1, day: 1)
|
|
||||||
XCTAssertTrue(d.is(.january(1)))
|
|
||||||
|
|
||||||
XCTAssertEqual(Monthday.january(1).toDateComponents().month, 1)
|
|
||||||
XCTAssertEqual(Monthday.february(1).toDateComponents().month, 2)
|
|
||||||
XCTAssertEqual(Monthday.march(1).toDateComponents().month, 3)
|
|
||||||
XCTAssertEqual(Monthday.april(1).toDateComponents().month, 4)
|
|
||||||
XCTAssertEqual(Monthday.may(1).toDateComponents().month, 5)
|
|
||||||
XCTAssertEqual(Monthday.june(1).toDateComponents().month, 6)
|
|
||||||
XCTAssertEqual(Monthday.july(1).toDateComponents().month, 7)
|
|
||||||
XCTAssertEqual(Monthday.august(1).toDateComponents().month, 8)
|
|
||||||
XCTAssertEqual(Monthday.september(1).toDateComponents().month, 9)
|
|
||||||
XCTAssertEqual(Monthday.october(1).toDateComponents().month, 10)
|
|
||||||
XCTAssertEqual(Monthday.november(1).toDateComponents().month, 11)
|
|
||||||
XCTAssertEqual(Monthday.december(1).toDateComponents().month, 12)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPeriod() {
|
|
||||||
let p0 = (1.year + 2.years + 1.month + 2.months + 3.days).tidied(to: .day)
|
|
||||||
XCTAssertEqual(p0.years, 3)
|
|
||||||
XCTAssertEqual(p0.months, 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")?.tidied(to: .day)
|
|
||||||
XCTAssertNotNil(p2)
|
|
||||||
XCTAssertEqual(p2!.hours, 2)
|
|
||||||
XCTAssertEqual(p2!.minutes, 10)
|
|
||||||
let p3 = Period("1 year, 2 months and 3 days")?.tidied(to: .day)
|
|
||||||
XCTAssertNotNil(p3)
|
|
||||||
XCTAssertEqual(p3!.years, 1)
|
|
||||||
XCTAssertEqual(p3!.months, 2)
|
|
||||||
XCTAssertEqual(p3!.days, 3)
|
|
||||||
|
|
||||||
Period.registerQuantifier("many", for: 100 * 1000)
|
|
||||||
let p4 = Period("many days")
|
|
||||||
XCTAssertEqual(p4!.days, 100 * 1000)
|
|
||||||
|
|
||||||
let date = Date(year: 1989, month: 6, day: 4) + 1.year
|
|
||||||
let year = date.dateComponents.year
|
|
||||||
XCTAssertEqual(year, 1990)
|
|
||||||
|
|
||||||
let p5 = Period(hours: 25).tidied(to: .day)
|
|
||||||
XCTAssertEqual(p5.days, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testTime() {
|
|
||||||
let t0 = Time(hour: -1, minute: -2, second: -3, nanosecond: -4)
|
|
||||||
XCTAssertNil(t0)
|
|
||||||
|
|
||||||
let t1 = Time("11:12:13.456")
|
|
||||||
XCTAssertNotNil(t1)
|
|
||||||
XCTAssertEqual(t1?.hour, 11)
|
|
||||||
XCTAssertEqual(t1?.minute, 12)
|
|
||||||
XCTAssertEqual(t1?.second, 13)
|
|
||||||
if let i = t1?.nanosecond.nanoseconds {
|
|
||||||
XCTAssertTrue(i.isAlmostEqual(to: (0.456.second.nanoseconds).nanoseconds, leeway: 0.001.seconds))
|
|
||||||
}
|
|
||||||
|
|
||||||
let components = t1?.toDateComponents()
|
|
||||||
XCTAssertEqual(components?.hour, 11)
|
|
||||||
XCTAssertEqual(components?.minute, 12)
|
|
||||||
XCTAssertEqual(components?.second, 13)
|
|
||||||
if let i = components?.nanosecond?.nanoseconds {
|
|
||||||
XCTAssertTrue(i.isAlmostEqual(to: (0.456.second.nanoseconds).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)
|
|
||||||
|
|
||||||
let t4 = Time("schedule")
|
|
||||||
XCTAssertNil(t4)
|
|
||||||
|
|
||||||
XCTAssertEqual(Time(hour: 1)!.intervalSinceStartOfDay, 1.hour)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testWeekday() {
|
|
||||||
// Be careful the time zone problem.
|
|
||||||
let d = Date(year: 2019, month: 1, day: 1)
|
|
||||||
XCTAssertTrue(d.is(.tuesday))
|
|
||||||
|
|
||||||
XCTAssertEqual(Weekday.monday.toDateComponents().weekday!, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
static var allTests = [
|
|
||||||
("testInterval", testInterval),
|
|
||||||
("testMonthday", testMonthday),
|
|
||||||
("testPeriod", testPeriod),
|
|
||||||
("testTime", testTime),
|
|
||||||
("testWeekday", testWeekday)
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
import XCTest
|
|
||||||
@testable import Schedule
|
|
||||||
|
|
||||||
#if canImport(ObjectiveC)
|
|
||||||
|
|
||||||
final class DeinitObserverTests: XCTestCase {
|
|
||||||
|
|
||||||
func testObserver() {
|
|
||||||
var i = 0
|
|
||||||
var fn = {
|
|
||||||
let obj = NSObject()
|
|
||||||
DeinitObserver.observe(obj) {
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn()
|
|
||||||
XCTAssertEqual(i, 1)
|
|
||||||
|
|
||||||
fn = {
|
|
||||||
let obj = NSObject()
|
|
||||||
let observer = DeinitObserver.observe(obj) {
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
observer.invalidate()
|
|
||||||
}
|
|
||||||
fn()
|
|
||||||
XCTAssertEqual(i, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
static var allTests = [
|
|
||||||
("testObserver", testObserver)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -16,21 +16,16 @@ final class ExtensionsTests: XCTestCase {
|
||||||
XCTAssertEqual(i.clampedAdding(1), Int.max)
|
XCTAssertEqual(i.clampedAdding(1), Int.max)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testClampedSubtracting() {
|
|
||||||
let i = Int.min
|
|
||||||
XCTAssertEqual(i.clampedSubtracting(1), Int.min)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testStartOfToday() {
|
func testStartOfToday() {
|
||||||
let components = Date().startOfToday.dateComponents
|
let components = Date().startOfToday.dateComponents
|
||||||
guard
|
|
||||||
let h = components.hour,
|
let h = components.hour
|
||||||
let m = components.minute,
|
let m = components.minute
|
||||||
let s = components.second
|
let s = components.second
|
||||||
else {
|
XCTAssertNotNil(h)
|
||||||
XCTFail()
|
XCTAssertNotNil(m)
|
||||||
return
|
XCTAssertNotNil(s)
|
||||||
}
|
|
||||||
XCTAssertEqual(h, 0)
|
XCTAssertEqual(h, 0)
|
||||||
XCTAssertEqual(m, 0)
|
XCTAssertEqual(m, 0)
|
||||||
XCTAssertEqual(s, 0)
|
XCTAssertEqual(s, 0)
|
||||||
|
@ -39,7 +34,6 @@ final class ExtensionsTests: XCTestCase {
|
||||||
static var allTests = [
|
static var allTests = [
|
||||||
("testClampedToInt", testClampedToInt),
|
("testClampedToInt", testClampedToInt),
|
||||||
("testClampedAdding", testClampedAdding),
|
("testClampedAdding", testClampedAdding),
|
||||||
("testClampedSubtracting", testClampedSubtracting),
|
|
||||||
("testStartOfToday", testStartOfToday)
|
("testStartOfToday", testStartOfToday)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ extension Date {
|
||||||
year: Int, month: Int, day: Int,
|
year: Int, month: Int, day: Int,
|
||||||
hour: Int = 0, minute: Int = 0, second: Int = 0,
|
hour: Int = 0, minute: Int = 0, second: Int = 0,
|
||||||
nanosecond: Int = 0
|
nanosecond: Int = 0
|
||||||
) {
|
) {
|
||||||
let components = DateComponents(
|
let components = DateComponents(
|
||||||
calendar: Calendar.gregorian,
|
calendar: Calendar.gregorian,
|
||||||
timeZone: TimeZone.current,
|
timeZone: TimeZone.current,
|
||||||
|
@ -26,7 +26,7 @@ extension Date {
|
||||||
extension Interval {
|
extension Interval {
|
||||||
|
|
||||||
func isAlmostEqual(to interval: Interval, leeway: Interval) -> Bool {
|
func isAlmostEqual(to interval: Interval, leeway: Interval) -> Bool {
|
||||||
return (interval - self).magnitude <= leeway.magnitude
|
return (interval - self).abs <= leeway.abs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,3 +76,8 @@ extension DispatchQueue {
|
||||||
return DispatchQueue.getSpecific(key: key) != nil
|
return DispatchQueue.getSpecific(key: key) != nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension TimeZone {
|
||||||
|
|
||||||
|
static let shanghai = TimeZone(identifier: "Asia/Shanghai")!
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
//
|
||||||
|
// IntervalTests.swift
|
||||||
|
// ScheduleTests
|
||||||
|
//
|
||||||
|
// Created by Quentin MED on 2019/4/4.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import Schedule
|
||||||
|
|
||||||
|
final class IntervalTests: XCTestCase {
|
||||||
|
|
||||||
|
private let leeway = 0.01.second
|
||||||
|
|
||||||
|
func testEquatable() {
|
||||||
|
XCTAssertEqual(1.second, 1.second)
|
||||||
|
XCTAssertEqual(1.week, 7.days)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIsNegative() {
|
||||||
|
XCTAssertFalse(1.second.isNegative)
|
||||||
|
XCTAssertTrue((-1).second.isNegative)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAbs() {
|
||||||
|
XCTAssertEqual(1.second, (-1).second.abs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNegated() {
|
||||||
|
XCTAssertEqual(1.second.negated, (-1).second)
|
||||||
|
XCTAssertEqual(1.second.negated.negated, 1.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCompare() {
|
||||||
|
XCTAssertEqual((-1).second.compare(1.second), ComparisonResult.orderedAscending)
|
||||||
|
XCTAssertEqual(8.days.compare(1.week), ComparisonResult.orderedDescending)
|
||||||
|
XCTAssertEqual(1.day.compare(24.hours), ComparisonResult.orderedSame)
|
||||||
|
|
||||||
|
XCTAssertTrue(23.hours < 1.day)
|
||||||
|
XCTAssertTrue(25.hours > 1.day)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLongerShorter() {
|
||||||
|
XCTAssertTrue((-25).hour.isLonger(than: 1.day))
|
||||||
|
XCTAssertTrue(1.week.isShorter(than: 8.days))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMultiplying() {
|
||||||
|
XCTAssertEqual(7.days * 2, 2.week)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAdding() {
|
||||||
|
XCTAssertEqual(6.days + 1.day, 1.week)
|
||||||
|
|
||||||
|
XCTAssertEqual(1.1.weeks, 1.week + 0.1.weeks)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testOperators() {
|
||||||
|
XCTAssertEqual(1.week - 6.days, 1.day)
|
||||||
|
|
||||||
|
var i = 6.days
|
||||||
|
i += 1.day
|
||||||
|
XCTAssertEqual(i, 1.week)
|
||||||
|
|
||||||
|
XCTAssertEqual(-(7.days), (-1).week)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAs() {
|
||||||
|
XCTAssertEqual(1.millisecond.asNanoseconds(), 1.microsecond.asNanoseconds() * pow(10, 3))
|
||||||
|
|
||||||
|
XCTAssertEqual(1.second.asNanoseconds(), pow(10, 9))
|
||||||
|
XCTAssertEqual(1.second.asMicroseconds(), pow(10, 6))
|
||||||
|
XCTAssertEqual(1.second.asMilliseconds(), pow(10, 3))
|
||||||
|
|
||||||
|
XCTAssertEqual(1.minute.asSeconds(), 60)
|
||||||
|
XCTAssertEqual(1.hour.asMinutes(), 60)
|
||||||
|
XCTAssertEqual(1.day.asHours(), 24)
|
||||||
|
XCTAssertEqual(1.week.asDays(), 7)
|
||||||
|
XCTAssertEqual(7.days.asWeeks(), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDate() {
|
||||||
|
let date0 = Date()
|
||||||
|
let date1 = date0.addingTimeInterval(100)
|
||||||
|
|
||||||
|
XCTAssertTrue(date1.intervalSinceNow.isAlmostEqual(to: 100.seconds, leeway: leeway))
|
||||||
|
|
||||||
|
XCTAssertEqual(date0.interval(since: date1), date0.timeIntervalSince(date1).seconds)
|
||||||
|
|
||||||
|
XCTAssertEqual(date0.adding(1.seconds), date0.addingTimeInterval(1))
|
||||||
|
XCTAssertEqual(date0 + 1.seconds, date0.addingTimeInterval(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDescription() {
|
||||||
|
XCTAssertEqual(1.nanosecond.debugDescription, "Interval: 1 nanosecond(s)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static var allTests = [
|
||||||
|
("testEquatable", testEquatable),
|
||||||
|
("testIsNegative", testIsNegative),
|
||||||
|
("testAbs", testAbs),
|
||||||
|
("testNegated", testNegated),
|
||||||
|
("testCompare", testCompare),
|
||||||
|
("testLongerShorter", testLongerShorter),
|
||||||
|
("testMultiplying", testMultiplying),
|
||||||
|
("testAdding", testAdding),
|
||||||
|
("testOperators", testOperators),
|
||||||
|
("testAs", testAs),
|
||||||
|
("testDate", testDate)
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import XCTest
|
||||||
|
@testable import Schedule
|
||||||
|
|
||||||
|
final class MonthdayTests: XCTestCase {
|
||||||
|
|
||||||
|
func testIs() {
|
||||||
|
let d = Date(year: 2019, month: 1, day: 1)
|
||||||
|
XCTAssertTrue(d.is(.january(1), in: TimeZone.shanghai))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAsDateComponents() {
|
||||||
|
let comps = Monthday.april(1).asDateComponents()
|
||||||
|
XCTAssertEqual(comps.month, 4)
|
||||||
|
XCTAssertEqual(comps.day, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDescription() {
|
||||||
|
let md = Monthday.april(1)
|
||||||
|
XCTAssertEqual(md.debugDescription, "Monthday: April 1st")
|
||||||
|
}
|
||||||
|
|
||||||
|
static var allTests = [
|
||||||
|
("testIs", testIs),
|
||||||
|
("testAsDateComponents", testAsDateComponents),
|
||||||
|
("testDescription", testDescription)
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
import XCTest
|
||||||
|
@testable import Schedule
|
||||||
|
|
||||||
|
final class PeriodTests: XCTestCase {
|
||||||
|
|
||||||
|
func testPeriod() {
|
||||||
|
let period = (1.year + 2.years + 1.month + 2.months + 3.days)
|
||||||
|
XCTAssertEqual(period.years, 3)
|
||||||
|
XCTAssertEqual(period.months, 3)
|
||||||
|
XCTAssertEqual(period.days, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInitWithString() {
|
||||||
|
let p1 = Period("one 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)
|
||||||
|
|
||||||
|
Period.registerQuantifier("many", for: 100 * 1000)
|
||||||
|
let p4 = Period("many days")
|
||||||
|
XCTAssertEqual(p4!.days, 100 * 1000)
|
||||||
|
|
||||||
|
let p5 = Period("hi, 😈")
|
||||||
|
XCTAssertNil(p5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAdd() {
|
||||||
|
XCTAssertEqual(1.month.adding(1.month).months, 2)
|
||||||
|
XCTAssertEqual(Period(days: 1).adding(1.day).days, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTidy() {
|
||||||
|
let period = 1.month.adding(25.hour).tidied(to: .day)
|
||||||
|
XCTAssertEqual(period.days, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAsDateComponents() {
|
||||||
|
let period = Period(years: 1, months: 2, days: 3, hours: 4, minutes: 5, seconds: 6, nanoseconds: 7)
|
||||||
|
let comps = period.asDateComponents()
|
||||||
|
XCTAssertEqual(comps.year, 1)
|
||||||
|
XCTAssertEqual(comps.month, 2)
|
||||||
|
XCTAssertEqual(comps.day, 3)
|
||||||
|
XCTAssertEqual(comps.hour, 4)
|
||||||
|
XCTAssertEqual(comps.minute, 5)
|
||||||
|
XCTAssertEqual(comps.second, 6)
|
||||||
|
XCTAssertEqual(comps.nanosecond, 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDate() {
|
||||||
|
let d = Date(year: 1989, month: 6, day: 4) + 1.year
|
||||||
|
let year = d.dateComponents.year
|
||||||
|
XCTAssertEqual(year, 1990)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDescription() {
|
||||||
|
let period = Period(years: 1, nanoseconds: 1)
|
||||||
|
XCTAssertEqual(period.debugDescription, "Period: 1 year(s) 1 nanosecond(s)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static var allTests = [
|
||||||
|
("testPeriod", testPeriod),
|
||||||
|
("testInitWithString", testInitWithString),
|
||||||
|
("testAdd", testAdd),
|
||||||
|
("testTidy", testTidy),
|
||||||
|
("testAsDateComponents", testAsDateComponents),
|
||||||
|
("testDate", testDate),
|
||||||
|
("testDescription", testDescription)
|
||||||
|
]
|
||||||
|
}
|
|
@ -3,36 +3,46 @@ import XCTest
|
||||||
|
|
||||||
final class PlanTests: XCTestCase {
|
final class PlanTests: XCTestCase {
|
||||||
|
|
||||||
let leeway = 0.01.seconds
|
private let leeway = 0.01.seconds
|
||||||
|
|
||||||
func testMake() {
|
func testOfIntervals() {
|
||||||
let intervals = [1.second, 2.hours, 3.days, 4.weeks]
|
let ints = [1.second, 2.hours, 3.days, 4.weeks]
|
||||||
let s0 = Plan.of(intervals[0], intervals[1], intervals[2], intervals[3])
|
let p = Plan.of(ints)
|
||||||
XCTAssertTrue(s0.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
|
XCTAssertTrue(p.makeIterator().isAlmostEqual(to: ints, leeway: leeway))
|
||||||
let s1 = Plan.from(intervals)
|
}
|
||||||
XCTAssertTrue(s1.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
|
|
||||||
|
|
||||||
let d0 = Date() + intervals[0]
|
func testOfDates() {
|
||||||
let d1 = d0 + intervals[1]
|
let ints = [1.second, 2.hours, 3.days, 4.weeks]
|
||||||
let d2 = d1 + intervals[2]
|
|
||||||
let d3 = d2 + intervals[3]
|
|
||||||
|
|
||||||
let s2 = Plan.of(d0, d1, d2, d3)
|
let d0 = Date() + ints[0]
|
||||||
XCTAssertTrue(s2.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
|
let d1 = d0 + ints[1]
|
||||||
|
let d2 = d1 + ints[2]
|
||||||
|
let d3 = d2 + ints[3]
|
||||||
|
|
||||||
let longTime = (100 * 365).days
|
let p = Plan.of(d0, d1, d2, d3)
|
||||||
XCTAssertTrue(Plan.distantPast.makeIterator().next()!.isLonger(than: longTime))
|
XCTAssertTrue(p.makeIterator().isAlmostEqual(to: ints, leeway: leeway))
|
||||||
XCTAssertTrue(Plan.distantFuture.makeIterator().next()!.isLonger(than: longTime))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDates() {
|
func testDates() {
|
||||||
let iterator = Plan.of(1.days, 2.weeks).dates.makeIterator()
|
let dates = Plan.of(1.days, 2.weeks).dates.makeIterator()
|
||||||
var next = iterator.next()
|
|
||||||
XCTAssertNotNil(next)
|
var n = dates.next()
|
||||||
XCTAssertTrue(next!.intervalSinceNow.isAlmostEqual(to: 1.days, leeway: leeway))
|
XCTAssertNotNil(n)
|
||||||
next = iterator.next()
|
XCTAssertTrue(n!.intervalSinceNow.isAlmostEqual(to: 1.days, leeway: leeway))
|
||||||
XCTAssertNotNil(next)
|
|
||||||
XCTAssertTrue(next!.intervalSinceNow.isAlmostEqual(to: 2.weeks + 1.days, leeway: leeway))
|
n = dates.next()
|
||||||
|
XCTAssertNotNil(n)
|
||||||
|
XCTAssertTrue(n!.intervalSinceNow.isAlmostEqual(to: 2.weeks + 1.days, leeway: leeway))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDistant() {
|
||||||
|
let distantPast = Plan.distantPast.makeIterator().next()
|
||||||
|
XCTAssertNotNil(distantPast)
|
||||||
|
XCTAssertTrue(distantPast!.isAlmostEqual(to: Date.distantPast.intervalSinceNow, leeway: leeway))
|
||||||
|
|
||||||
|
let distantFuture = Plan.distantFuture.makeIterator().next()
|
||||||
|
XCTAssertNotNil(distantFuture)
|
||||||
|
XCTAssertTrue(distantFuture!.isAlmostEqual(to: Date.distantFuture.intervalSinceNow, leeway: leeway))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNever() {
|
func testNever() {
|
||||||
|
@ -40,32 +50,25 @@ final class PlanTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testConcat() {
|
func testConcat() {
|
||||||
let s0: [Interval] = [1.second, 2.minutes, 3.hours]
|
let p0: [Interval] = [1.second, 2.minutes, 3.hours]
|
||||||
let s1: [Interval] = [4.days, 5.weeks]
|
let p1: [Interval] = [4.days, 5.weeks]
|
||||||
let s3 = Plan.from(s0).concat(Plan.from(s1))
|
let p2 = Plan.of(p0).concat(Plan.of(p1))
|
||||||
let s4 = Plan.from(s0 + s1)
|
let p3 = Plan.of(p0 + p1)
|
||||||
XCTAssertTrue(s3.isAlmostEqual(to: s4, leeway: leeway))
|
XCTAssertTrue(p2.isAlmostEqual(to: p3, leeway: leeway))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMerge() {
|
func testMerge() {
|
||||||
let intervals0: [Interval] = [1.second, 2.minutes, 1.hour]
|
let ints0: [Interval] = [1.second, 2.minutes, 1.hour]
|
||||||
let intervals1: [Interval] = [2.seconds, 1.minutes, 1.seconds]
|
let ints1: [Interval] = [2.seconds, 1.minutes, 1.seconds]
|
||||||
let scheudle0 = Plan.from(intervals0).merge(Plan.from(intervals1))
|
let p0 = Plan.of(ints0).merge(Plan.of(ints1))
|
||||||
let scheudle1 = Plan.of(1.second, 1.second, 1.minutes, 1.seconds, 58.seconds, 1.hour)
|
let p1 = Plan.of(1.second, 1.second, 1.minutes, 1.seconds, 58.seconds, 1.hour)
|
||||||
XCTAssertTrue(scheudle0.isAlmostEqual(to: scheudle1, leeway: leeway))
|
XCTAssertTrue(p0.isAlmostEqual(to: p1, leeway: leeway))
|
||||||
}
|
|
||||||
|
|
||||||
func testAt() {
|
|
||||||
let s = Plan.at(Date() + 1.second)
|
|
||||||
let next = s.makeIterator().next()
|
|
||||||
XCTAssertNotNil(next)
|
|
||||||
XCTAssertTrue(next!.isAlmostEqual(to: 1.second, leeway: leeway))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFirst() {
|
func testFirst() {
|
||||||
var count = 10
|
var count = 10
|
||||||
let s = Plan.every(1.second).first(count)
|
let p = Plan.every(1.second).first(count)
|
||||||
let i = s.makeIterator()
|
let i = p.makeIterator()
|
||||||
while count > 0 {
|
while count > 0 {
|
||||||
XCTAssertNotNil(i.next())
|
XCTAssertNotNil(i.next())
|
||||||
count -= 1
|
count -= 1
|
||||||
|
@ -75,29 +78,36 @@ final class PlanTests: XCTestCase {
|
||||||
|
|
||||||
func testUntil() {
|
func testUntil() {
|
||||||
let until = Date() + 10.seconds
|
let until = Date() + 10.seconds
|
||||||
let s = Plan.every(1.second).until(until).dates
|
let p = Plan.every(1.second).until(until).dates
|
||||||
let i = s.makeIterator()
|
let i = p.makeIterator()
|
||||||
while let date = i.next() {
|
while let date = i.next() {
|
||||||
XCTAssertLessThan(date, until)
|
XCTAssertLessThan(date, until)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNow() {
|
func testNow() {
|
||||||
let s0 = Plan.now
|
let p0 = Plan.now
|
||||||
let s1 = Plan.of(Date())
|
let p1 = Plan.of(Date())
|
||||||
XCTAssertTrue(s0.isAlmostEqual(to: s1, leeway: leeway))
|
XCTAssertTrue(p0.isAlmostEqual(to: p1, leeway: leeway))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAt() {
|
||||||
|
let p = Plan.at(Date() + 1.second)
|
||||||
|
let next = p.makeIterator().next()
|
||||||
|
XCTAssertNotNil(next)
|
||||||
|
XCTAssertTrue(next!.isAlmostEqual(to: 1.second, leeway: leeway))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAfterAndRepeating() {
|
func testAfterAndRepeating() {
|
||||||
let s0 = Plan.after(1.day, repeating: 1.hour).first(3)
|
let p0 = Plan.after(1.day, repeating: 1.hour).first(3)
|
||||||
let s1 = Plan.of(1.day, 1.hour, 1.hour)
|
let p1 = Plan.of(1.day, 1.hour, 1.hour)
|
||||||
XCTAssertTrue(s0.isAlmostEqual(to: s1, leeway: leeway))
|
XCTAssertTrue(p0.isAlmostEqual(to: p1, leeway: leeway))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEveryPeriod() {
|
func testEveryPeriod() {
|
||||||
let s = Plan.every("1 year").first(10)
|
let p = Plan.every("1 year").first(10)
|
||||||
var date = Date()
|
var date = Date()
|
||||||
for i in s.dates {
|
for i in p.dates {
|
||||||
XCTAssertEqual(i.dateComponents.year!, date.dateComponents.year! + 1)
|
XCTAssertEqual(i.dateComponents.year!, date.dateComponents.year! + 1)
|
||||||
XCTAssertEqual(i.dateComponents.month!, date.dateComponents.month!)
|
XCTAssertEqual(i.dateComponents.month!, date.dateComponents.month!)
|
||||||
XCTAssertEqual(i.dateComponents.day!, date.dateComponents.day!)
|
XCTAssertEqual(i.dateComponents.day!, date.dateComponents.day!)
|
||||||
|
@ -106,121 +116,47 @@ final class PlanTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEveryWeekday() {
|
func testEveryWeekday() {
|
||||||
let s = Plan.every(.friday, .monday).at("11:11:00").first(5)
|
let p = Plan.every(.friday, .monday).at("11:11:00").first(5)
|
||||||
for i in s.dates {
|
for i in p.dates {
|
||||||
XCTAssertTrue(i.dateComponents.weekday == 6 || i.dateComponents.weekday == 2)
|
XCTAssertTrue(i.dateComponents.weekday == 6 || i.dateComponents.weekday == 2)
|
||||||
XCTAssertEqual(i.dateComponents.hour, 11)
|
XCTAssertEqual(i.dateComponents.hour, 11)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEveryMonthday() {
|
func testEveryMonthday() {
|
||||||
let s = Plan.every(.april(1), .october(1)).at(11, 11).first(5)
|
let p = Plan.every(.april(1), .october(1)).at(11, 11).first(5)
|
||||||
for i in s.dates {
|
for i in p.dates {
|
||||||
XCTAssertTrue(i.dateComponents.month == 4 || i.dateComponents.month == 10)
|
XCTAssertTrue(i.dateComponents.month == 4 || i.dateComponents.month == 10)
|
||||||
XCTAssertEqual(i.dateComponents.day, 1)
|
XCTAssertEqual(i.dateComponents.day, 1)
|
||||||
XCTAssertEqual(i.dateComponents.hour, 11)
|
XCTAssertEqual(i.dateComponents.hour, 11)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPassingEmptyArrays() {
|
|
||||||
XCTAssertTrue(Plan.of([Interval]()).isNever())
|
|
||||||
XCTAssertTrue(Plan.of([Date]()).isNever())
|
|
||||||
|
|
||||||
XCTAssertTrue(Plan.every([Weekday]()).at(11, 11).isNever())
|
|
||||||
XCTAssertTrue(Plan.every([Monthday]()).at(11, 11).isNever())
|
|
||||||
|
|
||||||
XCTAssertTrue(Plan.every(.monday).at([]).isNever())
|
|
||||||
|
|
||||||
XCTAssertTrue(Plan.every([Weekday]()).at("11:11:00").isNever())
|
|
||||||
}
|
|
||||||
|
|
||||||
func testIntervalOffset() {
|
|
||||||
// Non-offset plan
|
|
||||||
let e1 = expectation(description: "testIntervalOffset_1")
|
|
||||||
let plan1 = Plan.after(1.second)
|
|
||||||
var date1: Date?
|
|
||||||
|
|
||||||
// Offset plan
|
|
||||||
let e2 = expectation(description: "testIntervalOffset_2")
|
|
||||||
let plan2 = plan1.offset(by: 1.second)
|
|
||||||
var date2: Date?
|
|
||||||
|
|
||||||
let task1 = plan1.do { date1 = Date(); e1.fulfill() }
|
|
||||||
let task2 = plan2.do { date2 = Date(); e2.fulfill() }
|
|
||||||
_ = task1
|
|
||||||
_ = task2
|
|
||||||
|
|
||||||
waitForExpectations(timeout: 3.5)
|
|
||||||
|
|
||||||
XCTAssertNotNil(date1)
|
|
||||||
XCTAssertNotNil(date2)
|
|
||||||
XCTAssertTrue(date2!.interval(since: date1!).isAlmostEqual(to: 1.second, leeway: 0.1.seconds))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testNegativeIntervalOffset() {
|
|
||||||
// Non-offset plan
|
|
||||||
let e1 = expectation(description: "testIntervalOffset_1")
|
|
||||||
let plan1 = Plan.after(2.seconds)
|
|
||||||
var date1: Date?
|
|
||||||
|
|
||||||
// Offset plan
|
|
||||||
let e2 = expectation(description: "testIntervalOffset_2")
|
|
||||||
let plan2 = plan1.offset(by: -1.second)
|
|
||||||
var date2: Date?
|
|
||||||
|
|
||||||
let task1 = plan1.do { date1 = Date(); e1.fulfill() }
|
|
||||||
let task2 = plan2.do { date2 = Date(); e2.fulfill() }
|
|
||||||
_ = task1
|
|
||||||
_ = task2
|
|
||||||
|
|
||||||
waitForExpectations(timeout: 2.5)
|
|
||||||
|
|
||||||
XCTAssertNotNil(date1)
|
|
||||||
XCTAssertNotNil(date2)
|
|
||||||
XCTAssertTrue(date2!.interval(since: date1!).isAlmostEqual(to: -1.second, leeway: 0.1.seconds))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testNilIntervalOffset() {
|
|
||||||
// Non-offset plan
|
|
||||||
let e1 = expectation(description: "testIntervalOffset_1")
|
|
||||||
let plan1 = Plan.after(1.second)
|
|
||||||
var date1: Date?
|
|
||||||
|
|
||||||
// Offset plan
|
|
||||||
let e2 = expectation(description: "testIntervalOffset_2")
|
|
||||||
let plan2 = plan1.offset(by: nil)
|
|
||||||
var date2: Date?
|
|
||||||
|
|
||||||
let task1 = plan1.do { date1 = Date(); e1.fulfill() }
|
|
||||||
let task2 = plan2.do { date2 = Date(); e2.fulfill() }
|
|
||||||
|
|
||||||
_ = task1
|
func testOffset() {
|
||||||
_ = task2
|
let p1 = Plan.after(1.second).first(100)
|
||||||
|
let p2 = p1.offset(by: 1.second).first(100)
|
||||||
waitForExpectations(timeout: 1.5)
|
|
||||||
|
for (d1, d2) in zip(p1.dates, p2.dates) {
|
||||||
XCTAssertNotNil(date1)
|
XCTAssertTrue(d2.interval(since: d1).isAlmostEqual(to: 1.second, leeway: leeway))
|
||||||
XCTAssertNotNil(date2)
|
}
|
||||||
XCTAssertTrue(date2!.interval(since: date1!).isAlmostEqual(to: 0.seconds, leeway: 0.1.seconds))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static var allTests = [
|
static var allTests = [
|
||||||
("testMake", testMake),
|
("testOfIntervals", testOfIntervals),
|
||||||
|
("testOfDates", testOfDates),
|
||||||
("testDates", testDates),
|
("testDates", testDates),
|
||||||
|
("testDistant", testDistant),
|
||||||
("testNever", testNever),
|
("testNever", testNever),
|
||||||
("testConcat", testConcat),
|
("testConcat", testConcat),
|
||||||
("testMerge", testMerge),
|
("testMerge", testMerge),
|
||||||
("testAt", testAt),
|
|
||||||
("testFirst", testFirst),
|
("testFirst", testFirst),
|
||||||
("testUntil", testUntil),
|
("testUntil", testUntil),
|
||||||
("testNow", testNow),
|
("testNow", testNow),
|
||||||
|
("testAt", testAt),
|
||||||
("testAfterAndRepeating", testAfterAndRepeating),
|
("testAfterAndRepeating", testAfterAndRepeating),
|
||||||
("testEveryPeriod", testEveryPeriod),
|
("testEveryPeriod", testEveryPeriod),
|
||||||
("testEveryWeekday", testEveryWeekday),
|
("testEveryWeekday", testEveryWeekday),
|
||||||
("testEveryMonthday", testEveryMonthday),
|
("testEveryMonthday", testEveryMonthday),
|
||||||
("testPassingEmptyArrays", testPassingEmptyArrays),
|
("testOffset", testOffset)
|
||||||
("testIntervalOffset", testIntervalOffset),
|
|
||||||
("testNegativeIntervalOffset", testNegativeIntervalOffset),
|
|
||||||
("testNilIntervalOffset", testNilIntervalOffset),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,81 +15,90 @@ final class TaskCenterTests: XCTestCase {
|
||||||
func testDefault() {
|
func testDefault() {
|
||||||
let task = makeTask()
|
let task = makeTask()
|
||||||
XCTAssertTrue(center.allTasks.contains(task))
|
XCTAssertTrue(center.allTasks.contains(task))
|
||||||
center.clear()
|
center.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAdd() {
|
func testAdd() {
|
||||||
let c = TaskCenter()
|
|
||||||
|
|
||||||
let task = makeTask()
|
let task = makeTask()
|
||||||
XCTAssertEqual(center.allTasks.count, 1)
|
XCTAssertEqual(center.allTasks.count, 1)
|
||||||
|
|
||||||
|
let c = TaskCenter()
|
||||||
c.add(task)
|
c.add(task)
|
||||||
|
|
||||||
XCTAssertEqual(center.allTasks.count, 0)
|
XCTAssertEqual(center.allTasks.count, 0)
|
||||||
XCTAssertEqual(c.allTasks.count, 1)
|
XCTAssertEqual(c.allTasks.count, 1)
|
||||||
|
|
||||||
c.add(task)
|
center.add(task)
|
||||||
XCTAssertEqual(c.allTasks.count, 1)
|
XCTAssertEqual(center.allTasks.count, 1)
|
||||||
|
XCTAssertEqual(c.allTasks.count, 0)
|
||||||
|
|
||||||
center.clear()
|
center.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRemove() {
|
func testRemove() {
|
||||||
let task = makeTask()
|
let task = makeTask()
|
||||||
|
let tag = UUID().uuidString
|
||||||
|
center.addTag(tag, to: task)
|
||||||
|
|
||||||
center.remove(task)
|
center.remove(task)
|
||||||
|
|
||||||
XCTAssertFalse(center.allTasks.contains(task))
|
XCTAssertFalse(center.allTasks.contains(task))
|
||||||
|
XCTAssertFalse(center.allTags.contains(tag))
|
||||||
|
|
||||||
|
center.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTag() {
|
func testTag() {
|
||||||
let task = makeTask()
|
let task = makeTask()
|
||||||
|
|
||||||
let tag = UUID().uuidString
|
let tag = UUID().uuidString
|
||||||
|
|
||||||
center.addTag(tag, to: task)
|
center.addTag(tag, to: task)
|
||||||
XCTAssertTrue(center.tasksForTag(tag).contains(task))
|
XCTAssertTrue(center.tasks(forTag: tag).contains(task))
|
||||||
XCTAssertTrue(center.tagsForTask(task).contains(tag))
|
XCTAssertTrue(center.tags(forTask: task).contains(tag))
|
||||||
|
|
||||||
center.removeTag(tag, from: task)
|
center.removeTag(tag, from: task)
|
||||||
XCTAssertFalse(center.tasksForTag(tag).contains(task))
|
XCTAssertFalse(center.tasks(forTag: tag).contains(task))
|
||||||
XCTAssertFalse(center.tagsForTask(task).contains(tag))
|
XCTAssertFalse(center.tags(forTask: task).contains(tag))
|
||||||
|
|
||||||
center.clear()
|
center.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAll() {
|
func testAll() {
|
||||||
let task = makeTask()
|
let task = makeTask()
|
||||||
|
let tag1 = UUID().uuidString
|
||||||
|
let tag2 = UUID().uuidString
|
||||||
|
|
||||||
let tag = UUID().uuidString
|
center.addTags([tag1, tag2], to: task)
|
||||||
|
|
||||||
center.addTag(tag, to: task)
|
XCTAssertEqual(center.allTags.sorted(), [tag1, tag2].sorted())
|
||||||
XCTAssertEqual(center.allTags, [tag])
|
|
||||||
XCTAssertEqual(center.allTasks, [task])
|
XCTAssertEqual(center.allTasks, [task])
|
||||||
|
|
||||||
center.clear()
|
center.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testOperation() {
|
func testOperation() {
|
||||||
let task = makeTask()
|
let task = makeTask()
|
||||||
|
|
||||||
let tag = UUID().uuidString
|
let tag = UUID().uuidString
|
||||||
|
|
||||||
center.addTag(tag, to: task)
|
center.addTag(tag, to: task)
|
||||||
|
|
||||||
center.suspendByTag(tag)
|
center.suspend(byTag: tag)
|
||||||
XCTAssertEqual(task.suspensions, 1)
|
XCTAssertEqual(task.suspensionCount, 1)
|
||||||
|
|
||||||
center.resumeByTag(tag)
|
center.resume(byTag: tag)
|
||||||
XCTAssertEqual(task.suspensions, 0)
|
XCTAssertEqual(task.suspensionCount, 0)
|
||||||
|
|
||||||
center.cancelByTag(tag)
|
center.cancel(byTag: tag)
|
||||||
XCTAssertTrue(task.isCancelled)
|
XCTAssertTrue(task.isCancelled)
|
||||||
|
|
||||||
center.clear()
|
center.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testWeak() {
|
func testWeak() {
|
||||||
let block = {
|
let block = {
|
||||||
_ = self.makeTask()
|
let task = self.makeTask()
|
||||||
|
XCTAssertEqual(self.center.allTasks.count, 1)
|
||||||
|
_ = task
|
||||||
}
|
}
|
||||||
block()
|
block()
|
||||||
|
|
||||||
|
|
|
@ -3,45 +3,85 @@ import XCTest
|
||||||
|
|
||||||
final class TaskTests: XCTestCase {
|
final class TaskTests: XCTestCase {
|
||||||
|
|
||||||
|
let leeway = 0.01.second
|
||||||
|
|
||||||
|
func testMetrics() {
|
||||||
|
let e = expectation(description: "testMetrics")
|
||||||
|
let date = Date()
|
||||||
|
|
||||||
|
let task = Plan.after(0.01.second, repeating: 0.01.second).do(queue: .global()) {
|
||||||
|
e.fulfill()
|
||||||
|
}
|
||||||
|
XCTAssertTrue(task.creationDate.interval(since: date).isAlmostEqual(to: 0.second, leeway: leeway))
|
||||||
|
|
||||||
|
waitForExpectations(timeout: 0.1)
|
||||||
|
|
||||||
|
XCTAssertNotNil(task.firstExecutionDate)
|
||||||
|
XCTAssertTrue(task.firstExecutionDate!.interval(since: date).isAlmostEqual(to: 0.01.second, leeway: leeway))
|
||||||
|
|
||||||
|
XCTAssertNotNil(task.lastExecutionDate)
|
||||||
|
XCTAssertTrue(task.lastExecutionDate!.interval(since: date).isAlmostEqual(to: 0.01.second, leeway: leeway))
|
||||||
|
|
||||||
|
XCTAssertEqual(task.executionDates!.count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
func testAfter() {
|
func testAfter() {
|
||||||
let e = expectation(description: "testSchedule")
|
let e = expectation(description: "testSchedule")
|
||||||
let date = Date()
|
let date = Date()
|
||||||
let task = Plan.after(0.1.second).do {
|
let task = Plan.after(0.01.second).do(queue: .global()) {
|
||||||
XCTAssertTrue(Date().timeIntervalSince(date).isAlmostEqual(to: 0.1, leeway: 0.1))
|
XCTAssertTrue(Date().interval(since: date).isAlmostEqual(to: 0.01.second, leeway: self.leeway))
|
||||||
e.fulfill()
|
e.fulfill()
|
||||||
}
|
}
|
||||||
waitForExpectations(timeout: 0.5)
|
waitForExpectations(timeout: 0.1)
|
||||||
task.cancel()
|
|
||||||
|
_ = task
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRepeat() {
|
func testRepeat() {
|
||||||
let e = expectation(description: "testRepeat")
|
let e = expectation(description: "testRepeat")
|
||||||
var t = 0
|
var count = 0
|
||||||
let task = Plan.every(0.1.second).first(3).do {
|
let task = Plan.every(0.01.second).first(3).do(queue: .global()) {
|
||||||
t += 1
|
count += 1
|
||||||
if t == 3 { e.fulfill() }
|
if count == 3 { e.fulfill() }
|
||||||
}
|
}
|
||||||
waitForExpectations(timeout: 1)
|
waitForExpectations(timeout: 0.1)
|
||||||
task.cancel()
|
|
||||||
|
_ = task
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTaskCenter() {
|
||||||
|
let task = Plan.never.do { }
|
||||||
|
XCTAssertTrue(task.taskCenter === TaskCenter.default)
|
||||||
|
|
||||||
|
task.removeFromTaskCenter(TaskCenter())
|
||||||
|
XCTAssertNotNil(task.taskCenter)
|
||||||
|
|
||||||
|
task.removeFromTaskCenter(task.taskCenter!)
|
||||||
|
XCTAssertNil(task.taskCenter)
|
||||||
|
|
||||||
|
let center = TaskCenter()
|
||||||
|
task.addToTaskCenter(center)
|
||||||
|
XCTAssertTrue(task.taskCenter === center)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDispatchQueue() {
|
func testDispatchQueue() {
|
||||||
let e = expectation(description: "testQueue")
|
let e = expectation(description: "testQueue")
|
||||||
let queue = DispatchQueue(label: "testQueue")
|
let q = DispatchQueue(label: UUID().uuidString)
|
||||||
|
|
||||||
let task = Plan.after(0.1.second).do(queue: queue) {
|
let task = Plan.after(0.01.second).do(queue: q) {
|
||||||
XCTAssertTrue(DispatchQueue.is(queue))
|
XCTAssertTrue(DispatchQueue.is(q))
|
||||||
e.fulfill()
|
e.fulfill()
|
||||||
}
|
}
|
||||||
waitForExpectations(timeout: 0.5)
|
waitForExpectations(timeout: 0.1)
|
||||||
task.cancel()
|
|
||||||
|
_ = task
|
||||||
}
|
}
|
||||||
|
|
||||||
func testThread() {
|
func testThread() {
|
||||||
let e = expectation(description: "testThread")
|
let e = expectation(description: "testThread")
|
||||||
DispatchQueue.global().async {
|
DispatchQueue.global().async {
|
||||||
let thread = Thread.current
|
let thread = Thread.current
|
||||||
let task = Plan.after(0.1.second).do { task in
|
let task = Plan.after(0.01.second).do { task in
|
||||||
XCTAssertTrue(thread === Thread.current)
|
XCTAssertTrue(thread === Thread.current)
|
||||||
e.fulfill()
|
e.fulfill()
|
||||||
task.cancel()
|
task.cancel()
|
||||||
|
@ -49,18 +89,52 @@ final class TaskTests: XCTestCase {
|
||||||
_ = task
|
_ = task
|
||||||
RunLoop.current.run()
|
RunLoop.current.run()
|
||||||
}
|
}
|
||||||
waitForExpectations(timeout: 0.5)
|
waitForExpectations(timeout: 0.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExecuteNow() {
|
||||||
|
let e = expectation(description: "testExecuteNow")
|
||||||
|
let task = Plan.never.do {
|
||||||
|
e.fulfill()
|
||||||
|
}
|
||||||
|
task.executeNow()
|
||||||
|
waitForExpectations(timeout: 0.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testReschedule() {
|
||||||
|
let e = expectation(description: "testReschedule")
|
||||||
|
var i = 0
|
||||||
|
let task = Plan.after(0.01.second).do(queue: .global()) { (task) in
|
||||||
|
i += 1
|
||||||
|
if task.executionCount == 3 && task.estimatedNextExecutionDate == nil {
|
||||||
|
e.fulfill()
|
||||||
|
}
|
||||||
|
if task.executionCount > 3 {
|
||||||
|
XCTFail("should never come here")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DispatchQueue.global().async(after: 0.02.second) {
|
||||||
|
task.reschedule(Plan.every(0.01.second).first(2))
|
||||||
|
}
|
||||||
|
waitForExpectations(timeout: 0.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSuspendResume() {
|
func testSuspendResume() {
|
||||||
let task1 = Plan.distantFuture.do { }
|
let task = Plan.never.do { }
|
||||||
XCTAssertEqual(task1.suspensions, 0)
|
XCTAssertEqual(task.suspensionCount, 0)
|
||||||
task1.suspend()
|
task.suspend()
|
||||||
task1.suspend()
|
task.suspend()
|
||||||
task1.suspend()
|
task.suspend()
|
||||||
XCTAssertEqual(task1.suspensions, 3)
|
XCTAssertEqual(task.suspensionCount, 3)
|
||||||
task1.resume()
|
task.resume()
|
||||||
XCTAssertEqual(task1.suspensions, 2)
|
XCTAssertEqual(task.suspensionCount, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCancel() {
|
||||||
|
let task = Plan.never.do { }
|
||||||
|
XCTAssertFalse(task.isCancelled)
|
||||||
|
task.cancel()
|
||||||
|
XCTAssertTrue(task.isCancelled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAddAndRemoveActions() {
|
func testAddAndRemoveActions() {
|
||||||
|
@ -71,81 +145,28 @@ final class TaskTests: XCTestCase {
|
||||||
XCTAssertTrue(Date().timeIntervalSince(date).isAlmostEqual(to: 0.1, leeway: 0.1))
|
XCTAssertTrue(Date().timeIntervalSince(date).isAlmostEqual(to: 0.1, leeway: 0.1))
|
||||||
e.fulfill()
|
e.fulfill()
|
||||||
}
|
}
|
||||||
XCTAssertEqual(task.countOfActions, 2)
|
XCTAssertEqual(task.actionCount, 2)
|
||||||
waitForExpectations(timeout: 0.5)
|
waitForExpectations(timeout: 0.5)
|
||||||
|
|
||||||
task.removeAction(byKey: key)
|
task.removeAction(byKey: key)
|
||||||
XCTAssertEqual(task.countOfActions, 1)
|
XCTAssertEqual(task.actionCount, 1)
|
||||||
|
|
||||||
task.cancel()
|
task.cancel()
|
||||||
|
|
||||||
task.removeAllActions()
|
task.removeAllActions()
|
||||||
XCTAssertEqual(task.countOfActions, 0)
|
XCTAssertEqual(task.actionCount, 0)
|
||||||
}
|
|
||||||
|
|
||||||
func testReschedule() {
|
|
||||||
let e = expectation(description: "testReschedule")
|
|
||||||
var i = 0
|
|
||||||
let task = Plan.after(0.1.second).do { (task) in
|
|
||||||
i += 1
|
|
||||||
if task.countOfExecutions == 6 && task.timeline.estimatedNextExecution == nil {
|
|
||||||
e.fulfill()
|
|
||||||
}
|
|
||||||
if task.countOfExecutions > 6 {
|
|
||||||
XCTFail("should never come here")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DispatchQueue.global().async(after: 0.5.second) {
|
|
||||||
task.reschedule(Plan.every(0.1.second).first(5))
|
|
||||||
}
|
|
||||||
waitForExpectations(timeout: 2)
|
|
||||||
task.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testHost() {
|
|
||||||
let e = expectation(description: "testHost")
|
|
||||||
let fn = {
|
|
||||||
let obj = NSObject()
|
|
||||||
let task = Plan.after(0.1.second).do(queue: .main, onElapse: {
|
|
||||||
XCTFail()
|
|
||||||
})
|
|
||||||
task.host(on: obj)
|
|
||||||
}
|
|
||||||
fn()
|
|
||||||
DispatchQueue.main.async(after: 0.2.seconds) {
|
|
||||||
e.fulfill()
|
|
||||||
}
|
|
||||||
waitForExpectations(timeout: 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testLifetime() {
|
|
||||||
let e = expectation(description: "testLifetime")
|
|
||||||
let task = Plan.after(1.hour).do { }
|
|
||||||
task.setLifetime(1.second)
|
|
||||||
XCTAssertEqual(task.lifetime, 1.second)
|
|
||||||
|
|
||||||
DispatchQueue.global().async(after: 0.5.second) {
|
|
||||||
XCTAssertTrue(task.restOfLifetime.isAlmostEqual(to: 0.5.second, leeway: 0.1.second))
|
|
||||||
task.subtractLifetime(-0.5.second)
|
|
||||||
}
|
|
||||||
DispatchQueue.global().async(after: 1.second) {
|
|
||||||
XCTAssertFalse(task.isCancelled)
|
|
||||||
}
|
|
||||||
DispatchQueue.global().async(after: 2.second) {
|
|
||||||
XCTAssertTrue(task.isCancelled)
|
|
||||||
e.fulfill()
|
|
||||||
}
|
|
||||||
waitForExpectations(timeout: 3)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static var allTests = [
|
static var allTests = [
|
||||||
("testAfter", testAfter),
|
("testAfter", testAfter),
|
||||||
("testRepeat", testRepeat),
|
("testRepeat", testRepeat),
|
||||||
|
("testTaskCenter", testTaskCenter),
|
||||||
("testDispatchQueue", testDispatchQueue),
|
("testDispatchQueue", testDispatchQueue),
|
||||||
("testThread", testThread),
|
("testThread", testThread),
|
||||||
("testAddAndRemoveActions", testAddAndRemoveActions),
|
("testExecuteNow", testExecuteNow),
|
||||||
("testReschedule", testReschedule),
|
("testReschedule", testReschedule),
|
||||||
("testHost", testHost),
|
("testSuspendResume", testSuspendResume),
|
||||||
("testLifetime", testLifetime)
|
("testCancel", testCancel),
|
||||||
|
("testAddAndRemoveActions", testAddAndRemoveActions)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import XCTest
|
||||||
|
@testable import Schedule
|
||||||
|
|
||||||
|
final class TimeTests: XCTestCase {
|
||||||
|
|
||||||
|
func testTime() {
|
||||||
|
let t1 = Time("11:12:13.456")
|
||||||
|
XCTAssertNotNil(t1)
|
||||||
|
XCTAssertEqual(t1?.hour, 11)
|
||||||
|
XCTAssertEqual(t1?.minute, 12)
|
||||||
|
XCTAssertEqual(t1?.second, 13)
|
||||||
|
if let i = t1?.nanosecond.nanoseconds {
|
||||||
|
XCTAssertTrue(i.isAlmostEqual(to: 0.456.second, 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)
|
||||||
|
|
||||||
|
let t4 = Time("schedule")
|
||||||
|
XCTAssertNil(t4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIntervalSinceStartOfDay() {
|
||||||
|
XCTAssertEqual(Time(hour: 1)!.intervalSinceStartOfDay, 1.hour)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAsDateComponents() {
|
||||||
|
let time = Time(hour: 11, minute: 12, second: 13, nanosecond: 456)
|
||||||
|
let components = time?.asDateComponents()
|
||||||
|
XCTAssertEqual(components?.hour, 11)
|
||||||
|
XCTAssertEqual(components?.minute, 12)
|
||||||
|
XCTAssertEqual(components?.second, 13)
|
||||||
|
XCTAssertEqual(components?.nanosecond, 456)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDescription() {
|
||||||
|
let time = Time("11:12:13.456")
|
||||||
|
XCTAssertEqual(time!.debugDescription, "Time: 11:12:13.456")
|
||||||
|
}
|
||||||
|
|
||||||
|
static var allTests = [
|
||||||
|
("testTime", testTime),
|
||||||
|
("testIntervalSinceStartOfDay", testIntervalSinceStartOfDay),
|
||||||
|
("testAsDateComponents", testAsDateComponents),
|
||||||
|
("testDescription", testDescription)
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import XCTest
|
||||||
|
@testable import Schedule
|
||||||
|
|
||||||
|
final class WeekdayTests: XCTestCase {
|
||||||
|
|
||||||
|
func testIs() {
|
||||||
|
let d = Date(year: 2019, month: 1, day: 1)
|
||||||
|
XCTAssertTrue(d.is(.tuesday, in: TimeZone.shanghai))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAsDateComponents() {
|
||||||
|
XCTAssertEqual(Weekday.monday.asDateComponents().weekday!, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDescription() {
|
||||||
|
let wd = Weekday.tuesday
|
||||||
|
XCTAssertEqual(wd.debugDescription, "Weekday: Tuesday")
|
||||||
|
}
|
||||||
|
|
||||||
|
static var allTests = [
|
||||||
|
("testIs", testIs),
|
||||||
|
("testAsDateComponents", testAsDateComponents),
|
||||||
|
("testDescription", testDescription)
|
||||||
|
]
|
||||||
|
}
|
|
@ -3,14 +3,17 @@ import XCTest
|
||||||
#if os(Linux)
|
#if os(Linux)
|
||||||
public func allTests() -> [XCTestCaseEntry] {
|
public func allTests() -> [XCTestCaseEntry] {
|
||||||
return [
|
return [
|
||||||
testCase(DateTimeTests.allTests),
|
testCase(AtomicTests.allTests),
|
||||||
|
testCase(BagTests.allTests),
|
||||||
|
testCase(ExtensionsTests.allTests),
|
||||||
|
testCase(IntervalTests.allTests),
|
||||||
|
testCase(MonthdayTests.allTests),
|
||||||
|
testCase(PeriodTests.allTests),
|
||||||
testCase(PlanTests.allTests),
|
testCase(PlanTests.allTests),
|
||||||
testCase(TaskCenterTests.allTests),
|
testCase(TaskCenterTests.allTests),
|
||||||
testCase(TaskTests.allTests),
|
testCase(TaskTests.allTests),
|
||||||
testCase(AtomicTests.allTests),
|
testCase(TimeTests.allTests),
|
||||||
testCase(CabinetTests.allTests),
|
testCase(WeekdayTests.allTests)
|
||||||
testCase(CalendarTests.allTests),
|
|
||||||
testCase(ExtensionsTests.allTests)
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
BIN
assets/demo.png
BIN
assets/demo.png
Binary file not shown.
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 182 KiB |
Loading…
Reference in New Issue