diff --git a/.gitignore b/.gitignore
index 02c0875..e1ae197 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
\ No newline at end of file
diff --git a/.swift-version b/.swift-version
new file mode 100644
index 0000000..8012ebb
--- /dev/null
+++ b/.swift-version
@@ -0,0 +1 @@
+4.2
\ No newline at end of file
diff --git a/README.md b/README.md
index d339ff3..97d7372 100644
--- a/README.md
+++ b/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:
+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("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")
+]
+```
-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
-```
\ No newline at end of file
diff --git a/Schedule.podspec b/Schedule.podspec
index 73def7f..5a7f46c 100644
--- a/Schedule.podspec
+++ b/Schedule.podspec
@@ -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
\ No newline at end of file
diff --git a/Schedule.xcodeproj/ScheduleTests_Info.plist b/Schedule.xcodeproj/ScheduleTests_Info.plist
new file mode 100644
index 0000000..7c23420
--- /dev/null
+++ b/Schedule.xcodeproj/ScheduleTests_Info.plist
@@ -0,0 +1,25 @@
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSPrincipalClass
+
+
+
diff --git a/Schedule.xcodeproj/Schedule_Info.plist b/Schedule.xcodeproj/Schedule_Info.plist
new file mode 100644
index 0000000..57ada9f
--- /dev/null
+++ b/Schedule.xcodeproj/Schedule_Info.plist
@@ -0,0 +1,25 @@
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSPrincipalClass
+
+
+
diff --git a/Schedule.xcodeproj/project.pbxproj b/Schedule.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..3c318a2
--- /dev/null
+++ b/Schedule.xcodeproj/project.pbxproj
@@ -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 = ""; };
+ OBJ_11 /* Schedule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Schedule.swift; sourceTree = ""; };
+ OBJ_12 /* Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = ""; };
+ OBJ_15 /* DateTimeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeTests.swift; sourceTree = ""; };
+ OBJ_16 /* ScheduleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleTests.swift; sourceTree = ""; };
+ OBJ_17 /* Util.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = ""; };
+ OBJ_18 /* XCTestManifests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestManifests.swift; sourceTree = ""; };
+ OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; };
+ OBJ_9 /* DateTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTime.swift; sourceTree = ""; };
+ "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 = "";
+ };
+ 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 */;
+}
diff --git a/Schedule.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Schedule.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/Schedule.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Schedule.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Schedule.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/Schedule.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/Schedule.xcodeproj/xcshareddata/xcschemes/Schedule-Package.xcscheme b/Schedule.xcodeproj/xcshareddata/xcschemes/Schedule-Package.xcscheme
new file mode 100644
index 0000000..d142845
--- /dev/null
+++ b/Schedule.xcodeproj/xcshareddata/xcschemes/Schedule-Package.xcscheme
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Schedule.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist b/Schedule.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist
new file mode 100644
index 0000000..12a793f
--- /dev/null
+++ b/Schedule.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist
@@ -0,0 +1,12 @@
+
+
+
+ SchemeUserState
+
+ Schedule-Package.xcscheme
+
+
+ SuppressBuildableAutocreation
+
+
+
diff --git a/Sources/Schedule/DateTime.swift b/Sources/Schedule/DateTime.swift
index 07855ce..5b29c53 100644
--- a/Sources/Schedule/DateTime.swift
+++ b/Sources/Schedule/DateTime.swift
@@ -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)
diff --git a/Sources/Schedule/Job.swift b/Sources/Schedule/Job.swift
index f5a7bee..d1a631d 100644
--- a/Sources/Schedule/Job.swift
+++ b/Sources/Schedule/Job.swift
@@ -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>
+ private var iterator: AtomicBox>
private let onElapse: (Job) -> Void
private let timer: DispatchSourceTimer
- private var suspensions = Atomic(0)
+ private var suspensions = AtomicBox(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, rhs: Optional) -> Bool {
return lhs.hashValue == rhs.hashValue
}
}
-private class JobCenter {
+private final class JobCenter {
static let shared = JobCenter()
diff --git a/Sources/Schedule/Schedule.swift b/Sources/Schedule/Schedule.swift
index 67dbeff..81c5a25 100644
--- a/Sources/Schedule/Schedule.swift
+++ b/Sources/Schedule/Schedule.swift
@@ -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
@@ -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() })
}
+}
+
+extension Schedule {
- public static func make(_ makeIterator: @escaping () -> I) -> Schedule where I: IteratorProtocol, I.Element == Interval {
- return Schedule(AnySequence(makeIterator))
+ /// 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(_ 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(_ 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(_ 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(_ makeUnderlyingIterator: @escaping () -> I) -> Schedule where I: IteratorProtocol, I.Element == Date {
return Schedule.make { () -> AnyIterator 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(_ 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 {
return AnySequence { () -> AnyIterator 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 { nil }
+ AnyIterator { 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 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 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 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 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 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 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 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 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 {
diff --git a/Sources/Schedule/Util.swift b/Sources/Schedule/Util.swift
index 8031da8..bbd49e4 100644
--- a/Sources/Schedule/Util.swift
+++ b/Sources/Schedule/Util.swift
@@ -7,7 +7,7 @@
import Foundation
-class Atomic {
+class AtomicBox {
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
+ }
+}
diff --git a/Tests/ScheduleTests/DateTimeTests.swift b/Tests/ScheduleTests/DateTimeTests.swift
index 5d10ee6..04b9189 100644
--- a/Tests/ScheduleTests/DateTimeTests.swift
+++ b/Tests/ScheduleTests/DateTimeTests.swift
@@ -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)
diff --git a/Tests/ScheduleTests/ScheduleTests.swift b/Tests/ScheduleTests/ScheduleTests.swift
index 502599d..b716c54 100644
--- a/Tests/ScheduleTests/ScheduleTests.swift
+++ b/Tests/ScheduleTests/ScheduleTests.swift
@@ -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)
diff --git a/Tests/ScheduleTests/Util.swift b/Tests/ScheduleTests/Util.swift
index 301e260..16351ab 100644
--- a/Tests/ScheduleTests/Util.swift
+++ b/Tests/ScheduleTests/Util.swift
@@ -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)
}
}