Supplement docs
This commit is contained in:
parent
28fc91ea06
commit
ce4d5d45b8
|
@ -1,4 +1,67 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
## Build generated
|
||||
build/
|
||||
DerivedData/
|
||||
|
||||
## Various settings
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata/
|
||||
|
||||
## Other
|
||||
*.moved-aside
|
||||
*.xccheckout
|
||||
*.xcscmblueprint
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||
# Packages/
|
||||
# Package.pins
|
||||
# Package.resolved
|
||||
.build/
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
#
|
||||
# Pods/
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||
# *.xcworkspace
|
||||
|
||||
# Carthage
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
# Carthage/Checkouts
|
||||
|
||||
Carthage/Build
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||
# screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots/**/*.png
|
||||
fastlane/test_output
|
|
@ -0,0 +1 @@
|
|||
4.2
|
80
README.md
80
README.md
|
@ -1,22 +1,29 @@
|
|||
# Schedule
|
||||
|
||||
Swift job scheduler.
|
||||
⏰ A interval-based and date-based job scheduler for swift.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- 📆 Date-based scheduling
|
||||
- ⏳ Interval-based scheduling
|
||||
- 📝 Mixture rules scheduling
|
||||
- 🚦 Suspend, resume, cancel
|
||||
- 🏷 Tag related management
|
||||
- 🍻 No need to concern about runloop
|
||||
- 👻 No need to concern about circular reference
|
||||
- 🍭 **Sweet apis**
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### Getting Start
|
||||
|
||||
Scheduling a job can not be simplier.
|
||||
|
||||
```swift
|
||||
func job() { }
|
||||
Schedule.every(1.minute).do(job)
|
||||
func heartBeat() { }
|
||||
Schedule.every(0.5.seconds).do(heartBeat)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Interval-based Scheduling
|
||||
|
||||
```swift
|
||||
|
@ -37,7 +44,7 @@ Schedule.from([1.second, 2.minutes, 3.hours]).do { }
|
|||
|
||||
|
||||
|
||||
## Date-based Scheduling
|
||||
### Date-based Scheduling
|
||||
|
||||
```swift
|
||||
import Schedule
|
||||
|
@ -62,7 +69,7 @@ import Schedule
|
|||
|
||||
/// concat
|
||||
let s0 = Schedule.at(birthdate)
|
||||
let s1 = Schedule.every(.year(1))
|
||||
let s1 = Schedule.every(1.year)
|
||||
let birthdaySchedule = s0.concat.s1
|
||||
birthdaySchedule.do {
|
||||
print("Happy birthday")
|
||||
|
@ -73,32 +80,65 @@ let s3 = Schedule.every(.january(1)).at(8, 30)
|
|||
let s4 = Schedule.every(.october(1)).at(8, 30)
|
||||
let holiday = s3.merge(s3)
|
||||
holidaySchedule.do {
|
||||
print("Holiday~")
|
||||
print("Happy holiday")
|
||||
}
|
||||
```
|
||||
|
||||
/// count
|
||||
let s5 = Schedule.after(5.seconds).concat(Schedule.every(1.day))
|
||||
let s6 = s5.count(10)
|
||||
|
||||
/// until
|
||||
let s7 = Schedule.every(.monday).at(11, 12)
|
||||
let s8 = s7.until(date)
|
||||
```
|
||||
|
||||
|
||||
### Job management
|
||||
|
||||
Normally, you don't need to care about the reference management of job. All jobs will be held by a inner instance `jobCenter`, so they won't be released.
|
||||
In genera, you don't need to care about the reference management of job. All jobs will be held by a inner shared instance `jobCenter`, so they won't be released, unless you do that yourself.
|
||||
|
||||
If you want everything in your control:
|
||||
|
||||
```swift
|
||||
let job = Schedule.every(1.day).do { }
|
||||
|
||||
job.suspend() // will suspend job, it won't change job's schedule
|
||||
job.resume() // will resume the suspended job, it won't change job's schedule
|
||||
job.cancel() // will cancel job, then job will be released after variable `job` is gone
|
||||
job.suspend()
|
||||
job.resume()
|
||||
job.cancel() // will release this job
|
||||
```
|
||||
|
||||
You also can use `tag` to organize jobs, and use `queue` to define which queue the job should be dispatched to:
|
||||
|
||||
```swift
|
||||
Schedule.every(1.day).do(queue: myJobQueue, tag: "remind me") { }
|
||||
let s = Schedule.every(1.day)
|
||||
s.do(queue: myJobQueue, tag: "log") { }
|
||||
s.do(queue: myJobQueue, tag: "log") { }
|
||||
|
||||
Job.suspend("remind me") // will suspend all jobs with this tag
|
||||
Job.resume("remind me") // will resume all jobs with this tag
|
||||
Job.cancel("remind me") // will cancel all jobs with this tag
|
||||
Job.suspend("log")
|
||||
Job.resume("log")
|
||||
Job.cancel("log")
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
Schedul supports all popular dependency managers.
|
||||
|
||||
### Cocoapods
|
||||
|
||||
```ruby
|
||||
pod 'Schedule'
|
||||
```
|
||||
|
||||
### Carthage
|
||||
|
||||
```swift
|
||||
github "jianstm/Schedule"
|
||||
```
|
||||
|
||||
### Swift Package Manager
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/jianstm/Schedule", from: "0.0.2")
|
||||
]
|
||||
```
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = "Schedule"
|
||||
s.version = "0.0.2"
|
||||
s.version = "0.0.3"
|
||||
s.summary = "Swift Job Schedule."
|
||||
s.homepage = "https://github.com/jianstm/Schedule"
|
||||
s.license = { :type => "MIT", :file => "LICENSE" }
|
||||
|
@ -9,10 +9,10 @@ Pod::Spec.new do |s|
|
|||
:tag => "#{s.version}" }
|
||||
s.source_files = "Sources/Schedule/*.swift"
|
||||
s.requires_arc = true
|
||||
s.swift_version = "4.0"
|
||||
s.swift_version = "4.2"
|
||||
|
||||
s.ios.deployment_target = "8.0"
|
||||
s.osx.deployment_target = "10.9"
|
||||
s.osx.deployment_target = "10.10"
|
||||
s.tvos.deployment_target = "9.0"
|
||||
s.watchos.deployment_target = "2.0"
|
||||
end
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,478 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
"Schedule::SchedulePackageTests::ProductTarget" /* SchedulePackageTests */ = {
|
||||
isa = PBXAggregateTarget;
|
||||
buildConfigurationList = OBJ_39 /* Build configuration list for PBXAggregateTarget "SchedulePackageTests" */;
|
||||
buildPhases = (
|
||||
);
|
||||
dependencies = (
|
||||
OBJ_42 /* PBXTargetDependency */,
|
||||
);
|
||||
name = SchedulePackageTests;
|
||||
productName = SchedulePackageTests;
|
||||
};
|
||||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
OBJ_27 /* DateTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* DateTime.swift */; };
|
||||
OBJ_28 /* Job.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* Job.swift */; };
|
||||
OBJ_29 /* Schedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* Schedule.swift */; };
|
||||
OBJ_30 /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* Util.swift */; };
|
||||
OBJ_37 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
|
||||
OBJ_48 /* DateTimeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* DateTimeTests.swift */; };
|
||||
OBJ_49 /* ScheduleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* ScheduleTests.swift */; };
|
||||
OBJ_50 /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* Util.swift */; };
|
||||
OBJ_51 /* XCTestManifests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* XCTestManifests.swift */; };
|
||||
OBJ_53 /* Schedule.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "Schedule::Schedule::Product" /* Schedule.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
6266004220EF028200D5E606 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = OBJ_1 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = "Schedule::Schedule";
|
||||
remoteInfo = Schedule;
|
||||
};
|
||||
6266004320EF028200D5E606 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = OBJ_1 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = "Schedule::ScheduleTests";
|
||||
remoteInfo = ScheduleTests;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
OBJ_10 /* Job.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Job.swift; sourceTree = "<group>"; };
|
||||
OBJ_11 /* Schedule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Schedule.swift; sourceTree = "<group>"; };
|
||||
OBJ_12 /* Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = "<group>"; };
|
||||
OBJ_15 /* DateTimeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_16 /* ScheduleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_17 /* Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = "<group>"; };
|
||||
OBJ_18 /* XCTestManifests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestManifests.swift; sourceTree = "<group>"; };
|
||||
OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = "<group>"; };
|
||||
OBJ_9 /* DateTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTime.swift; sourceTree = "<group>"; };
|
||||
"Schedule::Schedule::Product" /* Schedule.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Schedule.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
"Schedule::ScheduleTests::Product" /* ScheduleTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = ScheduleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
OBJ_31 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
OBJ_52 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
OBJ_53 /* Schedule.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
OBJ_13 /* Tests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_14 /* ScheduleTests */,
|
||||
);
|
||||
name = Tests;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
OBJ_14 /* ScheduleTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_15 /* DateTimeTests.swift */,
|
||||
OBJ_16 /* ScheduleTests.swift */,
|
||||
OBJ_17 /* Util.swift */,
|
||||
OBJ_18 /* XCTestManifests.swift */,
|
||||
);
|
||||
name = ScheduleTests;
|
||||
path = Tests/ScheduleTests;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
OBJ_19 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
"Schedule::Schedule::Product" /* Schedule.framework */,
|
||||
"Schedule::ScheduleTests::Product" /* ScheduleTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
OBJ_5 /* */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_6 /* Package.swift */,
|
||||
OBJ_7 /* Sources */,
|
||||
OBJ_13 /* Tests */,
|
||||
OBJ_19 /* Products */,
|
||||
);
|
||||
name = "";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
OBJ_7 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_8 /* Schedule */,
|
||||
);
|
||||
name = Sources;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
OBJ_8 /* Schedule */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_9 /* DateTime.swift */,
|
||||
OBJ_10 /* Job.swift */,
|
||||
OBJ_11 /* Schedule.swift */,
|
||||
OBJ_12 /* Util.swift */,
|
||||
);
|
||||
name = Schedule;
|
||||
path = Sources/Schedule;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
"Schedule::Schedule" /* Schedule */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = OBJ_23 /* Build configuration list for PBXNativeTarget "Schedule" */;
|
||||
buildPhases = (
|
||||
OBJ_26 /* Sources */,
|
||||
OBJ_31 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Schedule;
|
||||
productName = Schedule;
|
||||
productReference = "Schedule::Schedule::Product" /* Schedule.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
"Schedule::ScheduleTests" /* ScheduleTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = OBJ_44 /* Build configuration list for PBXNativeTarget "ScheduleTests" */;
|
||||
buildPhases = (
|
||||
OBJ_47 /* Sources */,
|
||||
OBJ_52 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
OBJ_54 /* PBXTargetDependency */,
|
||||
);
|
||||
name = ScheduleTests;
|
||||
productName = ScheduleTests;
|
||||
productReference = "Schedule::ScheduleTests::Product" /* ScheduleTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
"Schedule::SwiftPMPackageDescription" /* SchedulePackageDescription */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = OBJ_33 /* Build configuration list for PBXNativeTarget "SchedulePackageDescription" */;
|
||||
buildPhases = (
|
||||
OBJ_36 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = SchedulePackageDescription;
|
||||
productName = SchedulePackageDescription;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
OBJ_1 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 9999;
|
||||
};
|
||||
buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "Schedule" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = OBJ_5 /* */;
|
||||
productRefGroup = OBJ_19 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
"Schedule::Schedule" /* Schedule */,
|
||||
"Schedule::SwiftPMPackageDescription" /* SchedulePackageDescription */,
|
||||
"Schedule::SchedulePackageTests::ProductTarget" /* SchedulePackageTests */,
|
||||
"Schedule::ScheduleTests" /* ScheduleTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
OBJ_26 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
OBJ_27 /* DateTime.swift in Sources */,
|
||||
OBJ_28 /* Job.swift in Sources */,
|
||||
OBJ_29 /* Schedule.swift in Sources */,
|
||||
OBJ_30 /* Util.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
OBJ_36 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
OBJ_37 /* Package.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
OBJ_47 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
OBJ_48 /* DateTimeTests.swift in Sources */,
|
||||
OBJ_49 /* ScheduleTests.swift in Sources */,
|
||||
OBJ_50 /* Util.swift in Sources */,
|
||||
OBJ_51 /* XCTestManifests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
OBJ_42 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = "Schedule::ScheduleTests" /* ScheduleTests */;
|
||||
targetProxy = 6266004320EF028200D5E606 /* PBXContainerItemProxy */;
|
||||
};
|
||||
OBJ_54 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = "Schedule::Schedule" /* Schedule */;
|
||||
targetProxy = 6266004220EF028200D5E606 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
OBJ_24 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ENABLE_TESTABILITY = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = Schedule.xcodeproj/Schedule_Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = Schedule;
|
||||
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGET_NAME = Schedule;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_25 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ENABLE_TESTABILITY = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||
INFOPLIST_FILE = Schedule.xcodeproj/Schedule_Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
|
||||
OTHER_CFLAGS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "$(inherited)";
|
||||
OTHER_SWIFT_FLAGS = "$(inherited)";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = Schedule;
|
||||
PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGET_NAME = Schedule;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
OBJ_3 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_NS_ASSERTIONS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_SWIFT_FLAGS = "-DXcode";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
USE_HEADERMAP = NO;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_34 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
LD = /usr/bin/true;
|
||||
OTHER_SWIFT_FLAGS = "-swift-version 4 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk";
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_35 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
LD = /usr/bin/true;
|
||||
OTHER_SWIFT_FLAGS = "-swift-version 4 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk";
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
OBJ_4 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
OTHER_SWIFT_FLAGS = "-DXcode";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = macosx;
|
||||
SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
USE_HEADERMAP = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
OBJ_40 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_41 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
OBJ_45 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
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_VERSION = 4.0;
|
||||
TARGET_NAME = ScheduleTests;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
OBJ_46 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
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_VERSION = 4.0;
|
||||
TARGET_NAME = ScheduleTests;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
OBJ_2 /* Build configuration list for PBXProject "Schedule" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_3 /* Debug */,
|
||||
OBJ_4 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
OBJ_23 /* Build configuration list for PBXNativeTarget "Schedule" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_24 /* Debug */,
|
||||
OBJ_25 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
OBJ_33 /* Build configuration list for PBXNativeTarget "SchedulePackageDescription" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_34 /* Debug */,
|
||||
OBJ_35 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
OBJ_39 /* Build configuration list for PBXAggregateTarget "SchedulePackageTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_40 /* Debug */,
|
||||
OBJ_41 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
OBJ_44 /* Build configuration list for PBXNativeTarget "ScheduleTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
OBJ_45 /* Debug */,
|
||||
OBJ_46 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = OBJ_1 /* Project object */;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,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">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,81 @@
|
|||
<?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::Schedule"
|
||||
BuildableName = "Schedule.framework"
|
||||
BlueprintName = "Schedule"
|
||||
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">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "Schedule::Schedule"
|
||||
BuildableName = "Schedule.framework"
|
||||
BlueprintName = "Schedule"
|
||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<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>
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>Schedule-Package.xcscheme</key>
|
||||
<dict></dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict></dict>
|
||||
</dict>
|
||||
</plist>
|
|
@ -7,109 +7,191 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// `Interval` represents a length of time, it's contextless.
|
||||
/// `Interval` represents a duration of time.
|
||||
public struct Interval {
|
||||
|
||||
/// The length of this interval, measured in nanoseconds.
|
||||
public let nanoseconds: Double
|
||||
|
||||
/// Creates an interval from the given number of nanoseconds.
|
||||
public init(nanoseconds: Double) {
|
||||
self.nanoseconds = nanoseconds
|
||||
}
|
||||
|
||||
public init(seconds: Double) {
|
||||
self.nanoseconds = seconds * pow(10, 9)
|
||||
}
|
||||
|
||||
public var seconds: Double {
|
||||
return nanoseconds / pow(10, 9)
|
||||
}
|
||||
|
||||
/// A boolean value indicating whether this interval is negative.
|
||||
///
|
||||
/// A interval can be negative.
|
||||
///
|
||||
/// - The interval between 6:00 and 7:00 is `1.hour`,
|
||||
/// but the interval between 7:00 and 6:00 is `-1.hour`.
|
||||
/// In this case, `-1.hour` means **one hour ago**.
|
||||
///
|
||||
/// - The interval comparing `3.hour` and `1.hour` is `2.hour`,
|
||||
/// but the interval comparing `1.hour` and `3.hour` is `-2.hour`.
|
||||
/// In this case, `-2.hour` means **two hours shorter**
|
||||
public var isNegative: Bool {
|
||||
return nanoseconds.isLess(than: 0)
|
||||
}
|
||||
|
||||
public var dispatchInterval: DispatchTimeInterval {
|
||||
if nanoseconds > Double(Int.max) { return .nanoseconds(.max) }
|
||||
if nanoseconds < Double(Int.min) { return .nanoseconds(.min) }
|
||||
return .nanoseconds(Int(nanoseconds))
|
||||
/// The magnitude of this interval.
|
||||
///
|
||||
/// It's the absolute value of the length of this interval,
|
||||
/// measured in nanoseconds, but disregarding its sign.
|
||||
public var magnitude: Double {
|
||||
return nanoseconds.magnitude
|
||||
}
|
||||
|
||||
internal var ns: Int {
|
||||
if nanoseconds > Double(Int.max) { return .max }
|
||||
if nanoseconds < Double(Int.min) { return .min }
|
||||
return Int(nanoseconds)
|
||||
}
|
||||
|
||||
/// Returns a dispatchTimeInterval created from this interval.
|
||||
///
|
||||
/// The returned value will be clamped to the `DispatchTimeInterval`'s
|
||||
/// usable range [`.nanoseconds(.min)....nanoseconds(.max)`].
|
||||
public func asDispatchTimeInterval() -> DispatchTimeInterval {
|
||||
return .nanoseconds(ns)
|
||||
}
|
||||
|
||||
/// Returns a boolean value indicating whether this interval is longer than the given value.
|
||||
public func isLonger(than other: Interval) -> Bool {
|
||||
return magnitude > other.magnitude
|
||||
}
|
||||
|
||||
/// Returns a boolean value indicating whether this interval is shorter than the given value.
|
||||
public func isShorter(than other: Interval) -> Bool {
|
||||
return magnitude < other.magnitude
|
||||
}
|
||||
|
||||
/// Returns the longest interval of the given values.
|
||||
public static func longest(_ intervals: Interval...) -> Interval {
|
||||
return intervals.sorted(by: { $0.magnitude > $1.magnitude })[0]
|
||||
}
|
||||
|
||||
/// Returns the shortest interval of the given values.
|
||||
public static func shortest(_ intervals: Interval...) -> Interval {
|
||||
return intervals.sorted(by: { $0.magnitude < $1.magnitude })[0]
|
||||
}
|
||||
|
||||
/// Returns a new interval by multipling the left interval by the right number.
|
||||
///
|
||||
/// 1.hour * 2 == 2.hours
|
||||
public static func *(lhs: Interval, rhs: Double) -> Interval {
|
||||
return Interval(nanoseconds: lhs.nanoseconds * rhs)
|
||||
}
|
||||
|
||||
/// Returns a new interval by adding the right interval to the left interval.
|
||||
///
|
||||
/// 1.hour + 1.hour == 2.hours
|
||||
public static func +(lhs: Interval, rhs: Interval) -> Interval {
|
||||
return Interval(nanoseconds: lhs.nanoseconds + rhs.nanoseconds)
|
||||
}
|
||||
|
||||
/// Returns a new instarval by subtracting the right interval from the left interval.
|
||||
///
|
||||
/// 2.hours - 1.hour == 1.hour
|
||||
public static func -(lhs: Interval, rhs: Interval) -> Interval {
|
||||
return Interval(nanoseconds: lhs.nanoseconds - rhs.nanoseconds)
|
||||
}
|
||||
}
|
||||
|
||||
extension Interval {
|
||||
|
||||
/// Creates an interval from the given number of seconds.
|
||||
public init(seconds: Double) {
|
||||
self.nanoseconds = seconds * pow(10, 9)
|
||||
}
|
||||
|
||||
/// The length of this interval, measured in seconds.
|
||||
public var seconds: Double {
|
||||
return nanoseconds / pow(10, 9)
|
||||
}
|
||||
|
||||
/// The length of this interval, measured in minutes.
|
||||
public var minutes: Double {
|
||||
return seconds / 60
|
||||
}
|
||||
|
||||
/// The length of this interval, measured in hours.
|
||||
public var hours: Double {
|
||||
return minutes / 60
|
||||
}
|
||||
|
||||
/// The length of this interval, measured in days.
|
||||
public var days: Double {
|
||||
return hours / 24
|
||||
}
|
||||
|
||||
/// The length of this interval, measured in weeks.
|
||||
public var weeks: Double {
|
||||
return days / 7
|
||||
}
|
||||
}
|
||||
|
||||
extension Interval: Hashable {
|
||||
|
||||
/// The hashValue of this interval.
|
||||
public var hashValue: Int {
|
||||
return nanoseconds.hashValue
|
||||
}
|
||||
|
||||
/// Returns a boolean value indicating whether the interval is equal to another interval.
|
||||
public static func ==(lhs: Interval, rhs: Interval) -> Bool {
|
||||
return lhs.hashValue == rhs.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
extension Interval: Comparable {
|
||||
|
||||
public static func <(lhs: Interval, rhs: Interval) -> Bool {
|
||||
return lhs.nanoseconds.magnitude < rhs.nanoseconds.magnitude
|
||||
return lhs.nanoseconds == rhs.nanoseconds
|
||||
}
|
||||
}
|
||||
|
||||
extension Date {
|
||||
|
||||
/// The interval between this date and now.
|
||||
///
|
||||
/// If the date is earlier than now, the interval is negative.
|
||||
public var intervalSinceNow: Interval {
|
||||
return timeIntervalSinceNow.seconds
|
||||
}
|
||||
|
||||
/// Returns the interval between this date and the given date.
|
||||
///
|
||||
/// If the date is earlier than the given date, the interval is negative.
|
||||
public func interval(since date: Date) -> Interval {
|
||||
return timeIntervalSince(date).seconds
|
||||
}
|
||||
|
||||
/// Return a new date by adding an interval to the date.
|
||||
public static func +(lhs: Date, rhs: Interval) -> Date {
|
||||
return lhs.addingTimeInterval(rhs.seconds)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public static func +=(lhs: inout Date, rhs: Interval) -> Date {
|
||||
/// Add an interval to the date.
|
||||
public static func +=(lhs: inout Date, rhs: Interval) {
|
||||
lhs = lhs + rhs
|
||||
return lhs
|
||||
}
|
||||
}
|
||||
|
||||
/// `IntervalConvertible` provides a set of intuitive apis for creating interval.
|
||||
public protocol IntervalConvertible {
|
||||
|
||||
func asNanoseconds() -> Double
|
||||
var nanoseconds: Interval { get }
|
||||
}
|
||||
|
||||
extension Int: IntervalConvertible {
|
||||
|
||||
public func asNanoseconds() -> Double {
|
||||
return Double(self)
|
||||
public var nanoseconds: Interval {
|
||||
return Interval(nanoseconds: Double(self))
|
||||
}
|
||||
}
|
||||
|
||||
extension Double: IntervalConvertible {
|
||||
|
||||
public func asNanoseconds() -> Double {
|
||||
return self
|
||||
public var nanoseconds: Interval {
|
||||
return Interval(nanoseconds: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension IntervalConvertible {
|
||||
|
||||
public var nanoseconds: Interval {
|
||||
return Interval(nanoseconds: asNanoseconds())
|
||||
}
|
||||
|
||||
public var nanosecond: Interval {
|
||||
return nanoseconds
|
||||
}
|
||||
|
@ -171,19 +253,27 @@ extension IntervalConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
public enum Weekday: Int {
|
||||
case sunday = 1, monday, tuesday, wednesday, thursday, friday, saturday
|
||||
}
|
||||
|
||||
/// `Time` represents a time without a date.
|
||||
///
|
||||
/// It is a specific point in a day.
|
||||
public struct Time {
|
||||
|
||||
public let hour: Int
|
||||
|
||||
public let minute: Int
|
||||
|
||||
public let second: Int
|
||||
|
||||
public let nanosecond: Int
|
||||
|
||||
public init?(hour: Int, minute: Int, second: Int, nanosecond: Int) {
|
||||
/// Create a date with `hour`, `minute`, `second` and `nanosecond` fields.
|
||||
///
|
||||
/// If parameter is illegal, then return nil.
|
||||
///
|
||||
/// Time(hour: 25) == nil
|
||||
/// Time(hour: 1, minute: 61) == nil
|
||||
public init?(hour: Int, minute: Int = 0, second: Int = 0, nanosecond: Int = 0) {
|
||||
guard hour >= 0 && hour < 24 else { return nil }
|
||||
guard minute >= 0 && minute < 60 else { return nil }
|
||||
guard second >= 0 && second < 60 else { return nil }
|
||||
|
@ -195,17 +285,22 @@ public struct Time {
|
|||
self.nanosecond = nanosecond
|
||||
}
|
||||
|
||||
/// Create a time with a timing string
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// "11"
|
||||
/// "11:12"
|
||||
/// "11:12:12"
|
||||
/// "11:12:13.123"
|
||||
/// Time("11") == Time(hour: 11)
|
||||
/// Time("11:12") == Time(hour: 11, minute: 12)
|
||||
/// Time("11:12:13") == Time(hour: 11, minute: 12, second: 13)
|
||||
/// Time("11:12:13.123") == Time(hour: 11, minute: 12, second: 13, nanosecond: 123000000)
|
||||
///
|
||||
/// If timing's format is illegal, then return nil.
|
||||
public init?(timing: String) {
|
||||
let args = timing.split(separator: ":")
|
||||
var h = 0, m = 0, s = 0, ns = 0
|
||||
if args.count > 3 { return nil }
|
||||
|
||||
var h = 0, m = 0, s = 0, ns = 0
|
||||
|
||||
guard let _h = Int(args[0]) else { return nil }
|
||||
h = _h
|
||||
if args.count > 1 {
|
||||
|
@ -213,22 +308,22 @@ public struct Time {
|
|||
m = _m
|
||||
}
|
||||
if args.count > 2 {
|
||||
let components = args[2].split(separator: ".")
|
||||
if components.count > 2 { return nil }
|
||||
guard let _s = Int(components[0]) else { return nil }
|
||||
let values = args[2].split(separator: ".")
|
||||
if values.count > 2 { return nil }
|
||||
guard let _s = Int(values[0]) else { return nil }
|
||||
s = _s
|
||||
|
||||
if components.count > 1 {
|
||||
guard let _ns = Int(components[1]) else { return nil }
|
||||
let digit = components[1].count
|
||||
ns = Int(Double(_ns) * pow(10, Double(9 - digit)))
|
||||
if values.count > 1 {
|
||||
guard let _ns = Int(values[1]) else { return nil }
|
||||
let digits = values[1].count
|
||||
ns = Int(Double(_ns) * pow(10, Double(9 - digits)))
|
||||
}
|
||||
}
|
||||
|
||||
self.init(hour: h, minute: m, second: s, nanosecond: ns)
|
||||
}
|
||||
|
||||
func asDateComponents() -> DateComponents {
|
||||
internal func asDateComponents() -> DateComponents {
|
||||
return DateComponents(hour: hour, minute: minute, second: second, nanosecond: nanosecond)
|
||||
}
|
||||
}
|
||||
|
@ -236,23 +331,35 @@ public struct Time {
|
|||
|
||||
/// `Period` represents a period of time defined in terms of fields.
|
||||
///
|
||||
/// It's a littl different from `Interval`, for example:
|
||||
/// It's a little different from `Interval`.
|
||||
///
|
||||
/// If you add a period `1.month` to the 1st January, you will get the 1st February.
|
||||
/// If you add the same period to the 1st February, you will get the 1st March.
|
||||
/// For example:
|
||||
///
|
||||
/// If you add a period `1.month` to the 1st January,
|
||||
/// you will get the 1st February.
|
||||
/// If you add the same period to the 1st February,
|
||||
/// you will get the 1st March.
|
||||
/// But the intervals(`31.days` in case 1, `28.days` or `29.days` in case 2)
|
||||
/// in these two cases are quite different.
|
||||
public struct Period {
|
||||
|
||||
public let years: Int
|
||||
|
||||
public let months: Int
|
||||
|
||||
public let days: Int
|
||||
|
||||
public let hours: Int
|
||||
|
||||
public let minutes: Int
|
||||
|
||||
public let seconds: Int
|
||||
|
||||
public let nanoseconds: Int
|
||||
|
||||
public init(years: Int = 0, months: Int = 0, days: Int = 0, hours: Int = 0, minutes: Int = 0, seconds: Int = 0, nanoseconds: Int = 0) {
|
||||
public init(years: Int = 0, months: Int = 0, days: Int = 0,
|
||||
hours: Int = 0, minutes: Int = 0, seconds: Int = 0,
|
||||
nanoseconds: Int = 0) {
|
||||
self.years = years
|
||||
self.months = months
|
||||
self.days = days
|
||||
|
@ -262,72 +369,94 @@ public struct Period {
|
|||
self.nanoseconds = nanoseconds
|
||||
}
|
||||
|
||||
public func and(_ period: Period) -> Period {
|
||||
let years = self.years + period.years
|
||||
let months = self.months + period.months
|
||||
let days = self.days + period.days
|
||||
let hours = self.hours + period.hours
|
||||
let minutes = self.minutes + period.minutes
|
||||
let seconds = self.seconds + period.seconds
|
||||
let nanoseconds = self.nanoseconds + period.nanoseconds
|
||||
return Period(years: years, months: months, days: days, hours: hours, minutes: minutes, seconds: seconds, nanoseconds: nanoseconds)
|
||||
}
|
||||
|
||||
/// Returns a new date by adding the right period to the left period.
|
||||
public static func +(lhs: Period, rhs: Period) -> Period {
|
||||
return lhs.and(rhs)
|
||||
}
|
||||
|
||||
func asDateComponents() -> DateComponents {
|
||||
return DateComponents(year: years, month: months, day: days, hour: hours, minute: minutes, second: seconds, nanosecond: nanoseconds)
|
||||
return Period(years: lhs.years.clampedAdding(rhs.years),
|
||||
months: lhs.months.clampedAdding(rhs.months),
|
||||
days: lhs.days.clampedAdding(rhs.days),
|
||||
hours: lhs.hours.clampedAdding(rhs.hours),
|
||||
minutes: lhs.minutes.clampedAdding(rhs.minutes),
|
||||
seconds: lhs.seconds.clampedAdding(rhs.seconds),
|
||||
nanoseconds: lhs.nanoseconds.clampedAdding(rhs.nanoseconds))
|
||||
}
|
||||
|
||||
/// Returns a new date by adding the right period to the left date.
|
||||
public static func +(lhs: Date, rhs: Period) -> Date {
|
||||
return Calendar.autoupdatingCurrent.date(byAdding: rhs.asDateComponents(), to: lhs) ?? .distantFuture
|
||||
}
|
||||
}
|
||||
|
||||
extension Period {
|
||||
|
||||
public static func years(_ n: Int) -> Period {
|
||||
return Period(years: n)
|
||||
/// Returns a new period by adding the right interval to the left period.
|
||||
public static func +(lhs: Period, rhs: Interval) -> Period {
|
||||
return Period(years: lhs.years, months: lhs.months, days: lhs.days,
|
||||
hours: lhs.hours, minutes: lhs.minutes, seconds: lhs.seconds,
|
||||
nanoseconds: lhs.nanoseconds.clampedAdding(rhs.ns))
|
||||
}
|
||||
|
||||
public static func months(_ n: Int) -> Period {
|
||||
return Period(months: n)
|
||||
}
|
||||
public static func days(_ n: Int) -> Period {
|
||||
return Period(days: n)
|
||||
}
|
||||
public static func hours(_ n: Int) -> Period {
|
||||
return Period(hours: n)
|
||||
}
|
||||
public static func minutes(_ n: Int) -> Period {
|
||||
return Period(minutes: n)
|
||||
}
|
||||
public static func seconds(_ n: Int) -> Period {
|
||||
return Period(seconds: n)
|
||||
}
|
||||
public static func nanoseconds(_ n: Int) -> Period {
|
||||
return Period(nanoseconds: n)
|
||||
internal func asDateComponents() -> DateComponents {
|
||||
return DateComponents(year: years, month: months, day: days,
|
||||
hour: hours, minute: minutes, second: seconds,
|
||||
nanosecond: nanoseconds)
|
||||
}
|
||||
}
|
||||
|
||||
extension Int {
|
||||
|
||||
/// Period by setting years to this value.
|
||||
public var years: Period {
|
||||
return Period(years: self)
|
||||
}
|
||||
|
||||
/// Period by setting years to this value.
|
||||
public var year: Period {
|
||||
return years
|
||||
}
|
||||
|
||||
/// Period by setting months to this value.
|
||||
public var months: Period {
|
||||
return Period(months: self)
|
||||
}
|
||||
|
||||
/// Period by setting months to this value.
|
||||
public var month: Period {
|
||||
return months
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// `Weekday` represents a day of the week, without a time.
|
||||
public enum Weekday: Int {
|
||||
case sunday = 1, monday, tuesday, wednesday, thursday, friday, saturday
|
||||
}
|
||||
|
||||
|
||||
/// `MonthDay` represents a day in the month, without a time.
|
||||
public enum MonthDay {
|
||||
|
||||
case january(Int)
|
||||
|
||||
case february(Int)
|
||||
|
||||
case march(Int)
|
||||
|
||||
case april(Int)
|
||||
|
||||
case may(Int)
|
||||
|
||||
case june(Int)
|
||||
|
||||
case july(Int)
|
||||
|
||||
case august(Int)
|
||||
|
||||
case september(Int)
|
||||
|
||||
case october(Int)
|
||||
|
||||
case november(Int)
|
||||
|
||||
case december(Int)
|
||||
|
||||
func asDateComponents() -> DateComponents {
|
||||
internal func asDateComponents() -> DateComponents {
|
||||
switch self {
|
||||
case .january(let day): return DateComponents(month: 1, day: day)
|
||||
case .february(let day): return DateComponents(month: 2, day: day)
|
||||
|
|
|
@ -7,26 +7,30 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public class Job {
|
||||
/// `Job` represents an action that to be invoke.
|
||||
public final class Job {
|
||||
|
||||
/// Last time this job was invoked at.
|
||||
public private(set) var lastTime: Date?
|
||||
|
||||
/// Next time this job will be invoked at.
|
||||
public private(set) var nextTime: Date?
|
||||
|
||||
private var iterator: Atomic<AnyIterator<Interval>>
|
||||
private var iterator: AtomicBox<AnyIterator<Interval>>
|
||||
private let onElapse: (Job) -> Void
|
||||
private let timer: DispatchSourceTimer
|
||||
|
||||
private var suspensions = Atomic<UInt>(0)
|
||||
private var suspensions = AtomicBox<UInt>(0)
|
||||
|
||||
init(schedule: Schedule,
|
||||
queue: DispatchQueue? = nil,
|
||||
tag: String? = nil,
|
||||
onElapse: @escaping (Job) -> Void) {
|
||||
self.iterator = Atomic(schedule.makeIterator())
|
||||
self.iterator = AtomicBox(schedule.makeIterator())
|
||||
self.onElapse = onElapse
|
||||
self.timer = DispatchSource.makeTimerSource(queue: queue)
|
||||
|
||||
let interval = self.iterator.withLock({ $0.next() })?.dispatchInterval ?? DispatchTimeInterval.never
|
||||
let interval = self.iterator.withLock({ $0.next() })?.asDispatchTimeInterval() ?? DispatchTimeInterval.never
|
||||
self.timer.schedule(wallDeadline: .now() + interval)
|
||||
self.timer.setEventHandler { [weak self] in
|
||||
self?.elapse()
|
||||
|
@ -49,15 +53,17 @@ public class Job {
|
|||
|
||||
nextTime = now.addingTimeInterval(i.nanoseconds / pow(10, 9))
|
||||
onElapse(self)
|
||||
timer.schedule(wallDeadline: .now() + i.dispatchInterval)
|
||||
timer.schedule(wallDeadline: .now() + i.asDispatchTimeInterval())
|
||||
}
|
||||
|
||||
/// Reschedule this job with the schedule.
|
||||
public func reschedule(_ schedule: Schedule) {
|
||||
iterator.withLock {
|
||||
$0 = schedule.makeIterator()
|
||||
}
|
||||
}
|
||||
|
||||
/// Suspend this job.
|
||||
public func suspend() {
|
||||
let canSuspend = suspensions.withLock { (n) -> Bool in
|
||||
guard n < UInt.max else { return false }
|
||||
|
@ -70,6 +76,7 @@ public class Job {
|
|||
}
|
||||
}
|
||||
|
||||
/// Resume this job.
|
||||
public func resume() {
|
||||
let canResume = suspensions.withLock { (n) -> Bool in
|
||||
guard n > 0 else { return false }
|
||||
|
@ -81,23 +88,27 @@ public class Job {
|
|||
}
|
||||
}
|
||||
|
||||
/// Cancel this job.
|
||||
public func cancel() {
|
||||
timer.cancel()
|
||||
JobCenter.shared.remove(self)
|
||||
}
|
||||
|
||||
public static func cancel(_ tag: String) {
|
||||
JobCenter.shared.jobs(for: tag).forEach { $0.cancel() }
|
||||
}
|
||||
|
||||
/// Suspend all job that attach the tag.
|
||||
public static func suspend(_ tag: String) {
|
||||
JobCenter.shared.jobs(for: tag).forEach { $0.suspend() }
|
||||
}
|
||||
|
||||
/// Resume all job that attach the tag.
|
||||
public static func resume(_ tag: String) {
|
||||
JobCenter.shared.jobs(for: tag).forEach { $0.resume() }
|
||||
}
|
||||
|
||||
/// Cancel all job that attach the tag.
|
||||
public static func cancel(_ tag: String) {
|
||||
JobCenter.shared.jobs(for: tag).forEach { $0.cancel() }
|
||||
}
|
||||
|
||||
deinit {
|
||||
suspensions.withLock { (n) in
|
||||
while n > 0 {
|
||||
|
@ -111,10 +122,12 @@ public class Job {
|
|||
|
||||
extension Job: Hashable {
|
||||
|
||||
/// The hashValue of this job.
|
||||
public var hashValue: Int {
|
||||
return ObjectIdentifier(self).hashValue
|
||||
}
|
||||
|
||||
/// Returns a boolean value indicating whether the job is equal to another job.
|
||||
public static func ==(lhs: Job, rhs: Job) -> Bool {
|
||||
return lhs.hashValue == rhs.hashValue
|
||||
}
|
||||
|
@ -122,6 +135,9 @@ extension Job: Hashable {
|
|||
|
||||
extension Optional: Hashable where Wrapped: Job {
|
||||
|
||||
/// The hashValue of this job.
|
||||
///
|
||||
/// If job is nil, then return 0.
|
||||
public var hashValue: Int {
|
||||
if case .some(let wrapped) = self {
|
||||
return wrapped.hashValue
|
||||
|
@ -129,12 +145,15 @@ extension Optional: Hashable where Wrapped: Job {
|
|||
return 0
|
||||
}
|
||||
|
||||
/// Returns a boolean value indicating whether the job is equal to another job.
|
||||
///
|
||||
/// If both of these two are nil, then return true.
|
||||
public static func ==(lhs: Optional<Job>, rhs: Optional<Job>) -> Bool {
|
||||
return lhs.hashValue == rhs.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
private class JobCenter {
|
||||
private final class JobCenter {
|
||||
|
||||
static let shared = JobCenter()
|
||||
|
||||
|
|
|
@ -7,6 +7,13 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// `Schedule` represents a plan that gives the times
|
||||
/// at which a job should be invoked.
|
||||
///
|
||||
/// `Schedule` is interval based.
|
||||
/// When a new job is created in the `do` method, it will ask for the first
|
||||
/// interval in this schedule, then set up a timer to invoke itself
|
||||
/// after the interval.
|
||||
public struct Schedule {
|
||||
|
||||
private var sequence: AnySequence<Interval>
|
||||
|
@ -18,6 +25,13 @@ public struct Schedule {
|
|||
return sequence.makeIterator()
|
||||
}
|
||||
|
||||
/// Schedule a job with this schedule.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - queue: The dispatch queue to which the job will be submitted.
|
||||
/// - tag: The tag to attach to the job.
|
||||
/// - onElapse: The job to invoke when time is out.
|
||||
/// - Returns: The job just created.
|
||||
@discardableResult
|
||||
public func `do`(queue: DispatchQueue? = nil,
|
||||
tag: String? = nil,
|
||||
|
@ -25,21 +39,58 @@ public struct Schedule {
|
|||
return Job(schedule: self, queue: queue, tag: tag, onElapse: onElapse)
|
||||
}
|
||||
|
||||
/// Schedule a job with this schedule.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - queue: The dispatch queue to which the job will be submitted.
|
||||
/// - tag: The tag to attach to the queue
|
||||
/// - onElapse: The job to invoke when time is out.
|
||||
/// - Returns: The job just created.
|
||||
@discardableResult
|
||||
public func `do`(queue: DispatchQueue? = nil,
|
||||
tag: String? = nil,
|
||||
onElapse: @escaping () -> Void) -> Job {
|
||||
return self.do(queue: queue, tag: tag, onElapse: { (_) in onElapse() })
|
||||
}
|
||||
}
|
||||
|
||||
public static func make<I>(_ makeIterator: @escaping () -> I) -> Schedule where I: IteratorProtocol, I.Element == Interval {
|
||||
return Schedule(AnySequence(makeIterator))
|
||||
extension Schedule {
|
||||
|
||||
/// Creates a schedule from a `makeUnderlyingIterator()` method.
|
||||
///
|
||||
/// The job will be invoke after each interval
|
||||
/// produced by the iterator that `makeUnderlyingIterator` returns.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let schedule = Schedule.make {
|
||||
/// var i = 0
|
||||
/// return AnyIterator {
|
||||
/// i += 1
|
||||
/// return i
|
||||
/// }
|
||||
/// }
|
||||
/// schedule.do {
|
||||
/// print(Date())
|
||||
/// }
|
||||
///
|
||||
/// > "2001-01-01 00:00:00"
|
||||
/// > "2001-01-01 00:00:01"
|
||||
/// > "2001-01-01 00:00:03"
|
||||
/// > "2001-01-01 00:00:06"
|
||||
/// ...
|
||||
public static func make<I>(_ makeUnderlyingIterator: @escaping () -> I) -> Schedule where I: IteratorProtocol, I.Element == Interval {
|
||||
return Schedule(AnySequence(makeUnderlyingIterator))
|
||||
}
|
||||
|
||||
/// Creates a schedule from an interval sequence.
|
||||
/// The job will be invoke after each interval in the sequence.
|
||||
public static func from<S>(_ sequence: S) -> Schedule where S: Sequence, S.Element == Interval {
|
||||
return Schedule(sequence)
|
||||
}
|
||||
|
||||
/// Creates a schedule from an interval array.
|
||||
/// The job will be invoke after each interval in the array.
|
||||
public static func of(_ intervals: Interval...) -> Schedule {
|
||||
return Schedule(intervals)
|
||||
}
|
||||
|
@ -47,9 +98,33 @@ public struct Schedule {
|
|||
|
||||
extension Schedule {
|
||||
|
||||
public static func make<I>(_ makeIterator: @escaping () -> I) -> Schedule where I: IteratorProtocol, I.Element == Date {
|
||||
/// Creates a schedule from a `makeUnderlyingIterator()` method.
|
||||
///
|
||||
/// The job will be invoke at each date
|
||||
/// produced by the iterator that `makeUnderlyingIterator` returns.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let schedule = Schedule.make {
|
||||
/// return AnyIterator {
|
||||
/// return Date().addingTimeInterval(3)
|
||||
/// }
|
||||
/// }
|
||||
/// print("now:", Date())
|
||||
/// schedule.do {
|
||||
/// print("job", Date())
|
||||
/// }
|
||||
///
|
||||
/// > "now: 2001-01-01 00:00:00"
|
||||
/// > "job: 2001-01-01 00:00:03
|
||||
/// ...
|
||||
///
|
||||
/// You should not return `Date()` in making interator
|
||||
/// if you want to invoke a job immediately,
|
||||
/// use `Schedule.now` then `concat` another schedule instead.
|
||||
public static func make<I>(_ makeUnderlyingIterator: @escaping () -> I) -> Schedule where I: IteratorProtocol, I.Element == Date {
|
||||
return Schedule.make { () -> AnyIterator<Interval> in
|
||||
var iterator = makeIterator()
|
||||
var iterator = makeUnderlyingIterator()
|
||||
var previous: Date!
|
||||
return AnyIterator {
|
||||
previous = previous ?? Date()
|
||||
|
@ -60,14 +135,19 @@ extension Schedule {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a schedule from a date sequence.
|
||||
/// The job will be invoke at each date in the sequence.
|
||||
public static func from<S>(_ sequence: S) -> Schedule where S: Sequence, S.Element == Date {
|
||||
return Schedule.make(sequence.makeIterator)
|
||||
}
|
||||
|
||||
/// Creates a schedule from a date array.
|
||||
/// The job will be invoke at each date in the array.
|
||||
public static func of(_ dates: Date...) -> Schedule {
|
||||
return Schedule.from(dates)
|
||||
}
|
||||
|
||||
/// A dates sequence corresponding to this schedule.
|
||||
public var dates: AnySequence<Date> {
|
||||
return AnySequence { () -> AnyIterator<Date> in
|
||||
let iterator = self.makeIterator()
|
||||
|
@ -84,23 +164,35 @@ extension Schedule {
|
|||
|
||||
extension Schedule {
|
||||
|
||||
/// A schedule with a distant past date.
|
||||
public static var distantPast: Schedule {
|
||||
return Schedule.of(Date.distantPast)
|
||||
}
|
||||
|
||||
/// A schedule with a distant future date.
|
||||
public static var distantFuture: Schedule {
|
||||
return Schedule.of(Date.distantFuture)
|
||||
}
|
||||
|
||||
/// A schedule with no date.
|
||||
public static var never: Schedule {
|
||||
return Schedule.make {
|
||||
AnyIterator<Interval> { nil }
|
||||
AnyIterator<Date> { nil }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Schedule {
|
||||
|
||||
/// Returns a new schedule by concatenating a schedule to this schedule.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let s0 = Schedule.of(1.second, 2.seconds, 3.seconds)
|
||||
/// let s1 = Schedule.of(4.seconds, 4.seconds, 4.seconds)
|
||||
/// let s2 = s0.concat(s1)
|
||||
/// > s2
|
||||
/// > 1.second, 2.seconds, 3.seconds, 4.seconds, 4.seconds, 4.seconds
|
||||
public func concat(_ schedule: Schedule) -> Schedule {
|
||||
return Schedule.make { () -> AnyIterator<Interval> in
|
||||
let i0 = self.makeIterator()
|
||||
|
@ -112,6 +204,15 @@ extension Schedule {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a new schedule by merging a schedule to this schedule.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let s0 = Schedule.of(1.second, 3.seconds, 5.seconds)
|
||||
/// let s1 = Schedule.of(2.seconds, 4.seconds, 6.seconds)
|
||||
/// let s2 = s0.concat(s1)
|
||||
/// > s2
|
||||
/// > 1.second, 1.second, 1.second, 1.second, 1.second, 1.second
|
||||
public func merge(_ schedule: Schedule) -> Schedule {
|
||||
return Schedule.make { () -> AnyIterator<Interval> in
|
||||
let i0 = self.dates.makeIterator()
|
||||
|
@ -137,6 +238,14 @@ extension Schedule {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a new schedule by cutting out a specific number of this schedule.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let s0 = Schedule.every(1.second)
|
||||
/// let s1 = s0.count(3)
|
||||
/// > s1
|
||||
/// 1.second, 1.second, 1.second
|
||||
public func count(_ count: Int) -> Schedule {
|
||||
return Schedule.make { () -> AnyIterator<Interval> in
|
||||
let iterator = self.makeIterator()
|
||||
|
@ -149,48 +258,62 @@ extension Schedule {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a new schedule by cutting out the part which is before the date.
|
||||
public func until(_ date: Date) -> Schedule {
|
||||
return Schedule.make { () -> AnyIterator<Interval> in
|
||||
let iterator = self.makeIterator()
|
||||
var previous: Date!
|
||||
return AnyIterator {
|
||||
previous = previous ?? Date()
|
||||
guard let interval = iterator.next(), previous.addingTimeInterval(interval.seconds) < date else { return nil }
|
||||
guard let interval = iterator.next(),
|
||||
previous.addingTimeInterval(interval.seconds) < date else {
|
||||
return nil
|
||||
}
|
||||
previous.addTimeInterval(interval.seconds)
|
||||
return interval
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the job immediately.
|
||||
public static var now: Schedule {
|
||||
return Schedule.of(0.nanosecond)
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the job after the delay.
|
||||
public static func after(_ delay: Interval) -> Schedule {
|
||||
return Schedule.of(delay)
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the job every interval.
|
||||
public static func every(_ interval: Interval) -> Schedule {
|
||||
return Schedule.make {
|
||||
AnyIterator { interval }
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the job at the specific date.
|
||||
public static func at(_ date: Date) -> Schedule {
|
||||
return Schedule.of(date)
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the job after the delay then repeat
|
||||
/// every interval.
|
||||
public static func after(_ delay: Interval, repeating interval: Interval) -> Schedule {
|
||||
return Schedule.after(delay).concat(Schedule.every(interval))
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the job every period.
|
||||
public static func every(_ period: Period) -> Schedule {
|
||||
return Schedule.make { () -> AnyIterator<Interval> in
|
||||
let calendar = Calendar.autoupdatingCurrent
|
||||
var previous: Date!
|
||||
return AnyIterator {
|
||||
previous = previous ?? Date()
|
||||
guard let next = calendar.date(byAdding: period.asDateComponents(), to: previous) else { return nil }
|
||||
guard let next = calendar.date(byAdding: period.asDateComponents(),
|
||||
to: previous) else {
|
||||
return nil
|
||||
}
|
||||
defer { previous = next }
|
||||
return next.interval(since: previous)
|
||||
}
|
||||
|
@ -200,11 +323,17 @@ extension Schedule {
|
|||
|
||||
extension Schedule {
|
||||
|
||||
/// `EveryDateMiddleware` represents a middleware that wraps a schedule
|
||||
/// which only specify date without time.
|
||||
///
|
||||
/// You should call `at` method to get the time specified schedule.
|
||||
public struct EveryDateMiddleware {
|
||||
|
||||
fileprivate let schedule: Schedule
|
||||
|
||||
/// Returns a schedule at the specific timing.
|
||||
public func at(_ timing: Time) -> Schedule {
|
||||
|
||||
return Schedule.make { () -> AnyIterator<Interval> in
|
||||
let iterator = self.schedule.dates.makeIterator()
|
||||
let calendar = Calendar.autoupdatingCurrent
|
||||
|
@ -212,13 +341,29 @@ extension Schedule {
|
|||
return AnyIterator {
|
||||
previous = previous ?? Date()
|
||||
guard let date = iterator.next(),
|
||||
let next = calendar.nextDate(after: date, matching: timing.asDateComponents(), matchingPolicy: .strict) else { return nil }
|
||||
let next = calendar.nextDate(after: date,
|
||||
matching: timing.asDateComponents(),
|
||||
matchingPolicy: .strict) else {
|
||||
return nil
|
||||
}
|
||||
defer { previous = next }
|
||||
return next.interval(since: previous)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a schedule at the specific timing.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let s = Schedule.every(.monday).at("11:11")
|
||||
///
|
||||
/// Available format:
|
||||
///
|
||||
/// Time("11") == Time(hour: 11)
|
||||
/// Time("11:12") == Time(hour: 11, minute: 12)
|
||||
/// Time("11:12:13") == Time(hour: 11, minute: 12, second: 13)
|
||||
/// Time("11:12:13.123") == Time(hour: 11, minute: 12, second: 13, nanosecond: 123)
|
||||
public func at(_ timing: String) -> Schedule {
|
||||
guard let time = Time(timing: timing) else {
|
||||
return Schedule.never
|
||||
|
@ -226,6 +371,7 @@ extension Schedule {
|
|||
return at(time)
|
||||
}
|
||||
|
||||
/// Returns a schedule at the specific timing.
|
||||
public func at(_ timing: Int...) -> Schedule {
|
||||
let hour = timing[0]
|
||||
let minute = timing.count > 1 ? timing[1] : 0
|
||||
|
@ -239,6 +385,7 @@ extension Schedule {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the job every specific weekday.
|
||||
public static func every(_ weekday: Weekday) -> EveryDateMiddleware {
|
||||
let schedule = Schedule.make { () -> AnyIterator<Interval> in
|
||||
let calendar = Calendar.autoupdatingCurrent
|
||||
|
@ -257,6 +404,7 @@ extension Schedule {
|
|||
return EveryDateMiddleware(schedule: schedule)
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the job every specific weekdays.
|
||||
public static func every(_ weekdays: Weekday...) -> EveryDateMiddleware {
|
||||
var schedule = every(weekdays[0]).schedule
|
||||
if weekdays.count > 1 {
|
||||
|
@ -267,6 +415,7 @@ extension Schedule {
|
|||
return EveryDateMiddleware(schedule: schedule)
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the job every specific day in the month.
|
||||
public static func every(_ monthDay: MonthDay) -> EveryDateMiddleware {
|
||||
let schedule = Schedule.make { () -> AnyIterator<Interval> in
|
||||
let calendar = Calendar.autoupdatingCurrent
|
||||
|
@ -286,6 +435,7 @@ extension Schedule {
|
|||
return EveryDateMiddleware(schedule: schedule)
|
||||
}
|
||||
|
||||
/// Creates a schedule that invokes the job every specific days in the months.
|
||||
public static func every(_ mondays: MonthDay...) -> EveryDateMiddleware {
|
||||
var schedule = every(mondays[0]).schedule
|
||||
if mondays.count > 1 {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
class Atomic<Wrapped> {
|
||||
class AtomicBox<Wrapped> {
|
||||
|
||||
private var lock = NSLock()
|
||||
|
||||
|
@ -41,3 +41,15 @@ extension String {
|
|||
}
|
||||
}
|
||||
|
||||
extension Int {
|
||||
|
||||
func clampedAdding(_ other: Int) -> Int {
|
||||
let r = addingReportingOverflow(other)
|
||||
return r.overflow ? (other > 0 ? .max : .min) : r.partialValue
|
||||
}
|
||||
|
||||
func clampedSubtracting(_ other: Int) -> Int {
|
||||
let r = subtractingReportingOverflow(other)
|
||||
return r.overflow ? (other > 0 ? .max : .min) : r.partialValue
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,10 +12,10 @@ final class DateTimeTests: XCTestCase {
|
|||
|
||||
func testInterval2DispatchInterval() {
|
||||
let i0 = 1.23.seconds
|
||||
XCTAssertEqual(i0.dispatchInterval, DispatchTimeInterval.nanoseconds(Int(i0.nanoseconds)))
|
||||
XCTAssertEqual(i0.asDispatchTimeInterval(), DispatchTimeInterval.nanoseconds(Int(i0.nanoseconds)))
|
||||
|
||||
let i1 = 4.56.minutes + 7.89.hours
|
||||
XCTAssertEqual(i1.dispatchInterval, DispatchTimeInterval.nanoseconds(Int(i1.nanoseconds)))
|
||||
XCTAssertEqual(i1.asDispatchTimeInterval(), DispatchTimeInterval.nanoseconds(Int(i1.nanoseconds)))
|
||||
}
|
||||
|
||||
func testIntervalConvertible() {
|
||||
|
@ -42,7 +42,7 @@ final class DateTimeTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testPeriodAnd() {
|
||||
let dateComponents: Period = .years(1) + .months(2) + .days(3)
|
||||
let dateComponents: Period = 1.year + 2.months + 3.days
|
||||
XCTAssertEqual(dateComponents.years, 1)
|
||||
XCTAssertEqual(dateComponents.months, 2)
|
||||
XCTAssertEqual(dateComponents.days, 3)
|
||||
|
|
|
@ -10,7 +10,6 @@ final class ScheduleTests: XCTestCase {
|
|||
let s1 = Schedule.from(intervals)
|
||||
XCTAssert(s1.makeIterator().isEqual(to: intervals, leeway: 0.001.seconds))
|
||||
|
||||
|
||||
let d0 = Date() + intervals[0]
|
||||
let d1 = d0 + intervals[1]
|
||||
let d2 = d1 + intervals[2]
|
||||
|
@ -74,17 +73,13 @@ final class ScheduleTests: XCTestCase {
|
|||
func testMerge() {
|
||||
let intervals0: [Interval] = [1.second, 2.minutes, 1.hour]
|
||||
let intervals1: [Interval] = [2.seconds, 1.minutes, 1.seconds]
|
||||
|
||||
let scheudle0 = Schedule.from(intervals0).merge(Schedule.from(intervals1))
|
||||
|
||||
let scheudle1 = Schedule.of(1.second, 1.second, 1.minutes, 1.seconds, 58.seconds, 1.hour)
|
||||
XCTAssert(scheudle0.makeIterator().isEqual(to: scheudle1.makeIterator(), leeway: 0.001.seconds))
|
||||
}
|
||||
|
||||
func testEveryPeriod() {
|
||||
|
||||
let s = Schedule.every(.years(1)).count(10)
|
||||
|
||||
let s = Schedule.every(1.year).count(10)
|
||||
var date = Date()
|
||||
for i in s.dates {
|
||||
XCTAssertEqual(i.dateComponents.year!, date.dateComponents.year! + 1)
|
||||
|
|
|
@ -34,8 +34,8 @@ extension Sequence where Element == Interval {
|
|||
var i0 = self.makeIterator()
|
||||
var i1 = sequence.makeIterator()
|
||||
while let l = i0.next(), let r = i1.next() {
|
||||
let diff = Swift.max(l, r) - Swift.min(l, r)
|
||||
if diff < leeway { continue }
|
||||
let diff = Interval.longest(l, r) - Interval.shortest(l, r)
|
||||
if diff.isShorter(than: leeway) { continue }
|
||||
return false
|
||||
}
|
||||
return i0.next() == i1.next()
|
||||
|
@ -45,8 +45,8 @@ extension Sequence where Element == Interval {
|
|||
extension Interval {
|
||||
|
||||
func isEqual(to interval: Interval, leeway: Interval) -> Bool {
|
||||
let diff = Swift.max(self, interval) - Swift.min(self, interval)
|
||||
return diff < leeway
|
||||
let diff = Interval.longest(self, interval) - Interval.shortest(self, interval)
|
||||
return diff.isShorter(than: leeway)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue