Supplement docs

This commit is contained in:
QuentinJin 2018-07-07 11:59:21 +08:00
parent 28fc91ea06
commit ce4d5d45b8
18 changed files with 1200 additions and 155 deletions

71
.gitignore vendored
View File

@ -1,4 +1,67 @@
.DS_Store ## Build generated
/.build build/
/Packages DerivedData/
/*.xcodeproj
## 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

1
.swift-version Normal file
View File

@ -0,0 +1 @@
4.2

View File

@ -1,22 +1,29 @@
# Schedule # 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 ## Usage
### Getting Start
Scheduling a job can not be simplier. Scheduling a job can not be simplier.
```swift ```swift
func job() { } func heartBeat() { }
Schedule.every(1.minute).do(job) Schedule.every(0.5.seconds).do(heartBeat)
``` ```
### Interval-based Scheduling ### Interval-based Scheduling
```swift ```swift
@ -37,7 +44,7 @@ Schedule.from([1.second, 2.minutes, 3.hours]).do { }
## Date-based Scheduling ### Date-based Scheduling
```swift ```swift
import Schedule import Schedule
@ -62,7 +69,7 @@ import Schedule
/// concat /// concat
let s0 = Schedule.at(birthdate) let s0 = Schedule.at(birthdate)
let s1 = Schedule.every(.year(1)) let s1 = Schedule.every(1.year)
let birthdaySchedule = s0.concat.s1 let birthdaySchedule = s0.concat.s1
birthdaySchedule.do { birthdaySchedule.do {
print("Happy birthday") 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 s4 = Schedule.every(.october(1)).at(8, 30)
let holiday = s3.merge(s3) let holiday = s3.merge(s3)
holidaySchedule.do { 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 ### 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 ```swift
let job = Schedule.every(1.day).do { } let job = Schedule.every(1.day).do { }
job.suspend() // will suspend job, it won't change job's schedule job.suspend()
job.resume() // will resume the suspended job, it won't change job's schedule job.resume()
job.cancel() // will cancel job, then job will be released after variable `job` is gone 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 ```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
```

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = "Schedule" s.name = "Schedule"
s.version = "0.0.2" s.version = "0.0.3"
s.summary = "Swift Job Schedule." s.summary = "Swift Job Schedule."
s.homepage = "https://github.com/jianstm/Schedule" s.homepage = "https://github.com/jianstm/Schedule"
s.license = { :type => "MIT", :file => "LICENSE" } s.license = { :type => "MIT", :file => "LICENSE" }
@ -9,10 +9,10 @@ Pod::Spec.new do |s|
:tag => "#{s.version}" } :tag => "#{s.version}" }
s.source_files = "Sources/Schedule/*.swift" s.source_files = "Sources/Schedule/*.swift"
s.requires_arc = true s.requires_arc = true
s.swift_version = "4.0" s.swift_version = "4.2"
s.ios.deployment_target = "8.0" 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.tvos.deployment_target = "9.0"
s.watchos.deployment_target = "2.0" s.watchos.deployment_target = "2.0"
end end

View File

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

View File

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

View File

@ -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 */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

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

View File

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

View File

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

View File

@ -7,109 +7,191 @@
import Foundation import Foundation
/// `Interval` represents a length of time, it's contextless. /// `Interval` represents a duration of time.
public struct Interval { public struct Interval {
/// The length of this interval, measured in nanoseconds.
public let nanoseconds: Double public let nanoseconds: Double
/// Creates an interval from the given number of nanoseconds.
public init(nanoseconds: Double) { public init(nanoseconds: Double) {
self.nanoseconds = nanoseconds self.nanoseconds = nanoseconds
} }
public init(seconds: Double) { /// A boolean value indicating whether this interval is negative.
self.nanoseconds = seconds * pow(10, 9) ///
} /// A interval can be negative.
///
public var seconds: Double { /// - The interval between 6:00 and 7:00 is `1.hour`,
return nanoseconds / pow(10, 9) /// 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 { public var isNegative: Bool {
return nanoseconds.isLess(than: 0) return nanoseconds.isLess(than: 0)
} }
public var dispatchInterval: DispatchTimeInterval { /// The magnitude of this interval.
if nanoseconds > Double(Int.max) { return .nanoseconds(.max) } ///
if nanoseconds < Double(Int.min) { return .nanoseconds(.min) } /// It's the absolute value of the length of this interval,
return .nanoseconds(Int(nanoseconds)) /// 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 { public static func *(lhs: Interval, rhs: Double) -> Interval {
return Interval(nanoseconds: lhs.nanoseconds * rhs) 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 { public static func +(lhs: Interval, rhs: Interval) -> Interval {
return Interval(nanoseconds: lhs.nanoseconds + rhs.nanoseconds) 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 { public static func -(lhs: Interval, rhs: Interval) -> Interval {
return Interval(nanoseconds: lhs.nanoseconds - rhs.nanoseconds) 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 { extension Interval: Hashable {
/// The hashValue of this interval.
public var hashValue: Int { public var hashValue: Int {
return nanoseconds.hashValue return nanoseconds.hashValue
} }
/// Returns a boolean value indicating whether the interval is equal to another interval.
public static func ==(lhs: Interval, rhs: Interval) -> Bool { public static func ==(lhs: Interval, rhs: Interval) -> Bool {
return lhs.hashValue == rhs.hashValue return lhs.nanoseconds == rhs.nanoseconds
}
}
extension Interval: Comparable {
public static func <(lhs: Interval, rhs: Interval) -> Bool {
return lhs.nanoseconds.magnitude < rhs.nanoseconds.magnitude
} }
} }
extension Date { extension Date {
/// The interval between this date and now.
///
/// If the date is earlier than now, the interval is negative.
public var intervalSinceNow: Interval { public var intervalSinceNow: Interval {
return timeIntervalSinceNow.seconds return timeIntervalSinceNow.seconds
} }
/// Returns the interval between this date and the given date.
///
/// If the date is earlier than the given date, the interval is negative.
public func interval(since date: Date) -> Interval { public func interval(since date: Date) -> Interval {
return timeIntervalSince(date).seconds return timeIntervalSince(date).seconds
} }
/// Return a new date by adding an interval to the date.
public static func +(lhs: Date, rhs: Interval) -> Date { public static func +(lhs: Date, rhs: Interval) -> Date {
return lhs.addingTimeInterval(rhs.seconds) return lhs.addingTimeInterval(rhs.seconds)
} }
@discardableResult /// Add an interval to the date.
public static func +=(lhs: inout Date, rhs: Interval) -> Date { public static func +=(lhs: inout Date, rhs: Interval) {
lhs = lhs + rhs lhs = lhs + rhs
return lhs
} }
} }
/// `IntervalConvertible` provides a set of intuitive apis for creating interval.
public protocol IntervalConvertible { public protocol IntervalConvertible {
func asNanoseconds() -> Double var nanoseconds: Interval { get }
} }
extension Int: IntervalConvertible { extension Int: IntervalConvertible {
public func asNanoseconds() -> Double { public var nanoseconds: Interval {
return Double(self) return Interval(nanoseconds: Double(self))
} }
} }
extension Double: IntervalConvertible { extension Double: IntervalConvertible {
public func asNanoseconds() -> Double { public var nanoseconds: Interval {
return self return Interval(nanoseconds: self)
} }
} }
extension IntervalConvertible { extension IntervalConvertible {
public var nanoseconds: Interval {
return Interval(nanoseconds: asNanoseconds())
}
public var nanosecond: Interval { public var nanosecond: Interval {
return nanoseconds 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. /// `Time` represents a time without a date.
///
/// It is a specific point in a day.
public struct Time { public struct Time {
public let hour: Int public let hour: Int
public let minute: Int public let minute: Int
public let second: Int public let second: Int
public let nanosecond: 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 hour >= 0 && hour < 24 else { return nil }
guard minute >= 0 && minute < 60 else { return nil } guard minute >= 0 && minute < 60 else { return nil }
guard second >= 0 && second < 60 else { return nil } guard second >= 0 && second < 60 else { return nil }
@ -195,17 +285,22 @@ public struct Time {
self.nanosecond = nanosecond self.nanosecond = nanosecond
} }
/// Create a time with a timing string
///
/// For example: /// For example:
/// ///
/// "11" /// Time("11") == Time(hour: 11)
/// "11:12" /// Time("11:12") == Time(hour: 11, minute: 12)
/// "11:12:12" /// Time("11:12:13") == Time(hour: 11, minute: 12, second: 13)
/// "11:12:13.123" /// 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) { public init?(timing: String) {
let args = timing.split(separator: ":") let args = timing.split(separator: ":")
var h = 0, m = 0, s = 0, ns = 0
if args.count > 3 { return nil } if args.count > 3 { return nil }
var h = 0, m = 0, s = 0, ns = 0
guard let _h = Int(args[0]) else { return nil } guard let _h = Int(args[0]) else { return nil }
h = _h h = _h
if args.count > 1 { if args.count > 1 {
@ -213,22 +308,22 @@ public struct Time {
m = _m m = _m
} }
if args.count > 2 { if args.count > 2 {
let components = args[2].split(separator: ".") let values = args[2].split(separator: ".")
if components.count > 2 { return nil } if values.count > 2 { return nil }
guard let _s = Int(components[0]) else { return nil } guard let _s = Int(values[0]) else { return nil }
s = _s s = _s
if components.count > 1 { if values.count > 1 {
guard let _ns = Int(components[1]) else { return nil } guard let _ns = Int(values[1]) else { return nil }
let digit = components[1].count let digits = values[1].count
ns = Int(Double(_ns) * pow(10, Double(9 - digit))) ns = Int(Double(_ns) * pow(10, Double(9 - digits)))
} }
} }
self.init(hour: h, minute: m, second: s, nanosecond: ns) 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) 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. /// `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. /// For example:
/// If you add the same period to the 1st February, you will get the 1st March. ///
/// 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) /// But the intervals(`31.days` in case 1, `28.days` or `29.days` in case 2)
/// in these two cases are quite different. /// in these two cases are quite different.
public struct Period { public struct Period {
public let years: Int public let years: Int
public let months: Int public let months: Int
public let days: Int public let days: Int
public let hours: Int public let hours: Int
public let minutes: Int public let minutes: Int
public let seconds: Int public let seconds: Int
public let nanoseconds: 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.years = years
self.months = months self.months = months
self.days = days self.days = days
@ -262,72 +369,94 @@ public struct Period {
self.nanoseconds = nanoseconds self.nanoseconds = nanoseconds
} }
public func and(_ period: Period) -> Period { /// Returns a new date by adding the right period to the left 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)
}
public static func +(lhs: Period, rhs: Period) -> Period { public static func +(lhs: Period, rhs: Period) -> Period {
return lhs.and(rhs) return Period(years: lhs.years.clampedAdding(rhs.years),
} months: lhs.months.clampedAdding(rhs.months),
days: lhs.days.clampedAdding(rhs.days),
func asDateComponents() -> DateComponents { hours: lhs.hours.clampedAdding(rhs.hours),
return DateComponents(year: years, month: months, day: days, hour: hours, minute: minutes, second: seconds, nanosecond: nanoseconds) 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 { public static func +(lhs: Date, rhs: Period) -> Date {
return Calendar.autoupdatingCurrent.date(byAdding: rhs.asDateComponents(), to: lhs) ?? .distantFuture return Calendar.autoupdatingCurrent.date(byAdding: rhs.asDateComponents(), to: lhs) ?? .distantFuture
} }
}
extension Period {
public static func years(_ n: Int) -> Period { /// Returns a new period by adding the right interval to the left period.
return Period(years: n) 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 { internal func asDateComponents() -> DateComponents {
return Period(months: n) return DateComponents(year: years, month: months, day: days,
} hour: hours, minute: minutes, second: seconds,
public static func days(_ n: Int) -> Period { nanosecond: nanoseconds)
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)
} }
} }
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 { public enum MonthDay {
case january(Int) case january(Int)
case february(Int) case february(Int)
case march(Int) case march(Int)
case april(Int) case april(Int)
case may(Int) case may(Int)
case june(Int) case june(Int)
case july(Int) case july(Int)
case august(Int) case august(Int)
case september(Int) case september(Int)
case october(Int) case october(Int)
case november(Int) case november(Int)
case december(Int) case december(Int)
func asDateComponents() -> DateComponents { internal func asDateComponents() -> DateComponents {
switch self { switch self {
case .january(let day): return DateComponents(month: 1, day: day) case .january(let day): return DateComponents(month: 1, day: day)
case .february(let day): return DateComponents(month: 2, day: day) case .february(let day): return DateComponents(month: 2, day: day)

View File

@ -7,26 +7,30 @@
import Foundation 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? public private(set) var lastTime: Date?
/// Next time this job will be invoked at.
public private(set) var nextTime: Date? 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 onElapse: (Job) -> Void
private let timer: DispatchSourceTimer private let timer: DispatchSourceTimer
private var suspensions = Atomic<UInt>(0) private var suspensions = AtomicBox<UInt>(0)
init(schedule: Schedule, init(schedule: Schedule,
queue: DispatchQueue? = nil, queue: DispatchQueue? = nil,
tag: String? = nil, tag: String? = nil,
onElapse: @escaping (Job) -> Void) { onElapse: @escaping (Job) -> Void) {
self.iterator = Atomic(schedule.makeIterator()) self.iterator = AtomicBox(schedule.makeIterator())
self.onElapse = onElapse self.onElapse = onElapse
self.timer = DispatchSource.makeTimerSource(queue: queue) 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.schedule(wallDeadline: .now() + interval)
self.timer.setEventHandler { [weak self] in self.timer.setEventHandler { [weak self] in
self?.elapse() self?.elapse()
@ -49,15 +53,17 @@ public class Job {
nextTime = now.addingTimeInterval(i.nanoseconds / pow(10, 9)) nextTime = now.addingTimeInterval(i.nanoseconds / pow(10, 9))
onElapse(self) 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) { public func reschedule(_ schedule: Schedule) {
iterator.withLock { iterator.withLock {
$0 = schedule.makeIterator() $0 = schedule.makeIterator()
} }
} }
/// Suspend this job.
public func suspend() { public func suspend() {
let canSuspend = suspensions.withLock { (n) -> Bool in let canSuspend = suspensions.withLock { (n) -> Bool in
guard n < UInt.max else { return false } guard n < UInt.max else { return false }
@ -70,6 +76,7 @@ public class Job {
} }
} }
/// Resume this job.
public func resume() { public func resume() {
let canResume = suspensions.withLock { (n) -> Bool in let canResume = suspensions.withLock { (n) -> Bool in
guard n > 0 else { return false } guard n > 0 else { return false }
@ -81,23 +88,27 @@ public class Job {
} }
} }
/// Cancel this job.
public func cancel() { public func cancel() {
timer.cancel() timer.cancel()
JobCenter.shared.remove(self) JobCenter.shared.remove(self)
} }
public static func cancel(_ tag: String) { /// Suspend all job that attach the tag.
JobCenter.shared.jobs(for: tag).forEach { $0.cancel() }
}
public static func suspend(_ tag: String) { public static func suspend(_ tag: String) {
JobCenter.shared.jobs(for: tag).forEach { $0.suspend() } JobCenter.shared.jobs(for: tag).forEach { $0.suspend() }
} }
/// Resume all job that attach the tag.
public static func resume(_ tag: String) { public static func resume(_ tag: String) {
JobCenter.shared.jobs(for: tag).forEach { $0.resume() } 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 { deinit {
suspensions.withLock { (n) in suspensions.withLock { (n) in
while n > 0 { while n > 0 {
@ -111,10 +122,12 @@ public class Job {
extension Job: Hashable { extension Job: Hashable {
/// The hashValue of this job.
public var hashValue: Int { public var hashValue: Int {
return ObjectIdentifier(self).hashValue 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 { public static func ==(lhs: Job, rhs: Job) -> Bool {
return lhs.hashValue == rhs.hashValue return lhs.hashValue == rhs.hashValue
} }
@ -122,6 +135,9 @@ extension Job: Hashable {
extension Optional: Hashable where Wrapped: Job { extension Optional: Hashable where Wrapped: Job {
/// The hashValue of this job.
///
/// If job is nil, then return 0.
public var hashValue: Int { public var hashValue: Int {
if case .some(let wrapped) = self { if case .some(let wrapped) = self {
return wrapped.hashValue return wrapped.hashValue
@ -129,12 +145,15 @@ extension Optional: Hashable where Wrapped: Job {
return 0 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 { public static func ==(lhs: Optional<Job>, rhs: Optional<Job>) -> Bool {
return lhs.hashValue == rhs.hashValue return lhs.hashValue == rhs.hashValue
} }
} }
private class JobCenter { private final class JobCenter {
static let shared = JobCenter() static let shared = JobCenter()

View File

@ -7,6 +7,13 @@
import Foundation 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 { public struct Schedule {
private var sequence: AnySequence<Interval> private var sequence: AnySequence<Interval>
@ -18,6 +25,13 @@ public struct Schedule {
return sequence.makeIterator() 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 @discardableResult
public func `do`(queue: DispatchQueue? = nil, public func `do`(queue: DispatchQueue? = nil,
tag: String? = nil, tag: String? = nil,
@ -25,21 +39,58 @@ public struct Schedule {
return Job(schedule: self, queue: queue, tag: tag, onElapse: onElapse) 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 @discardableResult
public func `do`(queue: DispatchQueue? = nil, public func `do`(queue: DispatchQueue? = nil,
tag: String? = nil, tag: String? = nil,
onElapse: @escaping () -> Void) -> Job { onElapse: @escaping () -> Void) -> Job {
return self.do(queue: queue, tag: tag, onElapse: { (_) in onElapse() }) return self.do(queue: queue, tag: tag, onElapse: { (_) in onElapse() })
} }
}
extension Schedule {
public static func make<I>(_ makeIterator: @escaping () -> I) -> Schedule where I: IteratorProtocol, I.Element == Interval { /// Creates a schedule from a `makeUnderlyingIterator()` method.
return Schedule(AnySequence(makeIterator)) ///
/// 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 { public static func from<S>(_ sequence: S) -> Schedule where S: Sequence, S.Element == Interval {
return Schedule(sequence) return Schedule(sequence)
} }
/// Creates a schedule from an interval array.
/// The job will be invoke after each interval in the array.
public static func of(_ intervals: Interval...) -> Schedule { public static func of(_ intervals: Interval...) -> Schedule {
return Schedule(intervals) return Schedule(intervals)
} }
@ -47,9 +98,33 @@ public struct Schedule {
extension 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 return Schedule.make { () -> AnyIterator<Interval> in
var iterator = makeIterator() var iterator = makeUnderlyingIterator()
var previous: Date! var previous: Date!
return AnyIterator { return AnyIterator {
previous = previous ?? Date() 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 { public static func from<S>(_ sequence: S) -> Schedule where S: Sequence, S.Element == Date {
return Schedule.make(sequence.makeIterator) return Schedule.make(sequence.makeIterator)
} }
/// Creates a schedule from a date array.
/// The job will be invoke at each date in the array.
public static func of(_ dates: Date...) -> Schedule { public static func of(_ dates: Date...) -> Schedule {
return Schedule.from(dates) return Schedule.from(dates)
} }
/// A dates sequence corresponding to this schedule.
public var dates: AnySequence<Date> { public var dates: AnySequence<Date> {
return AnySequence { () -> AnyIterator<Date> in return AnySequence { () -> AnyIterator<Date> in
let iterator = self.makeIterator() let iterator = self.makeIterator()
@ -84,23 +164,35 @@ extension Schedule {
extension Schedule { extension Schedule {
/// A schedule with a distant past date.
public static var distantPast: Schedule { public static var distantPast: Schedule {
return Schedule.of(Date.distantPast) return Schedule.of(Date.distantPast)
} }
/// A schedule with a distant future date.
public static var distantFuture: Schedule { public static var distantFuture: Schedule {
return Schedule.of(Date.distantFuture) return Schedule.of(Date.distantFuture)
} }
/// A schedule with no date.
public static var never: Schedule { public static var never: Schedule {
return Schedule.make { return Schedule.make {
AnyIterator<Interval> { nil } AnyIterator<Date> { nil }
} }
} }
} }
extension Schedule { 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 { public func concat(_ schedule: Schedule) -> Schedule {
return Schedule.make { () -> AnyIterator<Interval> in return Schedule.make { () -> AnyIterator<Interval> in
let i0 = self.makeIterator() 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 { public func merge(_ schedule: Schedule) -> Schedule {
return Schedule.make { () -> AnyIterator<Interval> in return Schedule.make { () -> AnyIterator<Interval> in
let i0 = self.dates.makeIterator() 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 { public func count(_ count: Int) -> Schedule {
return Schedule.make { () -> AnyIterator<Interval> in return Schedule.make { () -> AnyIterator<Interval> in
let iterator = self.makeIterator() 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 { public func until(_ date: Date) -> Schedule {
return Schedule.make { () -> AnyIterator<Interval> in return Schedule.make { () -> AnyIterator<Interval> in
let iterator = self.makeIterator() let iterator = self.makeIterator()
var previous: Date! var previous: Date!
return AnyIterator { return AnyIterator {
previous = previous ?? Date() 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) previous.addTimeInterval(interval.seconds)
return interval return interval
} }
} }
} }
/// Creates a schedule that invokes the job immediately.
public static var now: Schedule { public static var now: Schedule {
return Schedule.of(0.nanosecond) return Schedule.of(0.nanosecond)
} }
/// Creates a schedule that invokes the job after the delay.
public static func after(_ delay: Interval) -> Schedule { public static func after(_ delay: Interval) -> Schedule {
return Schedule.of(delay) return Schedule.of(delay)
} }
/// Creates a schedule that invokes the job every interval.
public static func every(_ interval: Interval) -> Schedule { public static func every(_ interval: Interval) -> Schedule {
return Schedule.make { return Schedule.make {
AnyIterator { interval } AnyIterator { interval }
} }
} }
/// Creates a schedule that invokes the job at the specific date.
public static func at(_ date: Date) -> Schedule { public static func at(_ date: Date) -> Schedule {
return Schedule.of(date) return Schedule.of(date)
} }
/// Creates a schedule that invokes the job after the delay then repeat
/// every interval.
public static func after(_ delay: Interval, repeating interval: Interval) -> Schedule { public static func after(_ delay: Interval, repeating interval: Interval) -> Schedule {
return Schedule.after(delay).concat(Schedule.every(interval)) return Schedule.after(delay).concat(Schedule.every(interval))
} }
/// Creates a schedule that invokes the job every period.
public static func every(_ period: Period) -> Schedule { public static func every(_ period: Period) -> Schedule {
return Schedule.make { () -> AnyIterator<Interval> in return Schedule.make { () -> AnyIterator<Interval> in
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
var previous: Date! var previous: Date!
return AnyIterator { return AnyIterator {
previous = previous ?? Date() 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 } defer { previous = next }
return next.interval(since: previous) return next.interval(since: previous)
} }
@ -200,11 +323,17 @@ extension Schedule {
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 { public struct EveryDateMiddleware {
fileprivate let schedule: Schedule fileprivate let schedule: Schedule
/// Returns a schedule at the specific timing.
public func at(_ timing: Time) -> Schedule { public func at(_ timing: Time) -> Schedule {
return Schedule.make { () -> AnyIterator<Interval> in return Schedule.make { () -> AnyIterator<Interval> in
let iterator = self.schedule.dates.makeIterator() let iterator = self.schedule.dates.makeIterator()
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
@ -212,13 +341,29 @@ extension Schedule {
return AnyIterator { return AnyIterator {
previous = previous ?? Date() previous = previous ?? Date()
guard let date = iterator.next(), 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 } defer { previous = next }
return next.interval(since: previous) 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 { public func at(_ timing: String) -> Schedule {
guard let time = Time(timing: timing) else { guard let time = Time(timing: timing) else {
return Schedule.never return Schedule.never
@ -226,6 +371,7 @@ extension Schedule {
return at(time) return at(time)
} }
/// Returns a schedule at the specific timing.
public func at(_ timing: Int...) -> Schedule { public func at(_ timing: Int...) -> Schedule {
let hour = timing[0] let hour = timing[0]
let minute = timing.count > 1 ? timing[1] : 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 { public static func every(_ weekday: Weekday) -> EveryDateMiddleware {
let schedule = Schedule.make { () -> AnyIterator<Interval> in let schedule = Schedule.make { () -> AnyIterator<Interval> in
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
@ -257,6 +404,7 @@ extension Schedule {
return EveryDateMiddleware(schedule: schedule) return EveryDateMiddleware(schedule: schedule)
} }
/// Creates a schedule that invokes the job every specific weekdays.
public static func every(_ weekdays: Weekday...) -> EveryDateMiddleware { public static func every(_ weekdays: Weekday...) -> EveryDateMiddleware {
var schedule = every(weekdays[0]).schedule var schedule = every(weekdays[0]).schedule
if weekdays.count > 1 { if weekdays.count > 1 {
@ -267,6 +415,7 @@ extension Schedule {
return EveryDateMiddleware(schedule: 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 { public static func every(_ monthDay: MonthDay) -> EveryDateMiddleware {
let schedule = Schedule.make { () -> AnyIterator<Interval> in let schedule = Schedule.make { () -> AnyIterator<Interval> in
let calendar = Calendar.autoupdatingCurrent let calendar = Calendar.autoupdatingCurrent
@ -286,6 +435,7 @@ extension Schedule {
return EveryDateMiddleware(schedule: 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 { public static func every(_ mondays: MonthDay...) -> EveryDateMiddleware {
var schedule = every(mondays[0]).schedule var schedule = every(mondays[0]).schedule
if mondays.count > 1 { if mondays.count > 1 {

View File

@ -7,7 +7,7 @@
import Foundation import Foundation
class Atomic<Wrapped> { class AtomicBox<Wrapped> {
private var lock = NSLock() 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
}
}

View File

@ -12,10 +12,10 @@ final class DateTimeTests: XCTestCase {
func testInterval2DispatchInterval() { func testInterval2DispatchInterval() {
let i0 = 1.23.seconds 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 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() { func testIntervalConvertible() {
@ -42,7 +42,7 @@ final class DateTimeTests: XCTestCase {
} }
func testPeriodAnd() { 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.years, 1)
XCTAssertEqual(dateComponents.months, 2) XCTAssertEqual(dateComponents.months, 2)
XCTAssertEqual(dateComponents.days, 3) XCTAssertEqual(dateComponents.days, 3)

View File

@ -10,7 +10,6 @@ final class ScheduleTests: XCTestCase {
let s1 = Schedule.from(intervals) let s1 = Schedule.from(intervals)
XCTAssert(s1.makeIterator().isEqual(to: intervals, leeway: 0.001.seconds)) XCTAssert(s1.makeIterator().isEqual(to: intervals, leeway: 0.001.seconds))
let d0 = Date() + intervals[0] let d0 = Date() + intervals[0]
let d1 = d0 + intervals[1] let d1 = d0 + intervals[1]
let d2 = d1 + intervals[2] let d2 = d1 + intervals[2]
@ -74,17 +73,13 @@ final class ScheduleTests: XCTestCase {
func testMerge() { func testMerge() {
let intervals0: [Interval] = [1.second, 2.minutes, 1.hour] let intervals0: [Interval] = [1.second, 2.minutes, 1.hour]
let intervals1: [Interval] = [2.seconds, 1.minutes, 1.seconds] let intervals1: [Interval] = [2.seconds, 1.minutes, 1.seconds]
let scheudle0 = Schedule.from(intervals0).merge(Schedule.from(intervals1)) 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) 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)) XCTAssert(scheudle0.makeIterator().isEqual(to: scheudle1.makeIterator(), leeway: 0.001.seconds))
} }
func testEveryPeriod() { func testEveryPeriod() {
let s = Schedule.every(1.year).count(10)
let s = Schedule.every(.years(1)).count(10)
var date = Date() var date = Date()
for i in s.dates { for i in s.dates {
XCTAssertEqual(i.dateComponents.year!, date.dateComponents.year! + 1) XCTAssertEqual(i.dateComponents.year!, date.dateComponents.year! + 1)

View File

@ -34,8 +34,8 @@ extension Sequence where Element == Interval {
var i0 = self.makeIterator() var i0 = self.makeIterator()
var i1 = sequence.makeIterator() var i1 = sequence.makeIterator()
while let l = i0.next(), let r = i1.next() { while let l = i0.next(), let r = i1.next() {
let diff = Swift.max(l, r) - Swift.min(l, r) let diff = Interval.longest(l, r) - Interval.shortest(l, r)
if diff < leeway { continue } if diff.isShorter(than: leeway) { continue }
return false return false
} }
return i0.next() == i1.next() return i0.next() == i1.next()
@ -45,8 +45,8 @@ extension Sequence where Element == Interval {
extension Interval { extension Interval {
func isEqual(to interval: Interval, leeway: Interval) -> Bool { func isEqual(to interval: Interval, leeway: Interval) -> Bool {
let diff = Swift.max(self, interval) - Swift.min(self, interval) let diff = Interval.longest(self, interval) - Interval.shortest(self, interval)
return diff < leeway return diff.isShorter(than: leeway)
} }
} }