commit
0d16c26cdd
|
@ -1,24 +0,0 @@
|
||||||
<?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>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>1</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
|
@ -5,10 +5,12 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "Schedule",
|
name: "Schedule",
|
||||||
products: [
|
products: [
|
||||||
.library(name: "Schedule", targets: ["Schedule"])
|
.library(name: "Schedule", targets: ["Schedule"]),
|
||||||
|
.executable(name: "ScheduleDemo", targets: ["ScheduleDemo"])
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(name: "Schedule"),
|
.target(name: "Schedule"),
|
||||||
.testTarget(name: "ScheduleTests", dependencies: ["Schedule"])
|
.testTarget(name: "ScheduleTests", dependencies: ["Schedule"]),
|
||||||
|
.target(name: "ScheduleDemo", dependencies: ["Schedule"])
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
//: Playground - noun: a place where people can play
|
|
||||||
|
|
||||||
import PlaygroundSupport
|
|
||||||
import Schedule
|
|
||||||
|
|
||||||
PlaygroundPage.current.needsIndefiniteExecution = true
|
|
||||||
|
|
||||||
Plan.after(1.second).do {
|
|
||||||
print("1 second passed!")
|
|
||||||
}
|
|
||||||
|
|
||||||
Plan.after(1.minute, repeating: 0.5.seconds).do {
|
|
||||||
print("Ping!")
|
|
||||||
}
|
|
||||||
|
|
||||||
Plan.every("one minute and ten seconds").do {
|
|
||||||
print("One minute and ten seconds have elapsed!")
|
|
||||||
}
|
|
||||||
|
|
||||||
Plan.every(.monday, .tuesday, .wednesday, .thursday, .friday).at(6, 30).do {
|
|
||||||
print("Get up!")
|
|
||||||
}
|
|
||||||
|
|
||||||
Plan.every(.june(14)).at("9:30").do {
|
|
||||||
print("Happy birthday!")
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<playground version='5.0' target-platform='macos' display-mode='raw' last-migration='1000'>
|
|
||||||
<timeline fileName='timeline.xctimeline'/>
|
|
||||||
</playground>
|
|
|
@ -1,22 +1,18 @@
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = "Schedule"
|
s.name = "Schedule"
|
||||||
s.version = "1.0.0"
|
s.version = "2.0.0-beta.1"
|
||||||
s.summary = "A lightweight task scheduler for Swift."
|
s.license = { :type => "MIT" }
|
||||||
s.description = <<-DESC
|
|
||||||
Schedule is a missing lightweight task scheduler for Swift.
|
|
||||||
It allows you run timed tasks using an incredibly human-friendly syntax.
|
|
||||||
DESC
|
|
||||||
s.homepage = "https://github.com/jianstm/Schedule"
|
s.homepage = "https://github.com/jianstm/Schedule"
|
||||||
s.license = { :type => "MIT", :file => "LICENSE" }
|
|
||||||
s.author = { "Quentin Jin" => "jianstm@gmail.com" }
|
s.author = { "Quentin Jin" => "jianstm@gmail.com" }
|
||||||
s.source = { :git => "https://github.com/jianstm/Schedule.git",
|
s.summary = "Lightweight timing task scheduler"
|
||||||
:tag => "#{s.version}" }
|
|
||||||
|
s.source = { :git => "https://github.com/jianstm/Schedule.git", :tag => "#{s.version}" }
|
||||||
s.source_files = "Sources/Schedule/*.swift"
|
s.source_files = "Sources/Schedule/*.swift"
|
||||||
s.requires_arc = true
|
|
||||||
s.swift_version = "4.2"
|
s.swift_version = "4.2"
|
||||||
|
|
||||||
s.ios.deployment_target = "8.0"
|
s.ios.deployment_target = "10.0"
|
||||||
s.osx.deployment_target = "10.10"
|
s.osx.deployment_target = "10.12"
|
||||||
s.tvos.deployment_target = "9.0"
|
s.tvos.deployment_target = "10.0"
|
||||||
s.watchos.deployment_target = "2.0"
|
s.watchos.deployment_target = "3.0"
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>BNDL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string></string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -15,13 +15,11 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>FMWK</string>
|
<string>FMWK</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0.0</string>
|
<string>2.0.0-beta.1</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
|
||||||
<string>Copyright © 2018 Quentin Jin. All rights reserved.</string>
|
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
</dict>
|
</dict>
|
File diff suppressed because it is too large
Load Diff
|
@ -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>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1000"
|
LastUpgradeVersion = "9999"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
@ -14,9 +14,23 @@
|
||||||
buildForAnalyzing = "YES">
|
buildForAnalyzing = "YES">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "52D6D97B1BEFF229002C0205"
|
BlueprintIdentifier = "Schedule::Schedule"
|
||||||
BuildableName = "Schedule.framework"
|
BuildableName = "Schedule.framework"
|
||||||
BlueprintName = "Schedule-iOS"
|
BlueprintName = "Schedule"
|
||||||
|
ReferencedContainer = "container:Schedule.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "Schedule::ScheduleDemo"
|
||||||
|
BuildableName = "ScheduleDemo"
|
||||||
|
BlueprintName = "ScheduleDemo"
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
ReferencedContainer = "container:Schedule.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
|
@ -26,30 +40,19 @@
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
codeCoverageEnabled = "YES"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<Testables>
|
<Testables>
|
||||||
<TestableReference
|
<TestableReference
|
||||||
skipped = "NO"
|
skipped = "NO">
|
||||||
parallelizable = "YES">
|
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "52D6D9851BEFF229002C0205"
|
BlueprintIdentifier = "Schedule::ScheduleTests"
|
||||||
BuildableName = "Schedule-iOS Tests.xctest"
|
BuildableName = "ScheduleTests.xctest"
|
||||||
BlueprintName = "Schedule-iOS Tests"
|
BlueprintName = "ScheduleTests"
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
ReferencedContainer = "container:Schedule.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</TestableReference>
|
</TestableReference>
|
||||||
</Testables>
|
</Testables>
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "52D6D97B1BEFF229002C0205"
|
|
||||||
BuildableName = "Schedule.framework"
|
|
||||||
BlueprintName = "Schedule-iOS"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
<AdditionalOptions>
|
<AdditionalOptions>
|
||||||
</AdditionalOptions>
|
</AdditionalOptions>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
|
@ -66,9 +69,9 @@
|
||||||
<MacroExpansion>
|
<MacroExpansion>
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "52D6D97B1BEFF229002C0205"
|
BlueprintIdentifier = "Schedule::Schedule"
|
||||||
BuildableName = "Schedule.framework"
|
BuildableName = "Schedule.framework"
|
||||||
BlueprintName = "Schedule-iOS"
|
BlueprintName = "Schedule"
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
ReferencedContainer = "container:Schedule.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</MacroExpansion>
|
</MacroExpansion>
|
||||||
|
@ -81,15 +84,6 @@
|
||||||
savedToolIdentifier = ""
|
savedToolIdentifier = ""
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
debugDocumentVersioning = "YES">
|
debugDocumentVersioning = "YES">
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "52D6D97B1BEFF229002C0205"
|
|
||||||
BuildableName = "Schedule.framework"
|
|
||||||
BlueprintName = "Schedule-iOS"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
<AnalyzeAction
|
<AnalyzeAction
|
||||||
buildConfiguration = "Debug">
|
buildConfiguration = "Debug">
|
|
@ -1,101 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Scheme
|
|
||||||
LastUpgradeVersion = "1000"
|
|
||||||
version = "1.3">
|
|
||||||
<BuildAction
|
|
||||||
parallelizeBuildables = "YES"
|
|
||||||
buildImplicitDependencies = "YES">
|
|
||||||
<BuildActionEntries>
|
|
||||||
<BuildActionEntry
|
|
||||||
buildForTesting = "YES"
|
|
||||||
buildForRunning = "YES"
|
|
||||||
buildForProfiling = "YES"
|
|
||||||
buildForArchiving = "YES"
|
|
||||||
buildForAnalyzing = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "52D6DA0E1BF000BD002C0205"
|
|
||||||
BuildableName = "Schedule.framework"
|
|
||||||
BlueprintName = "Schedule-macOS"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildActionEntry>
|
|
||||||
</BuildActionEntries>
|
|
||||||
</BuildAction>
|
|
||||||
<TestAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
codeCoverageEnabled = "YES"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
||||||
<Testables>
|
|
||||||
<TestableReference
|
|
||||||
skipped = "NO"
|
|
||||||
parallelizable = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "DD7502791C68FCFC006590AF"
|
|
||||||
BuildableName = "Schedule-macOS Tests.xctest"
|
|
||||||
BlueprintName = "Schedule-macOS Tests"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</TestableReference>
|
|
||||||
</Testables>
|
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "52D6DA0E1BF000BD002C0205"
|
|
||||||
BuildableName = "Schedule.framework"
|
|
||||||
BlueprintName = "Schedule-macOS"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
<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 = "52D6DA0E1BF000BD002C0205"
|
|
||||||
BuildableName = "Schedule.framework"
|
|
||||||
BlueprintName = "Schedule-macOS"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
<AdditionalOptions>
|
|
||||||
</AdditionalOptions>
|
|
||||||
</LaunchAction>
|
|
||||||
<ProfileAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
||||||
savedToolIdentifier = ""
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
debugDocumentVersioning = "YES">
|
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "52D6DA0E1BF000BD002C0205"
|
|
||||||
BuildableName = "Schedule.framework"
|
|
||||||
BlueprintName = "Schedule-macOS"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
</ProfileAction>
|
|
||||||
<AnalyzeAction
|
|
||||||
buildConfiguration = "Debug">
|
|
||||||
</AnalyzeAction>
|
|
||||||
<ArchiveAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
revealArchiveInOrganizer = "YES">
|
|
||||||
</ArchiveAction>
|
|
||||||
</Scheme>
|
|
|
@ -1,101 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<Scheme
|
|
||||||
LastUpgradeVersion = "1000"
|
|
||||||
version = "1.3">
|
|
||||||
<BuildAction
|
|
||||||
parallelizeBuildables = "YES"
|
|
||||||
buildImplicitDependencies = "YES">
|
|
||||||
<BuildActionEntries>
|
|
||||||
<BuildActionEntry
|
|
||||||
buildForTesting = "YES"
|
|
||||||
buildForRunning = "YES"
|
|
||||||
buildForProfiling = "YES"
|
|
||||||
buildForArchiving = "YES"
|
|
||||||
buildForAnalyzing = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "52D6D9EF1BEFFFBE002C0205"
|
|
||||||
BuildableName = "Schedule.framework"
|
|
||||||
BlueprintName = "Schedule-tvOS"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</BuildActionEntry>
|
|
||||||
</BuildActionEntries>
|
|
||||||
</BuildAction>
|
|
||||||
<TestAction
|
|
||||||
buildConfiguration = "Debug"
|
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
||||||
codeCoverageEnabled = "YES"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
||||||
<Testables>
|
|
||||||
<TestableReference
|
|
||||||
skipped = "NO"
|
|
||||||
parallelizable = "YES">
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "DD75028C1C690C7A006590AF"
|
|
||||||
BuildableName = "Schedule-tvOS Tests.xctest"
|
|
||||||
BlueprintName = "Schedule-tvOS Tests"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</TestableReference>
|
|
||||||
</Testables>
|
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "52D6D9EF1BEFFFBE002C0205"
|
|
||||||
BuildableName = "Schedule.framework"
|
|
||||||
BlueprintName = "Schedule-tvOS"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
<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 = "52D6D9EF1BEFFFBE002C0205"
|
|
||||||
BuildableName = "Schedule.framework"
|
|
||||||
BlueprintName = "Schedule-tvOS"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
<AdditionalOptions>
|
|
||||||
</AdditionalOptions>
|
|
||||||
</LaunchAction>
|
|
||||||
<ProfileAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
||||||
savedToolIdentifier = ""
|
|
||||||
useCustomWorkingDirectory = "NO"
|
|
||||||
debugDocumentVersioning = "YES">
|
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "52D6D9EF1BEFFFBE002C0205"
|
|
||||||
BuildableName = "Schedule.framework"
|
|
||||||
BlueprintName = "Schedule-tvOS"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
</ProfileAction>
|
|
||||||
<AnalyzeAction
|
|
||||||
buildConfiguration = "Debug">
|
|
||||||
</AnalyzeAction>
|
|
||||||
<ArchiveAction
|
|
||||||
buildConfiguration = "Release"
|
|
||||||
revealArchiveInOrganizer = "YES">
|
|
||||||
</ArchiveAction>
|
|
||||||
</Scheme>
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1000"
|
LastUpgradeVersion = "9999"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
@ -14,9 +14,9 @@
|
||||||
buildForAnalyzing = "YES">
|
buildForAnalyzing = "YES">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "52D6D9E11BEFFF6E002C0205"
|
BlueprintIdentifier = "Schedule::ScheduleDemo"
|
||||||
BuildableName = "Schedule.framework"
|
BuildableName = "ScheduleDemo"
|
||||||
BlueprintName = "Schedule-watchOS"
|
BlueprintName = "ScheduleDemo"
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
ReferencedContainer = "container:Schedule.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</BuildActionEntry>
|
</BuildActionEntry>
|
||||||
|
@ -26,9 +26,18 @@
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
codeCoverageEnabled = "YES"
|
|
||||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
<Testables>
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "Schedule::ScheduleTests"
|
||||||
|
BuildableName = "ScheduleTests.xctest"
|
||||||
|
BlueprintName = "ScheduleTests"
|
||||||
|
ReferencedContainer = "container:Schedule.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
</Testables>
|
</Testables>
|
||||||
<AdditionalOptions>
|
<AdditionalOptions>
|
||||||
</AdditionalOptions>
|
</AdditionalOptions>
|
||||||
|
@ -43,15 +52,16 @@
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES">
|
||||||
<MacroExpansion>
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
BuildableIdentifier = "primary"
|
BuildableIdentifier = "primary"
|
||||||
BlueprintIdentifier = "52D6D9E11BEFFF6E002C0205"
|
BlueprintIdentifier = "Schedule::ScheduleDemo"
|
||||||
BuildableName = "Schedule.framework"
|
BuildableName = "ScheduleDemo"
|
||||||
BlueprintName = "Schedule-watchOS"
|
BlueprintName = "ScheduleDemo"
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
ReferencedContainer = "container:Schedule.xcodeproj">
|
||||||
</BuildableReference>
|
</BuildableReference>
|
||||||
</MacroExpansion>
|
</BuildableProductRunnable>
|
||||||
<AdditionalOptions>
|
<AdditionalOptions>
|
||||||
</AdditionalOptions>
|
</AdditionalOptions>
|
||||||
</LaunchAction>
|
</LaunchAction>
|
||||||
|
@ -61,15 +71,6 @@
|
||||||
savedToolIdentifier = ""
|
savedToolIdentifier = ""
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
debugDocumentVersioning = "YES">
|
debugDocumentVersioning = "YES">
|
||||||
<MacroExpansion>
|
|
||||||
<BuildableReference
|
|
||||||
BuildableIdentifier = "primary"
|
|
||||||
BlueprintIdentifier = "52D6D9E11BEFFF6E002C0205"
|
|
||||||
BuildableName = "Schedule.framework"
|
|
||||||
BlueprintName = "Schedule-watchOS"
|
|
||||||
ReferencedContainer = "container:Schedule.xcodeproj">
|
|
||||||
</BuildableReference>
|
|
||||||
</MacroExpansion>
|
|
||||||
</ProfileAction>
|
</ProfileAction>
|
||||||
<AnalyzeAction
|
<AnalyzeAction
|
||||||
buildConfiguration = "Debug">
|
buildConfiguration = "Debug">
|
|
@ -1,21 +1,30 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
/// A value box that can read and write the underlying value atomically.
|
||||||
final class Atomic<T> {
|
final class Atomic<T> {
|
||||||
|
|
||||||
private var _value: T
|
private var v: T
|
||||||
private let _lock = NSLock()
|
private let lock = NSLock()
|
||||||
|
|
||||||
init(_ value: T) {
|
init(_ value: T) {
|
||||||
self._value = value
|
self.v = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a snapshot of the value nonatomically.
|
||||||
|
@inline(__always)
|
||||||
|
func snapshot() -> T {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the value atomically.
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
func read<U>(_ body: (T) -> U) -> U {
|
func read<U>(_ body: (T) -> U) -> U {
|
||||||
return _lock.withLock { body(_value) }
|
return lock.withLock { body(v) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Writes the value atomically.
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
func write<U>(_ body: (inout T) -> U) -> U {
|
func write<U>(_ body: (inout T) -> U) -> U {
|
||||||
return _lock.withLock { body(&_value) }
|
return lock.withLock { body(&v) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct BucketKey {
|
|
||||||
|
|
||||||
private let _underlying: UInt64
|
|
||||||
|
|
||||||
init(underlying: UInt64) {
|
|
||||||
self._underlying = underlying
|
|
||||||
}
|
|
||||||
|
|
||||||
func increased() -> BucketKey {
|
|
||||||
let i = _underlying &+ 1
|
|
||||||
return BucketKey(underlying: i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension BucketKey: Equatable {
|
|
||||||
|
|
||||||
static func == (lhs: BucketKey, rhs: BucketKey) -> Bool {
|
|
||||||
return lhs._underlying == rhs._underlying
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Bucket<Element> {
|
|
||||||
|
|
||||||
private typealias Entry = (key: BucketKey, element: Element)
|
|
||||||
|
|
||||||
private var key = BucketKey(underlying: 0)
|
|
||||||
|
|
||||||
private var entries: [Entry] = []
|
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
mutating func add(_ new: Element) -> BucketKey {
|
|
||||||
defer { key = key.increased() }
|
|
||||||
|
|
||||||
let entry = (key: key, element: new)
|
|
||||||
entries.append(entry)
|
|
||||||
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
mutating func removeElement(for key: BucketKey) -> Element? {
|
|
||||||
if let index = entries.firstIndex(where: { $0.key == key }) {
|
|
||||||
return entries.remove(at: index).element
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func element(for key: BucketKey) -> Element? {
|
|
||||||
if let entry = entries.first(where: { $0.key == key }) {
|
|
||||||
return entry.element
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
mutating func removeAll() {
|
|
||||||
entries.removeAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
var count: Int {
|
|
||||||
return entries.count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Bucket: Sequence {
|
|
||||||
|
|
||||||
func makeIterator() -> AnyIterator<Element> {
|
|
||||||
var iterator = entries.makeIterator()
|
|
||||||
return AnyIterator<Element> {
|
|
||||||
return iterator.next()?.element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct CabinetKey: Equatable {
|
||||||
|
|
||||||
|
private let i: UInt64
|
||||||
|
|
||||||
|
init(underlying: UInt64) {
|
||||||
|
self.i = underlying
|
||||||
|
}
|
||||||
|
|
||||||
|
func increased() -> CabinetKey {
|
||||||
|
return CabinetKey(underlying: i &+ 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func == (lhs: CabinetKey, rhs: CabinetKey) -> Bool {
|
||||||
|
return lhs.i == rhs.i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cabinet<Element> {
|
||||||
|
|
||||||
|
private typealias Entry = (key: CabinetKey, element: Element)
|
||||||
|
|
||||||
|
private var key = CabinetKey(underlying: 0)
|
||||||
|
private var entries: [Entry] = []
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
mutating func append(_ new: Element) -> CabinetKey {
|
||||||
|
defer { key = key.increased() }
|
||||||
|
|
||||||
|
let entry = (key: key, element: new)
|
||||||
|
entries.append(entry)
|
||||||
|
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(_ key: CabinetKey) -> Element? {
|
||||||
|
if let entry = entries.first(where: { $0.key == key }) {
|
||||||
|
return entry.element
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
mutating func delete(_ key: CabinetKey) -> Element? {
|
||||||
|
if let i = entries.firstIndex(where: { $0.key == key }) {
|
||||||
|
return entries.remove(at: i).element
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func clear() {
|
||||||
|
entries.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
var count: Int {
|
||||||
|
return entries.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Cabinet: Sequence {
|
||||||
|
|
||||||
|
func makeIterator() -> AnyIterator<Element> {
|
||||||
|
var iterator = entries.makeIterator()
|
||||||
|
return AnyIterator<Element> {
|
||||||
|
return iterator.next()?.element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,34 +6,36 @@ private var deinitObserverKey: Void = ()
|
||||||
|
|
||||||
class DeinitObserver {
|
class DeinitObserver {
|
||||||
|
|
||||||
private(set) weak var observed: AnyObject?
|
private(set) weak var object: AnyObject?
|
||||||
|
|
||||||
private var block: (() -> Void)?
|
private var action: (() -> Void)?
|
||||||
|
|
||||||
private init(_ block: @escaping () -> Void) {
|
private init(_ action: @escaping () -> Void) {
|
||||||
self.block = block
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
static func observe(_ observed: AnyObject, onDeinit block: @escaping () -> Void) -> DeinitObserver {
|
static func observe(
|
||||||
let observer = DeinitObserver(block)
|
_ object: AnyObject,
|
||||||
observer.observed = observed
|
onDeinit action: @escaping () -> Void
|
||||||
|
) -> DeinitObserver {
|
||||||
|
let observer = DeinitObserver(action)
|
||||||
|
observer.object = object
|
||||||
|
|
||||||
objc_setAssociatedObject(observed, &deinitObserverKey, observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
objc_setAssociatedObject(object, &deinitObserverKey, observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||||
|
|
||||||
return observer
|
return observer
|
||||||
}
|
}
|
||||||
|
|
||||||
func cancel() {
|
func invalidate() {
|
||||||
block = nil
|
action = nil
|
||||||
|
if let o = object {
|
||||||
if let o = observed {
|
|
||||||
objc_setAssociatedObject(o, &deinitObserverKey, nil, .OBJC_ASSOCIATION_ASSIGN)
|
objc_setAssociatedObject(o, &deinitObserverKey, nil, .OBJC_ASSOCIATION_ASSIGN)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
block?()
|
action?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
//
|
||||||
|
// Deprecated.swift
|
||||||
|
// Schedule
|
||||||
|
//
|
||||||
|
// Created by Quentin Jin on 2019/3/4.
|
||||||
|
// Copyright © 2019 Schedule. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Monthday {
|
||||||
|
|
||||||
|
/// A Boolean value indicating whether today is the monthday.
|
||||||
|
@available(*, deprecated, message: "Use date.is(_:Monthday)")
|
||||||
|
public var isToday: Bool {
|
||||||
|
let components = toDateComponents()
|
||||||
|
|
||||||
|
let m = Calendar.gregorian.component(.month, from: Date())
|
||||||
|
let d = Calendar.gregorian.component(.day, from: Date())
|
||||||
|
|
||||||
|
return m == components.month && d == components.day
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Weekday {
|
||||||
|
|
||||||
|
/// A Boolean value indicating whether today is the weekday.
|
||||||
|
@available(*, deprecated, message: "Use date.is(_:Weekday)")
|
||||||
|
public var isToday: Bool {
|
||||||
|
return Calendar.gregorian.component(.weekday, from: Date()) == rawValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Time {
|
||||||
|
|
||||||
|
/// The interval between this time and zero o'clock.
|
||||||
|
@available(*, deprecated, renamed: "intervalSinceStartOfDay")
|
||||||
|
public var intervalSinceZeroClock: Interval {
|
||||||
|
return hour.hours + minute.minutes + second.seconds + nanosecond.nanoseconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Plan {
|
||||||
|
|
||||||
|
/// Creates a plan from an interval sequence.
|
||||||
|
/// The task will be executed after each interval in the sequence.
|
||||||
|
@available(*, deprecated, message: "Use Plan.of")
|
||||||
|
public static func from<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Interval {
|
||||||
|
return Plan.of(sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a plan from a date sequence.
|
||||||
|
/// The task will be executed at each date in the sequence.
|
||||||
|
/// - Note: Returns `Plan.never` if given no parameters.
|
||||||
|
@available(*, deprecated, message: "Use Plan.of")
|
||||||
|
public static func from<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Date {
|
||||||
|
return Plan.make(sequence.makeIterator)
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,21 +22,21 @@ extension Int {
|
||||||
|
|
||||||
extension Calendar {
|
extension Calendar {
|
||||||
|
|
||||||
static var standard: Calendar {
|
static let gregorian: Calendar = {
|
||||||
var calendar = Calendar(identifier: .gregorian)
|
var cal = Calendar(identifier: .gregorian)
|
||||||
calendar.locale = Locale(identifier: "en_US_POSIX")
|
cal.locale = Locale(identifier: "en_US_POSIX")
|
||||||
return calendar
|
return cal
|
||||||
}
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Date {
|
extension Date {
|
||||||
|
|
||||||
var start: Date {
|
var startOfToday: Date {
|
||||||
return Calendar.standard.startOfDay(for: self)
|
return Calendar.gregorian.startOfDay(for: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NSLock {
|
extension NSLocking {
|
||||||
|
|
||||||
@inline(__always)
|
@inline(__always)
|
||||||
func withLock<T>(_ body: () throws -> T) rethrows -> T {
|
func withLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||||
|
|
|
@ -62,6 +62,8 @@ extension Interval: Hashable {
|
||||||
extension Interval: CustomStringConvertible {
|
extension Interval: CustomStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this interval.
|
/// A textual representation of this interval.
|
||||||
|
///
|
||||||
|
/// Interval: 1000 nanoseconds
|
||||||
public var description: String {
|
public var description: String {
|
||||||
return "Interval: \(nanoseconds.clampedToInt()) nanoseconds"
|
return "Interval: \(nanoseconds.clampedToInt()) nanoseconds"
|
||||||
}
|
}
|
||||||
|
@ -70,13 +72,15 @@ extension Interval: CustomStringConvertible {
|
||||||
extension Interval: CustomDebugStringConvertible {
|
extension Interval: CustomDebugStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this interval for debugging.
|
/// A textual representation of this interval for debugging.
|
||||||
|
///
|
||||||
|
/// Interval: 1000 nanoseconds
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
return description
|
return description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Comparing
|
// MARK: - Comparing
|
||||||
extension Interval {
|
extension Interval: Comparable {
|
||||||
|
|
||||||
/// Compares two intervals.
|
/// Compares two intervals.
|
||||||
///
|
///
|
||||||
|
@ -86,6 +90,14 @@ extension Interval {
|
||||||
return now.adding(self).compare(now.adding(other))
|
return now.adding(self).compare(now.adding(other))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a boolean value indicating whether the first interval is
|
||||||
|
/// less than the second interval.
|
||||||
|
///
|
||||||
|
/// A negative interval is always less than a positive interval.
|
||||||
|
public static func < (lhs: Interval, rhs: Interval) -> Bool {
|
||||||
|
return lhs.compare(rhs) == .orderedAscending
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a boolean value indicating whether this interval is longer
|
/// Returns a boolean value indicating whether this interval is longer
|
||||||
/// than the given value.
|
/// than the given value.
|
||||||
public func isLonger(than other: Interval) -> Bool {
|
public func isLonger(than other: Interval) -> Bool {
|
||||||
|
@ -101,41 +113,30 @@ extension Interval {
|
||||||
/// Returns the longest interval of the given values.
|
/// Returns the longest interval of the given values.
|
||||||
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given no parameters.
|
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given no parameters.
|
||||||
public static func longest(_ intervals: Interval...) -> Interval {
|
public static func longest(_ intervals: Interval...) -> Interval {
|
||||||
return Interval.longest(intervals)
|
return longest(intervals)!
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the longest interval of the given values.
|
/// Returns the longest interval of the given values.
|
||||||
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given an empty array.
|
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given an empty array.
|
||||||
public static func longest(_ intervals: [Interval]) -> Interval {
|
public static func longest(_ intervals: [Interval]) -> Interval? {
|
||||||
guard !intervals.isEmpty else { return .init(nanoseconds: 0) }
|
guard !intervals.isEmpty else { return nil }
|
||||||
return intervals.sorted(by: { $0.magnitude > $1.magnitude })[0]
|
return intervals.sorted(by: { $0.magnitude > $1.magnitude })[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the shortest interval of the given values.
|
/// Returns the shortest interval of the given values.
|
||||||
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given no parameters.
|
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given no parameters.
|
||||||
public static func shortest(_ intervals: Interval...) -> Interval {
|
public static func shortest(_ intervals: Interval...) -> Interval {
|
||||||
return Interval.shortest(intervals)
|
return shortest(intervals)!
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the shortest interval of the given values.
|
/// Returns the shortest interval of the given values.
|
||||||
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given an empty array.
|
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given an empty array.
|
||||||
public static func shortest(_ intervals: [Interval]) -> Interval {
|
public static func shortest(_ intervals: [Interval]) -> Interval? {
|
||||||
guard !intervals.isEmpty else { return .init(nanoseconds: 0) }
|
guard !intervals.isEmpty else { return nil }
|
||||||
return intervals.sorted(by: { $0.magnitude < $1.magnitude })[0]
|
return intervals.sorted(by: { $0.magnitude < $1.magnitude })[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Interval: Comparable {
|
|
||||||
|
|
||||||
/// Returns a Boolean value indicating whether the first interval is
|
|
||||||
/// less than the second interval.
|
|
||||||
///
|
|
||||||
/// A negative interval is always less than a positive interval.
|
|
||||||
public static func < (lhs: Interval, rhs: Interval) -> Bool {
|
|
||||||
return lhs.compare(rhs) == .orderedAscending
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Adding & Subtracting
|
// MARK: - Adding & Subtracting
|
||||||
extension Interval {
|
extension Interval {
|
||||||
/// Returns a new interval by multipling this interval by the given number.
|
/// Returns a new interval by multipling this interval by the given number.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// `Monthday` represents a day of a month without years.
|
/// `Monthday` represents a day of a month.
|
||||||
public enum Monthday {
|
public enum Monthday {
|
||||||
|
|
||||||
case january(Int)
|
case january(Int)
|
||||||
|
@ -27,13 +27,6 @@ public enum Monthday {
|
||||||
|
|
||||||
case december(Int)
|
case december(Int)
|
||||||
|
|
||||||
/// A Boolean value indicating whether today is the weekday.
|
|
||||||
public var isToday: Bool {
|
|
||||||
let lhs = Calendar.standard.dateComponents(in: TimeZone.current, from: Date())
|
|
||||||
let rhs = toDateComponents()
|
|
||||||
return lhs.month == rhs.month && lhs.day == rhs.day
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a dateComponenets of the monthday, using gregorian calender and
|
/// Returns a dateComponenets of the monthday, using gregorian calender and
|
||||||
/// current time zone.
|
/// current time zone.
|
||||||
public func toDateComponents() -> DateComponents {
|
public func toDateComponents() -> DateComponents {
|
||||||
|
@ -52,28 +45,53 @@ public enum Monthday {
|
||||||
case .november(let n): month = 11; day = n
|
case .november(let n): month = 11; day = n
|
||||||
case .december(let n): month = 12; day = n
|
case .december(let n): month = 12; day = n
|
||||||
}
|
}
|
||||||
return DateComponents(calendar: Calendar.standard,
|
return DateComponents(
|
||||||
|
calendar: Calendar.gregorian,
|
||||||
timeZone: TimeZone.current,
|
timeZone: TimeZone.current,
|
||||||
month: month,
|
month: month,
|
||||||
day: day)
|
day: day
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Date {
|
||||||
|
|
||||||
|
/// Returns a boolean value indicating whether today is the monthday.
|
||||||
|
public func `is`(_ monthday: Monthday) -> Bool {
|
||||||
|
let components = monthday.toDateComponents()
|
||||||
|
|
||||||
|
let m = Calendar.gregorian.component(.month, from: self)
|
||||||
|
let d = Calendar.gregorian.component(.day, from: self)
|
||||||
|
return m == components.month && d == components.day
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Monthday: CustomStringConvertible {
|
extension Monthday: CustomStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this monthday.
|
/// A textual representation of this monthday.
|
||||||
|
///
|
||||||
|
/// "Monthday: May 1st"
|
||||||
public var description: String {
|
public var description: String {
|
||||||
let month = toDateComponents().month!
|
let components = toDateComponents()
|
||||||
let day = toDateComponents().day!
|
|
||||||
|
|
||||||
let monthDesc = Calendar.standard.monthSymbols[month - 1]
|
let m = components.month!
|
||||||
return "Monthday: \(monthDesc) \(day)"
|
let d = components.day!
|
||||||
|
|
||||||
|
let ms = Calendar.gregorian.monthSymbols[m - 1]
|
||||||
|
|
||||||
|
let fmt = NumberFormatter()
|
||||||
|
fmt.numberStyle = .ordinal
|
||||||
|
let ds = fmt.string(from: NSNumber(value: d))!
|
||||||
|
|
||||||
|
return "Monthday: \(ms) \(ds)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Monthday: CustomDebugStringConvertible {
|
extension Monthday: CustomDebugStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this monthday for debugging.
|
/// A textual representation of this monthday for debugging.
|
||||||
|
///
|
||||||
|
/// "Monthday: May 1st"
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
return description
|
return description
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,9 +102,11 @@ public struct Period {
|
||||||
|
|
||||||
/// Returns a new period by adding an interval to this period.
|
/// Returns a new period by adding an interval to this period.
|
||||||
public func adding(_ interval: Interval) -> Period {
|
public func adding(_ interval: Interval) -> Period {
|
||||||
return Period(years: years, months: months, days: days,
|
return Period(
|
||||||
|
years: years, months: months, days: days,
|
||||||
hours: hours, minutes: minutes, seconds: seconds,
|
hours: hours, minutes: minutes, seconds: seconds,
|
||||||
nanoseconds: nanoseconds.clampedAdding(interval.nanoseconds.clampedToInt())).tidied(to: .day)
|
nanoseconds: nanoseconds.clampedAdding(interval.nanoseconds.clampedToInt())
|
||||||
|
).tidied(to: .day)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds two periods and produces their sum.
|
/// Adds two periods and produces their sum.
|
||||||
|
@ -166,7 +168,7 @@ extension Date {
|
||||||
|
|
||||||
/// Returns a new date by adding a period to this date.
|
/// Returns a new date by adding a period to this date.
|
||||||
public func adding(_ period: Period) -> Date {
|
public func adding(_ period: Period) -> Date {
|
||||||
return Calendar.standard.date(byAdding: period.toDateComponents(), to: self) ?? .distantFuture
|
return Calendar.gregorian.date(byAdding: period.toDateComponents(), to: self) ?? .distantFuture
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a date with a period added to it.
|
/// Returns a date with a period added to it.
|
||||||
|
@ -197,6 +199,8 @@ extension Int {
|
||||||
extension Period: CustomStringConvertible {
|
extension Period: CustomStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this period.
|
/// A textual representation of this period.
|
||||||
|
///
|
||||||
|
/// Period: 1 year(s) 2 month(s) 3 day(s)
|
||||||
public var description: String {
|
public var description: String {
|
||||||
let period = tidied(to: .day)
|
let period = tidied(to: .day)
|
||||||
var desc = "Period:"
|
var desc = "Period:"
|
||||||
|
@ -214,6 +218,8 @@ extension Period: CustomStringConvertible {
|
||||||
extension Period: CustomDebugStringConvertible {
|
extension Period: CustomDebugStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this period for debugging.
|
/// A textual representation of this period for debugging.
|
||||||
|
///
|
||||||
|
/// Period: 1 year(s) 2 month(s) 3 day(s)
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
return description
|
return description
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,14 @@ import Foundation
|
||||||
/// `Plan` is `Interval` based.
|
/// `Plan` is `Interval` based.
|
||||||
public struct Plan {
|
public struct Plan {
|
||||||
|
|
||||||
private var sequence: AnySequence<Interval>
|
private var iSeq: AnySequence<Interval>
|
||||||
|
|
||||||
private init<S>(_ sequence: S) where S: Sequence, S.Element == Interval {
|
private init<S>(_ sequence: S) where S: Sequence, S.Element == Interval {
|
||||||
self.sequence = AnySequence(sequence)
|
iSeq = AnySequence(sequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeIterator() -> AnyIterator<Interval> {
|
func makeIterator() -> AnyIterator<Interval> {
|
||||||
return sequence.makeIterator()
|
return iSeq.makeIterator()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Schedules a task with this plan.
|
/// Schedules a task with this plan.
|
||||||
|
@ -67,12 +68,6 @@ extension Plan {
|
||||||
return Plan(AnySequence(makeUnderlyingIterator))
|
return Plan(AnySequence(makeUnderlyingIterator))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan from an interval sequence.
|
|
||||||
/// The task will be executed after each interval in the sequence.
|
|
||||||
public static func from<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Interval {
|
|
||||||
return Plan(sequence)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a plan from a list of intervals.
|
/// Creates a plan from a list of intervals.
|
||||||
/// The task will be executed after each interval in the array.
|
/// The task will be executed after each interval in the array.
|
||||||
/// - Note: Returns `Plan.never` if given no parameters.
|
/// - Note: Returns `Plan.never` if given no parameters.
|
||||||
|
@ -83,8 +78,7 @@ extension Plan {
|
||||||
/// Creates a plan from a list of intervals.
|
/// Creates a plan from a list of intervals.
|
||||||
/// The task will be executed after each interval in the array.
|
/// The task will be executed after each interval in the array.
|
||||||
/// - Note: Returns `Plan.never` if given an empty array.
|
/// - Note: Returns `Plan.never` if given an empty array.
|
||||||
public static func of(_ intervals: [Interval]) -> Plan {
|
public static func of<S>(_ intervals: S) -> Plan where S: Sequence, S.Element == Interval {
|
||||||
guard !intervals.isEmpty else { return .never }
|
|
||||||
return Plan(intervals)
|
return Plan(intervals)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,15 +122,8 @@ extension Plan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a plan from a date sequence.
|
|
||||||
/// The task will be executed at each date in the sequence.
|
|
||||||
public static func from<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Date {
|
|
||||||
return Plan.make(sequence.makeIterator)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a plan from a list of dates.
|
/// Creates a plan from a list of dates.
|
||||||
/// The task will be executed at each date in the array.
|
/// The task will be executed at each date in the array.
|
||||||
/// - Note: Returns `Plan.never` if given no parameters.
|
|
||||||
public static func of(_ dates: Date...) -> Plan {
|
public static func of(_ dates: Date...) -> Plan {
|
||||||
return Plan.of(dates)
|
return Plan.of(dates)
|
||||||
}
|
}
|
||||||
|
@ -144,9 +131,8 @@ extension Plan {
|
||||||
/// Creates a plan from a list of dates.
|
/// Creates a plan from a list of dates.
|
||||||
/// The task will be executed at each date in the array.
|
/// The task will be executed at each date in the array.
|
||||||
/// - Note: Returns `Plan.never` if given no parameters.
|
/// - Note: Returns `Plan.never` if given no parameters.
|
||||||
public static func of(_ dates: [Date]) -> Plan {
|
public static func of<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Date {
|
||||||
guard !dates.isEmpty else { return .never }
|
return Plan.make(sequence.makeIterator)
|
||||||
return Plan.from(dates)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A dates sequence corresponding to this plan.
|
/// A dates sequence corresponding to this plan.
|
||||||
|
@ -308,7 +294,7 @@ extension Plan {
|
||||||
/// Creates a plan that executes the task every period.
|
/// Creates a plan that executes the task every period.
|
||||||
public static func every(_ period: Period) -> Plan {
|
public static func every(_ period: Period) -> Plan {
|
||||||
return Plan.make { () -> AnyIterator<Interval> in
|
return Plan.make { () -> AnyIterator<Interval> in
|
||||||
let calendar = Calendar.standard
|
let calendar = Calendar.gregorian
|
||||||
var last: Date!
|
var last: Date!
|
||||||
return AnyIterator {
|
return AnyIterator {
|
||||||
last = last ?? Date()
|
last = last ?? Date()
|
||||||
|
@ -347,7 +333,7 @@ extension Plan {
|
||||||
public func at(_ time: Time) -> Plan {
|
public func at(_ time: Time) -> Plan {
|
||||||
guard !self.plan.isNever() else { return .never }
|
guard !self.plan.isNever() else { return .never }
|
||||||
|
|
||||||
var interval = time.intervalSinceZeroClock
|
var interval = time.intervalSinceStartOfDay
|
||||||
return Plan.make { () -> AnyIterator<Interval> in
|
return Plan.make { () -> AnyIterator<Interval> in
|
||||||
let it = self.plan.makeIterator()
|
let it = self.plan.makeIterator()
|
||||||
return AnyIterator {
|
return AnyIterator {
|
||||||
|
@ -412,16 +398,16 @@ extension Plan {
|
||||||
/// Creates a plan that executes the task every specific weekday.
|
/// Creates a plan that executes the task every specific weekday.
|
||||||
public static func every(_ weekday: Weekday) -> DateMiddleware {
|
public static func every(_ weekday: Weekday) -> DateMiddleware {
|
||||||
let plan = Plan.make { () -> AnyIterator<Date> in
|
let plan = Plan.make { () -> AnyIterator<Date> in
|
||||||
let calendar = Calendar.standard
|
let calendar = Calendar.gregorian
|
||||||
var date: Date!
|
var date: Date?
|
||||||
return AnyIterator<Date> {
|
return AnyIterator<Date> {
|
||||||
if weekday.isToday {
|
if let d = date {
|
||||||
date = Date().start
|
date = calendar.date(byAdding: .day, value: 7, to: d)
|
||||||
} else if date == nil {
|
} else if Date().is(weekday) {
|
||||||
let components = weekday.toDateComponents()
|
date = Date().startOfToday
|
||||||
date = calendar.nextDate(after: date, matching: components, matchingPolicy: .strict)
|
|
||||||
} else {
|
} else {
|
||||||
date = calendar.date(byAdding: .day, value: 7, to: date)
|
let components = weekday.toDateComponents()
|
||||||
|
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
@ -452,16 +438,16 @@ extension Plan {
|
||||||
/// Creates a plan that executes the task every specific day in the month.
|
/// Creates a plan that executes the task every specific day in the month.
|
||||||
public static func every(_ monthday: Monthday) -> DateMiddleware {
|
public static func every(_ monthday: Monthday) -> DateMiddleware {
|
||||||
let plan = Plan.make { () -> AnyIterator<Date> in
|
let plan = Plan.make { () -> AnyIterator<Date> in
|
||||||
let calendar = Calendar.standard
|
let calendar = Calendar.gregorian
|
||||||
var date: Date!
|
var date: Date?
|
||||||
return AnyIterator<Date> {
|
return AnyIterator<Date> {
|
||||||
if monthday.isToday {
|
if let d = date {
|
||||||
date = Date().start
|
date = calendar.date(byAdding: .year, value: 1, to: d)
|
||||||
} else if date == nil {
|
} else if Date().is(monthday) {
|
||||||
let components = monthday.toDateComponents()
|
date = Date().startOfToday
|
||||||
date = calendar.nextDate(after: date, matching: components, matchingPolicy: .strict)
|
|
||||||
} else {
|
} else {
|
||||||
date = calendar.date(byAdding: .year, value: 1, to: date)
|
let components = monthday.toDateComponents()
|
||||||
|
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
@ -492,7 +478,7 @@ extension Plan {
|
||||||
|
|
||||||
extension Plan {
|
extension Plan {
|
||||||
public func isNever() -> Bool {
|
public func isNever() -> Bool {
|
||||||
return self.sequence.makeIterator().next() == nil
|
return self.iSeq.makeIterator().next() == nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,13 @@ import Foundation
|
||||||
/// `ActionKey` represents a token that can be used to remove the action.
|
/// `ActionKey` represents a token that can be used to remove the action.
|
||||||
public struct ActionKey {
|
public struct ActionKey {
|
||||||
|
|
||||||
fileprivate let bucketKey: BucketKey
|
fileprivate let cabinetKey: CabinetKey
|
||||||
}
|
}
|
||||||
|
|
||||||
extension BucketKey {
|
extension CabinetKey {
|
||||||
|
|
||||||
func asActionKey() -> ActionKey {
|
func asActionKey() -> ActionKey {
|
||||||
return ActionKey(bucketKey: self)
|
return ActionKey(cabinetKey: self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,19 +18,17 @@ open class Task {
|
||||||
|
|
||||||
public typealias Action = (Task) -> Void
|
public typealias Action = (Task) -> Void
|
||||||
|
|
||||||
private let _lock = NSLock()
|
private let _mutex = NSRecursiveLock()
|
||||||
|
|
||||||
private weak var _taskCenter: TaskCenter?
|
|
||||||
|
|
||||||
private var _iterator: AnyIterator<Interval>
|
private var _iterator: AnyIterator<Interval>
|
||||||
private var _timer: DispatchSourceTimer
|
private var _timer: DispatchSourceTimer
|
||||||
|
|
||||||
private lazy var _onElapseActions = Bucket<Action>()
|
private lazy var _onElapseActions = Cabinet<Action>()
|
||||||
private var _onDeinitActions = Bucket<Action>()
|
private lazy var _onDeinitActions = Cabinet<Action>()
|
||||||
|
|
||||||
private lazy var _suspensions: UInt64 = 0
|
private lazy var _suspensions: UInt64 = 0
|
||||||
private lazy var _timeline = Timeline()
|
private lazy var _timeline = Timeline()
|
||||||
private lazy var _tags: Set<String> = []
|
|
||||||
private lazy var _countOfExecutions: Int = 0
|
private lazy var _countOfExecutions: Int = 0
|
||||||
|
|
||||||
private lazy var _lifetime: Interval = Int.max.seconds
|
private lazy var _lifetime: Interval = Int.max.seconds
|
||||||
|
@ -44,6 +42,9 @@ open class Task {
|
||||||
return timer
|
return timer
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
open internal(set) weak var taskCenter: TaskCenter?
|
||||||
|
let taskCenterMutex = NSRecursiveLock()
|
||||||
|
|
||||||
init(plan: Plan,
|
init(plan: Plan,
|
||||||
queue: DispatchQueue?,
|
queue: DispatchQueue?,
|
||||||
onElapse: @escaping (Task) -> Void) {
|
onElapse: @escaping (Task) -> Void) {
|
||||||
|
@ -51,7 +52,7 @@ open class Task {
|
||||||
_iterator = plan.makeIterator()
|
_iterator = plan.makeIterator()
|
||||||
_timer = DispatchSource.makeTimerSource(queue: queue)
|
_timer = DispatchSource.makeTimerSource(queue: queue)
|
||||||
|
|
||||||
_onElapseActions.add(onElapse)
|
_onElapseActions.append(onElapse)
|
||||||
|
|
||||||
_timer.setEventHandler { [weak self] in
|
_timer.setEventHandler { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
@ -63,8 +64,8 @@ open class Task {
|
||||||
_timeline.estimatedNextExecution = Date().adding(interval)
|
_timeline.estimatedNextExecution = Date().adding(interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskCenter.default.add(self)
|
|
||||||
_timer.resume()
|
_timer.resume()
|
||||||
|
TaskCenter.default.add(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
@ -81,7 +82,7 @@ open class Task {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func scheduleNext() {
|
private func scheduleNext() {
|
||||||
_lock.withLock {
|
_mutex.withLock {
|
||||||
let now = Date()
|
let now = Date()
|
||||||
var estimated = _timeline.estimatedNextExecution ?? now
|
var estimated = _timeline.estimatedNextExecution ?? now
|
||||||
repeat {
|
repeat {
|
||||||
|
@ -99,7 +100,7 @@ open class Task {
|
||||||
|
|
||||||
/// Execute this task now, without disrupting its plan.
|
/// Execute this task now, without disrupting its plan.
|
||||||
public func execute() {
|
public func execute() {
|
||||||
let actions = _lock.withLock { () -> Bucket<Task.Action> in
|
let actions = _mutex.withLock { () -> Cabinet<Task.Action> in
|
||||||
let now = Date()
|
let now = Date()
|
||||||
if _timeline.firstExecution == nil {
|
if _timeline.firstExecution == nil {
|
||||||
_timeline.firstExecution = now
|
_timeline.firstExecution = now
|
||||||
|
@ -124,29 +125,16 @@ open class Task {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
open internal(set) var taskCenter: TaskCenter? {
|
|
||||||
get {
|
|
||||||
return _lock.withLock {
|
|
||||||
_taskCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
_lock.withLock {
|
|
||||||
_taskCenter = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The number of times the task has been executed.
|
/// The number of times the task has been executed.
|
||||||
public var countOfExecutions: Int {
|
public var countOfExecutions: Int {
|
||||||
return _lock.withLock {
|
return _mutex.withLock {
|
||||||
_countOfExecutions
|
_countOfExecutions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Boolean indicating whether the task was canceled.
|
/// A Boolean indicating whether the task was canceled.
|
||||||
public var isCancelled: Bool {
|
public var isCancelled: Bool {
|
||||||
return _lock.withLock {
|
return _mutex.withLock {
|
||||||
_timer.isCancelled
|
_timer.isCancelled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,7 +143,7 @@ open class Task {
|
||||||
|
|
||||||
/// Reschedules this task with the new plan.
|
/// Reschedules this task with the new plan.
|
||||||
public func reschedule(_ new: Plan) {
|
public func reschedule(_ new: Plan) {
|
||||||
_lock.withLock {
|
_mutex.withLock {
|
||||||
_iterator = new.makeIterator()
|
_iterator = new.makeIterator()
|
||||||
}
|
}
|
||||||
scheduleNext()
|
scheduleNext()
|
||||||
|
@ -163,14 +151,14 @@ open class Task {
|
||||||
|
|
||||||
/// Suspensions of this task.
|
/// Suspensions of this task.
|
||||||
public var suspensions: UInt64 {
|
public var suspensions: UInt64 {
|
||||||
return _lock.withLock {
|
return _mutex.withLock {
|
||||||
_suspensions
|
_suspensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Suspends this task.
|
/// Suspends this task.
|
||||||
public func suspend() {
|
public func suspend() {
|
||||||
_lock.withLock {
|
_mutex.withLock {
|
||||||
if _suspensions < UInt64.max {
|
if _suspensions < UInt64.max {
|
||||||
_timer.suspend()
|
_timer.suspend()
|
||||||
_suspensions += 1
|
_suspensions += 1
|
||||||
|
@ -180,7 +168,7 @@ open class Task {
|
||||||
|
|
||||||
/// Resumes this task.
|
/// Resumes this task.
|
||||||
public func resume() {
|
public func resume() {
|
||||||
_lock.withLock {
|
_mutex.withLock {
|
||||||
if _suspensions > 0 {
|
if _suspensions > 0 {
|
||||||
_timer.resume()
|
_timer.resume()
|
||||||
_suspensions -= 1
|
_suspensions -= 1
|
||||||
|
@ -190,7 +178,7 @@ open class Task {
|
||||||
|
|
||||||
/// Cancels this task.
|
/// Cancels this task.
|
||||||
public func cancel() {
|
public func cancel() {
|
||||||
_lock.withLock {
|
_mutex.withLock {
|
||||||
_timer.cancel()
|
_timer.cancel()
|
||||||
}
|
}
|
||||||
TaskCenter.default.remove(self)
|
TaskCenter.default.remove(self)
|
||||||
|
@ -198,8 +186,8 @@ open class Task {
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
open func onDeinit(_ body: @escaping Action) -> ActionKey {
|
open func onDeinit(_ body: @escaping Action) -> ActionKey {
|
||||||
return _lock.withLock {
|
return _mutex.withLock {
|
||||||
return _onDeinitActions.add(body).asActionKey()
|
return _onDeinitActions.append(body).asActionKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,21 +195,21 @@ open class Task {
|
||||||
|
|
||||||
/// The snapshot timeline of this task.
|
/// The snapshot timeline of this task.
|
||||||
public var timeline: Timeline {
|
public var timeline: Timeline {
|
||||||
return _lock.withLock {
|
return _mutex.withLock {
|
||||||
_timeline
|
_timeline
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The lifetime of this task.
|
/// The lifetime of this task.
|
||||||
public var lifetime: Interval {
|
public var lifetime: Interval {
|
||||||
return _lock.withLock {
|
return _mutex.withLock {
|
||||||
_lifetime
|
_lifetime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The rest of lifetime.
|
/// The rest of lifetime.
|
||||||
public var restOfLifetime: Interval {
|
public var restOfLifetime: Interval {
|
||||||
return _lock.withLock {
|
return _mutex.withLock {
|
||||||
_lifetime - Date().interval(since: _timeline.initialization)
|
_lifetime - Date().interval(since: _timeline.initialization)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,16 +224,16 @@ open class Task {
|
||||||
public func setLifetime(_ interval: Interval) -> Bool {
|
public func setLifetime(_ interval: Interval) -> Bool {
|
||||||
guard restOfLifetime.isPositive else { return false }
|
guard restOfLifetime.isPositive else { return false }
|
||||||
|
|
||||||
_lock.lock()
|
_mutex.lock()
|
||||||
let age = Date().interval(since: _timeline.initialization)
|
let age = Date().interval(since: _timeline.initialization)
|
||||||
guard age.isShorter(than: interval) else {
|
guard age.isShorter(than: interval) else {
|
||||||
_lock.unlock()
|
_mutex.unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
_lifetime = interval
|
_lifetime = interval
|
||||||
_lifetimeTimer.schedule(after: interval - age)
|
_lifetimeTimer.schedule(after: interval - age)
|
||||||
_lock.unlock()
|
_mutex.unlock()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,7 +249,7 @@ open class Task {
|
||||||
guard rest.isPositive else { return false }
|
guard rest.isPositive else { return false }
|
||||||
rest += interval
|
rest += interval
|
||||||
guard rest.isPositive else { return false }
|
guard rest.isPositive else { return false }
|
||||||
_lock.withLock {
|
_mutex.withLock {
|
||||||
_lifetime += interval
|
_lifetime += interval
|
||||||
_lifetimeTimer.schedule(after: rest)
|
_lifetimeTimer.schedule(after: rest)
|
||||||
}
|
}
|
||||||
|
@ -283,7 +271,7 @@ open class Task {
|
||||||
|
|
||||||
/// The number of actions in this task.
|
/// The number of actions in this task.
|
||||||
public var countOfActions: Int {
|
public var countOfActions: Int {
|
||||||
return _lock.withLock {
|
return _mutex.withLock {
|
||||||
_onElapseActions.count
|
_onElapseActions.count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,94 +279,28 @@ open class Task {
|
||||||
/// Adds action to this task.
|
/// Adds action to this task.
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public func addAction(_ action: @escaping (Task) -> Void) -> ActionKey {
|
public func addAction(_ action: @escaping (Task) -> Void) -> ActionKey {
|
||||||
return _lock.withLock {
|
return _mutex.withLock {
|
||||||
return _onElapseActions.add(action).asActionKey()
|
return _onElapseActions.append(action).asActionKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes action by key from this task.
|
/// Removes action by key from this task.
|
||||||
public func removeAction(byKey key: ActionKey) {
|
public func removeAction(byKey key: ActionKey) {
|
||||||
_lock.withLock {
|
_mutex.withLock {
|
||||||
_ = _onElapseActions.removeElement(for: key.bucketKey)
|
_ = _onElapseActions.delete(key.cabinetKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes all actions from this task.
|
/// Removes all actions from this task.
|
||||||
public func removeAllActions() {
|
public func removeAllActions() {
|
||||||
_lock.withLock {
|
_mutex.withLock {
|
||||||
_onElapseActions.removeAll()
|
_onElapseActions.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Tag
|
// MARK: - Tag
|
||||||
|
open func add(to: TaskCenter) {
|
||||||
/// All tags associated with this task.
|
_mutex.lock()
|
||||||
public var tags: Set<String> {
|
|
||||||
return _lock.withLock {
|
|
||||||
_tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds tags to this task.
|
|
||||||
public func addTags(_ tags: [String]) {
|
|
||||||
var set = Set(tags)
|
|
||||||
|
|
||||||
_lock.lock()
|
|
||||||
set.subtract(_tags)
|
|
||||||
guard set.count > 0 else {
|
|
||||||
_lock.unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_tags.formUnion(set)
|
|
||||||
_lock.unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds tags to this task.
|
|
||||||
public func addTags(_ tags: String...) {
|
|
||||||
addTags(tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds the tag to this task.
|
|
||||||
public func addTag(_ tag: String) {
|
|
||||||
addTags(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes tags from this task.
|
|
||||||
public func removeTags(_ tags: [String]) {
|
|
||||||
var set = Set(tags)
|
|
||||||
_lock.lock()
|
|
||||||
set.formIntersection(_tags)
|
|
||||||
guard set.count > 0 else {
|
|
||||||
_lock.unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_tags.subtract(set)
|
|
||||||
_lock.unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes tags from this task.
|
|
||||||
public func removeTags(_ tags: String...) {
|
|
||||||
removeTags(tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes the tag from this task.
|
|
||||||
public func removeTag(_ tag: String) {
|
|
||||||
removeTags(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Suspends all tasks that have the tag.
|
|
||||||
public static func suspend(byTag tag: String) {
|
|
||||||
TaskCenter.default.tasksWithTag(tag).forEach { $0.suspend() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resumes all tasks that have the tag.
|
|
||||||
public static func resume(byTag tag: String) {
|
|
||||||
TaskCenter.default.tasksWithTag(tag).forEach { $0.resume() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cancels all tasks that have the tag.
|
|
||||||
public static func cancel(byTag tag: String) {
|
|
||||||
TaskCenter.default.tasksWithTag(tag).forEach { $0.cancel() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,8 +322,7 @@ extension Task: CustomStringConvertible {
|
||||||
/// A textual representation of this task.
|
/// A textual representation of this task.
|
||||||
public var description: String {
|
public var description: String {
|
||||||
return "Task: { " +
|
return "Task: { " +
|
||||||
"\"isCancelled\": \(_timer.isCancelled)" +
|
"\"isCancelled\": \(_timer.isCancelled), " +
|
||||||
"\"tags\": \(_tags), " +
|
|
||||||
"\"countOfActions\": \(_onElapseActions.count), " +
|
"\"countOfActions\": \(_onElapseActions.count), " +
|
||||||
"\"countOfExecutions\": \(_countOfExecutions), " +
|
"\"countOfExecutions\": \(_countOfExecutions), " +
|
||||||
"\"timeline\": \(_timeline)" +
|
"\"timeline\": \(_timeline)" +
|
||||||
|
|
|
@ -2,88 +2,163 @@ import Foundation
|
||||||
|
|
||||||
private let _default = TaskCenter()
|
private let _default = TaskCenter()
|
||||||
|
|
||||||
private class _TaskWrapper {
|
extension TaskCenter {
|
||||||
|
|
||||||
|
private class TaskBox: Hashable {
|
||||||
|
|
||||||
weak var task: Task?
|
weak var task: Task?
|
||||||
|
|
||||||
|
// To find slot
|
||||||
let hashValue: Int
|
let hashValue: Int
|
||||||
|
|
||||||
init(_ task: Task) {
|
init(_ task: Task) {
|
||||||
self.task = task
|
self.task = task
|
||||||
self.hashValue = task.hashValue
|
self.hashValue = task.hashValue
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extension _TaskWrapper: Hashable {
|
// To find task
|
||||||
|
static func == (lhs: TaskBox, rhs: TaskBox) -> Bool {
|
||||||
static func == (lhs: _TaskWrapper, rhs: _TaskWrapper) -> Bool {
|
|
||||||
return lhs.task == rhs.task
|
return lhs.task == rhs.task
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open class TaskCenter {
|
open class TaskCenter {
|
||||||
|
|
||||||
private let _lock = NSLock()
|
private let mutex = NSRecursiveLock()
|
||||||
private var _tasks: Set<_TaskWrapper> = []
|
|
||||||
|
private var taskMap: [String: Set<TaskBox>] = [:]
|
||||||
|
private var tagMap: [TaskBox: Set<String>] = [:]
|
||||||
|
|
||||||
open class var `default`: TaskCenter {
|
open class var `default`: TaskCenter {
|
||||||
return _default
|
return _default
|
||||||
}
|
}
|
||||||
|
|
||||||
open func add(_ task: Task) {
|
open func add(_ task: Task) {
|
||||||
if let center = task.taskCenter {
|
task.taskCenterMutex.lock()
|
||||||
if center === self {
|
defer {
|
||||||
return
|
task.taskCenterMutex.unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let center = task.taskCenter {
|
||||||
|
if center === self { return }
|
||||||
center.remove(task)
|
center.remove(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
task.taskCenter = self
|
task.taskCenter = self
|
||||||
|
|
||||||
_lock.withLock {
|
mutex.withLock {
|
||||||
let wrapper = _TaskWrapper(task)
|
let box = TaskBox(task)
|
||||||
_tasks.insert(wrapper)
|
tagMap[box] = []
|
||||||
|
|
||||||
task.onDeinit { [weak self, weak wrapper = wrapper] task in
|
|
||||||
guard let self = self, let wrapper = wrapper else { return }
|
|
||||||
self._tasks.remove(wrapper)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task.onDeinit { [weak self] (t) in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.remove(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open func remove(_ task: Task) {
|
open func remove(_ task: Task) {
|
||||||
|
task.taskCenterMutex.lock()
|
||||||
|
defer {
|
||||||
|
task.taskCenterMutex.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
guard task.taskCenter === self else {
|
guard task.taskCenter === self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
task.taskCenter = nil
|
task.taskCenter = nil
|
||||||
|
|
||||||
_lock.withLock {
|
mutex.withLock {
|
||||||
let wrapper = _TaskWrapper(task)
|
let box = TaskBox(task)
|
||||||
_tasks.remove(wrapper)
|
if let tags = self.tagMap[box] {
|
||||||
|
for tag in tags {
|
||||||
|
self.taskMap[tag]?.remove(box)
|
||||||
|
}
|
||||||
|
self.tagMap[box] = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open func tasksWithTag(_ tag: String) -> [Task] {
|
open func addTag(_ tag: String, to task: Task) {
|
||||||
return _lock.withLock {
|
addTags([tag], to: task)
|
||||||
return _tasks.compactMap { wrapper in
|
|
||||||
if let task = wrapper.task, task.tags.contains(tag) {
|
|
||||||
return task
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
open func addTags(_ tags: [String], to task: Task) {
|
||||||
|
guard task.taskCenter === self else { return }
|
||||||
|
|
||||||
|
mutex.withLock {
|
||||||
|
let box = TaskBox(task)
|
||||||
|
if tagMap[box] == nil {
|
||||||
|
tagMap[box] = []
|
||||||
}
|
}
|
||||||
|
for tag in tags {
|
||||||
|
tagMap[box]?.insert(tag)
|
||||||
|
if taskMap[tag] == nil {
|
||||||
|
taskMap[tag] = []
|
||||||
|
}
|
||||||
|
taskMap[tag]?.insert(box)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open func removeTag(_ tag: String, from task: Task) {
|
||||||
|
removeTags([tag], from: task)
|
||||||
|
}
|
||||||
|
|
||||||
|
open func removeTags(_ tags: [String], from task: Task) {
|
||||||
|
guard task.taskCenter === self else { return }
|
||||||
|
|
||||||
|
mutex.withLock {
|
||||||
|
let box = TaskBox(task)
|
||||||
|
for tag in tags {
|
||||||
|
tagMap[box]?.remove(tag)
|
||||||
|
taskMap[tag]?.remove(box)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open func tagsForTask(_ task: Task) -> [String] {
|
||||||
|
guard task.taskCenter === self else { return [] }
|
||||||
|
|
||||||
|
return mutex.withLock {
|
||||||
|
Array(tagMap[TaskBox(task)] ?? [])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open func tasksForTag(_ tag: String) -> [Task] {
|
||||||
|
return mutex.withLock {
|
||||||
|
taskMap[tag]?.compactMap { $0.task } ?? []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open var allTasks: [Task] {
|
open var allTasks: [Task] {
|
||||||
return _lock.withLock {
|
return mutex.withLock {
|
||||||
return _tasks.compactMap { $0.task }
|
tagMap.compactMap { $0.key.task }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open var allTags: [String] {
|
||||||
|
return mutex.withLock {
|
||||||
|
taskMap.map { $0.key }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open func clear() {
|
open func clear() {
|
||||||
_lock.withLock {
|
mutex.withLock {
|
||||||
_tasks = []
|
tagMap = [:]
|
||||||
|
taskMap = [:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func suspendByTag(_ tag: String) {
|
||||||
|
tasksForTag(tag).forEach { $0.suspend() }
|
||||||
|
}
|
||||||
|
|
||||||
|
open func resumeByTag(_ tag: String) {
|
||||||
|
tasksForTag(tag).forEach { $0.resume() }
|
||||||
|
}
|
||||||
|
|
||||||
|
open func cancelByTag(_ tag: String) {
|
||||||
|
tasksForTag(tag).forEach { $0.cancel() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,12 @@ public struct Time {
|
||||||
/// Time(hour: 25) => nil
|
/// Time(hour: 25) => nil
|
||||||
/// Time(hour: 1, minute: 61) => nil
|
/// Time(hour: 1, minute: 61) => nil
|
||||||
public init?(hour: Int, minute: Int = 0, second: Int = 0, nanosecond: Int = 0) {
|
public init?(hour: Int, minute: Int = 0, second: Int = 0, nanosecond: Int = 0) {
|
||||||
guard (0..<24).contains(hour),
|
guard
|
||||||
|
(0..<24).contains(hour),
|
||||||
(0..<60).contains(minute),
|
(0..<60).contains(minute),
|
||||||
(0..<60).contains(second),
|
(0..<60).contains(second),
|
||||||
(0..<Int(1.second.nanoseconds)).contains(nanosecond) else { return nil }
|
(0..<Int(1.second.nanoseconds)).contains(nanosecond)
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
self.hour = hour
|
self.hour = hour
|
||||||
self.minute = minute
|
self.minute = minute
|
||||||
|
@ -85,15 +87,15 @@ public struct Time {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The interval between this time and zero o'clock.
|
/// The interval between this time and start of today
|
||||||
public var intervalSinceZeroClock: Interval {
|
public var intervalSinceStartOfDay: Interval {
|
||||||
return hour.hours + minute.minutes + second.seconds + nanosecond.nanoseconds
|
return hour.hours + minute.minutes + second.seconds + nanosecond.nanoseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a dateComponenets of the time, using gregorian calender and
|
/// Returns a dateComponenets of the time, using gregorian calender and
|
||||||
/// current time zone.
|
/// current time zone.
|
||||||
public func toDateComponents() -> DateComponents {
|
public func toDateComponents() -> DateComponents {
|
||||||
return DateComponents(calendar: Calendar.standard,
|
return DateComponents(calendar: Calendar.gregorian,
|
||||||
timeZone: TimeZone.current,
|
timeZone: TimeZone.current,
|
||||||
hour: hour,
|
hour: hour,
|
||||||
minute: minute,
|
minute: minute,
|
||||||
|
@ -105,6 +107,8 @@ public struct Time {
|
||||||
extension Time: CustomStringConvertible {
|
extension Time: CustomStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this time.
|
/// A textual representation of this time.
|
||||||
|
///
|
||||||
|
/// "Time: 11:11:11.111"
|
||||||
public var description: String {
|
public var description: String {
|
||||||
let h = "\(hour)".padding(toLength: 2, withPad: "0", startingAt: 0)
|
let h = "\(hour)".padding(toLength: 2, withPad: "0", startingAt: 0)
|
||||||
let m = "\(minute)".padding(toLength: 2, withPad: "0", startingAt: 0)
|
let m = "\(minute)".padding(toLength: 2, withPad: "0", startingAt: 0)
|
||||||
|
@ -117,6 +121,8 @@ extension Time: CustomStringConvertible {
|
||||||
extension Time: CustomDebugStringConvertible {
|
extension Time: CustomDebugStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this time for debugging.
|
/// A textual representation of this time for debugging.
|
||||||
|
///
|
||||||
|
/// "Time: 11:11:11.111"
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
return description
|
return description
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,44 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
/// `Weekday` represents a day of a week without time.
|
/// `Weekday` represents a day of a week.
|
||||||
public enum Weekday: Int {
|
public enum Weekday: Int {
|
||||||
|
|
||||||
case sunday = 1, monday, tuesday, wednesday, thursday, friday, saturday
|
case sunday = 1, monday, tuesday, wednesday, thursday, friday, saturday
|
||||||
|
|
||||||
/// A Boolean value indicating whether today is the weekday.
|
/// Returns dateComponenets of the weekday, using gregorian calender and
|
||||||
public var isToday: Bool {
|
|
||||||
return Calendar.standard.dateComponents(in: .current, from: Date()).weekday == rawValue
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a dateComponenets of the weekday, using gregorian calender and
|
|
||||||
/// current time zone.
|
/// current time zone.
|
||||||
public func toDateComponents() -> DateComponents {
|
public func toDateComponents() -> DateComponents {
|
||||||
return DateComponents(calendar: Calendar.standard,
|
return DateComponents(
|
||||||
|
calendar: Calendar.gregorian,
|
||||||
timeZone: TimeZone.current,
|
timeZone: TimeZone.current,
|
||||||
weekday: rawValue)
|
weekday: rawValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Date {
|
||||||
|
|
||||||
|
/// Returns a boolean value indicating whether this day is the weekday.
|
||||||
|
public func `is`(_ weekday: Weekday) -> Bool {
|
||||||
|
return Calendar.gregorian.component(.weekday, from: self) == weekday.rawValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Weekday: CustomStringConvertible {
|
extension Weekday: CustomStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this weekday.
|
/// A textual representation of this weekday.
|
||||||
|
///
|
||||||
|
/// Weekday: Friday
|
||||||
public var description: String {
|
public var description: String {
|
||||||
return "Weekday: \(Calendar.standard.weekdaySymbols[rawValue - 1])"
|
return "Weekday: \(Calendar.gregorian.weekdaySymbols[rawValue - 1])"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Weekday: CustomDebugStringConvertible {
|
extension Weekday: CustomDebugStringConvertible {
|
||||||
|
|
||||||
/// A textual representation of this weekday for debugging.
|
/// A textual representation of this weekday for debugging.
|
||||||
|
///
|
||||||
|
/// Weekday: Friday
|
||||||
public var debugDescription: String {
|
public var debugDescription: String {
|
||||||
return description
|
return description
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// Log.swift
|
||||||
|
// Schedule
|
||||||
|
//
|
||||||
|
// Created by Quentin Jin on 2019/3/12.
|
||||||
|
// Copyright © 2019 Schedule. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let fmt = ISO8601DateFormatter()
|
||||||
|
|
||||||
|
func Log(_ t: Any) {
|
||||||
|
|
||||||
|
let now = fmt.string(from: Date())
|
||||||
|
let thread = Thread.isMainThread ? "main" : "background"
|
||||||
|
|
||||||
|
print("\(now) [\(thread)] -> \(t)")
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// main.swift
|
||||||
|
// Schedule
|
||||||
|
//
|
||||||
|
// Created by Quentin Jin on 2019/3/12.
|
||||||
|
// Copyright © 2019 Schedule. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Schedule
|
||||||
|
|
||||||
|
Log("Wake up")
|
||||||
|
|
||||||
|
let t1 = Plan.after(1.second).do {
|
||||||
|
Log("1 second passed!")
|
||||||
|
}
|
||||||
|
|
||||||
|
let t2 = Plan.after(1.minute, repeating: 0.5.seconds).do {
|
||||||
|
Log("Ping!")
|
||||||
|
}
|
||||||
|
|
||||||
|
let t3 = Plan.every("one minute and ten seconds").do {
|
||||||
|
Log("One minute and ten seconds have elapsed!")
|
||||||
|
}
|
||||||
|
|
||||||
|
let t4 = Plan.every(.monday, .tuesday, .wednesday, .thursday, .friday).at(6, 50).do {
|
||||||
|
Log("Get up!")
|
||||||
|
}
|
||||||
|
|
||||||
|
let t5 = Plan.every(.june(14)).at("08:59:59 am").do {
|
||||||
|
Log("Happy birthday!")
|
||||||
|
}
|
||||||
|
|
||||||
|
RunLoop.current.run()
|
|
@ -3,24 +3,28 @@ import XCTest
|
||||||
|
|
||||||
final class AtomicTests: XCTestCase {
|
final class AtomicTests: XCTestCase {
|
||||||
|
|
||||||
|
func testSnapshot() {
|
||||||
|
let i = Atomic<Int>(1)
|
||||||
|
XCTAssertEqual(i.snapshot(), 1)
|
||||||
|
}
|
||||||
|
|
||||||
func testRead() {
|
func testRead() {
|
||||||
let atom = Atomic<Int>(1)
|
let i = Atomic<Int>(1)
|
||||||
atom.read {
|
i.read {
|
||||||
XCTAssertEqual($0, 1)
|
XCTAssertEqual($0, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testWrite() {
|
func testWrite() {
|
||||||
let atom = Atomic<Int>(1)
|
let i = Atomic<Int>(1)
|
||||||
atom.write {
|
i.write {
|
||||||
$0 = 2
|
$0 += 1
|
||||||
}
|
|
||||||
atom.read {
|
|
||||||
XCTAssertEqual($0, 2)
|
|
||||||
}
|
}
|
||||||
|
XCTAssertEqual(i.snapshot(), 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
static var allTests = [
|
static var allTests = [
|
||||||
|
("testSnapshot", testSnapshot),
|
||||||
("testRead", testRead),
|
("testRead", testRead),
|
||||||
("testWrite", testWrite)
|
("testWrite", testWrite)
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
import XCTest
|
|
||||||
@testable import Schedule
|
|
||||||
|
|
||||||
final class BucketTests: XCTestCase {
|
|
||||||
|
|
||||||
typealias Fn = () -> Int
|
|
||||||
|
|
||||||
func testBucketKey() {
|
|
||||||
let key = BucketKey(underlying: 0)
|
|
||||||
XCTAssertEqual(key.increased(), BucketKey(underlying: 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAdd() {
|
|
||||||
var bucket = Bucket<Fn>()
|
|
||||||
bucket.add { 1 }
|
|
||||||
bucket.add { 2 }
|
|
||||||
bucket.add { 3 }
|
|
||||||
XCTAssertEqual(bucket.count, 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testRemove() {
|
|
||||||
var bucket = Bucket<Fn>()
|
|
||||||
|
|
||||||
let k1 = bucket.add { 1 }
|
|
||||||
let k2 = bucket.add { 2 }
|
|
||||||
|
|
||||||
let fn1 = bucket.removeElement(for: k1)
|
|
||||||
XCTAssertNotNil(fn1)
|
|
||||||
|
|
||||||
let fn2 = bucket.removeElement(for: k2)
|
|
||||||
XCTAssertNotNil(fn2)
|
|
||||||
|
|
||||||
XCTAssertNil(bucket.removeElement(for: k2.increased()))
|
|
||||||
|
|
||||||
bucket.removeAll()
|
|
||||||
XCTAssertEqual(bucket.count, 0)
|
|
||||||
|
|
||||||
bucket.add { 3 }
|
|
||||||
XCTAssertEqual(bucket.count, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testGet() {
|
|
||||||
var bucket = Bucket<Fn>()
|
|
||||||
let k1 = bucket.add { 1 }
|
|
||||||
let k2 = bucket.add { 2 }
|
|
||||||
XCTAssertNotNil(bucket.element(for: k1))
|
|
||||||
XCTAssertNotNil(bucket.element(for: k2))
|
|
||||||
guard let fn1 = bucket.element(for: k1), let fn2 = bucket.element(for: k2) else {
|
|
||||||
XCTFail()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
XCTAssertEqual(fn1(), 1)
|
|
||||||
XCTAssertEqual(fn2(), 2)
|
|
||||||
|
|
||||||
XCTAssertNil(bucket.element(for: k2.increased()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSequence() {
|
|
||||||
var bucket = Bucket<Fn>()
|
|
||||||
bucket.add { 0 }
|
|
||||||
bucket.add { 1 }
|
|
||||||
bucket.add { 2 }
|
|
||||||
|
|
||||||
var i = 0
|
|
||||||
for fn in bucket {
|
|
||||||
XCTAssertEqual(fn(), i)
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static var allTests = [
|
|
||||||
("testBucketKey", testBucketKey),
|
|
||||||
("testAdd", testAdd),
|
|
||||||
("testRemove", testRemove),
|
|
||||||
("testGet", testGet),
|
|
||||||
("testSequence", testSequence)
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
import XCTest
|
||||||
|
@testable import Schedule
|
||||||
|
|
||||||
|
final class CabinetTests: XCTestCase {
|
||||||
|
|
||||||
|
typealias Fn = () -> Int
|
||||||
|
|
||||||
|
func testCabinetKey() {
|
||||||
|
let key = CabinetKey(underlying: 0)
|
||||||
|
XCTAssertEqual(key.increased(), CabinetKey(underlying: 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAppend() {
|
||||||
|
var cabinet = Cabinet<Fn>()
|
||||||
|
let k1 = cabinet.append { 1 }
|
||||||
|
let k2 = cabinet.append { 2 }
|
||||||
|
|
||||||
|
XCTAssertEqual(k1.increased(), k2)
|
||||||
|
XCTAssertEqual(cabinet.count, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGet() {
|
||||||
|
var cabinet = Cabinet<Fn>()
|
||||||
|
let k1 = cabinet.append { 1 }
|
||||||
|
let k2 = cabinet.append { 2 }
|
||||||
|
|
||||||
|
guard
|
||||||
|
let fn1 = cabinet.get(k1),
|
||||||
|
let fn2 = cabinet.get(k2)
|
||||||
|
else {
|
||||||
|
XCTFail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
XCTAssertEqual(fn1(), 1)
|
||||||
|
XCTAssertEqual(fn2(), 2)
|
||||||
|
|
||||||
|
XCTAssertNil(cabinet.get(k2.increased()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDelete() {
|
||||||
|
var cabinet = Cabinet<Fn>()
|
||||||
|
|
||||||
|
let k1 = cabinet.append { 1 }
|
||||||
|
let k2 = cabinet.append { 2 }
|
||||||
|
|
||||||
|
XCTAssertEqual(cabinet.count, 2)
|
||||||
|
|
||||||
|
let fn1 = cabinet.delete(k1)
|
||||||
|
XCTAssertNotNil(fn1)
|
||||||
|
|
||||||
|
let fn2 = cabinet.delete(k2)
|
||||||
|
XCTAssertNotNil(fn2)
|
||||||
|
|
||||||
|
XCTAssertEqual(cabinet.count, 0)
|
||||||
|
|
||||||
|
XCTAssertNil(cabinet.delete(k2.increased()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testClear() {
|
||||||
|
var cabinet = Cabinet<Fn>()
|
||||||
|
|
||||||
|
cabinet.append { 1 }
|
||||||
|
cabinet.append { 2 }
|
||||||
|
|
||||||
|
XCTAssertEqual(cabinet.count, 2)
|
||||||
|
|
||||||
|
cabinet.clear()
|
||||||
|
XCTAssertEqual(cabinet.count, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSequence() {
|
||||||
|
var cabinet = Cabinet<Fn>()
|
||||||
|
cabinet.append { 0 }
|
||||||
|
cabinet.append { 1 }
|
||||||
|
cabinet.append { 2 }
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
for fn in cabinet {
|
||||||
|
XCTAssertEqual(fn(), i)
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var allTests = [
|
||||||
|
("testCabinetKey", testCabinetKey),
|
||||||
|
("testAppend", testAppend),
|
||||||
|
("testGet", testGet),
|
||||||
|
("testDelete", testDelete),
|
||||||
|
("testClear", testClear),
|
||||||
|
("testSequence", testSequence)
|
||||||
|
]
|
||||||
|
}
|
|
@ -22,9 +22,9 @@ final class DateTimeTests: XCTestCase {
|
||||||
XCTAssertTrue(1.1.second.isLonger(than: 1.0.second))
|
XCTAssertTrue(1.1.second.isLonger(than: 1.0.second))
|
||||||
XCTAssertTrue(3.days.isShorter(than: 1.week))
|
XCTAssertTrue(3.days.isShorter(than: 1.week))
|
||||||
XCTAssertEqual(Interval.longest(1.hour, 1.day, 1.week), 1.week)
|
XCTAssertEqual(Interval.longest(1.hour, 1.day, 1.week), 1.week)
|
||||||
XCTAssertEqual(Interval.longest([]), .init(nanoseconds: 0))
|
XCTAssertEqual(Interval.longest([]), nil)
|
||||||
XCTAssertEqual(Interval.shortest(1.hour, 59.minutes, 2999.seconds), 2999.seconds)
|
XCTAssertEqual(Interval.shortest(1.hour, 59.minutes, 2999.seconds), 2999.seconds)
|
||||||
XCTAssertEqual(Interval.shortest([]), .init(nanoseconds: 0))
|
XCTAssertEqual(Interval.shortest([]), nil)
|
||||||
|
|
||||||
XCTAssertEqual(1.second * 60, 1.minute)
|
XCTAssertEqual(1.second * 60, 1.minute)
|
||||||
XCTAssertEqual(59.minutes + 60.seconds, 1.hour)
|
XCTAssertEqual(59.minutes + 60.seconds, 1.hour)
|
||||||
|
@ -35,7 +35,7 @@ final class DateTimeTests: XCTestCase {
|
||||||
XCTAssertEqual(-(1.second), (-1).second)
|
XCTAssertEqual(-(1.second), (-1).second)
|
||||||
|
|
||||||
let i1 = Interval(seconds: 24 * 60 * 60)
|
let i1 = Interval(seconds: 24 * 60 * 60)
|
||||||
XCTAssertEqual(1.nanosecond * i1.nanoseconds, 1.day)
|
XCTAssertEqual(1.nanosecond * i1.asNanoseconds(), 1.day)
|
||||||
XCTAssertEqual(2.microsecond * i1.asMicroseconds(), 2.days)
|
XCTAssertEqual(2.microsecond * i1.asMicroseconds(), 2.days)
|
||||||
XCTAssertEqual(3.millisecond * i1.asMilliseconds(), 3.days)
|
XCTAssertEqual(3.millisecond * i1.asMilliseconds(), 3.days)
|
||||||
XCTAssertEqual(4.second * i1.asSeconds(), 4.days)
|
XCTAssertEqual(4.second * i1.asSeconds(), 4.days)
|
||||||
|
@ -52,6 +52,9 @@ final class DateTimeTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMonthday() {
|
func testMonthday() {
|
||||||
|
let d = Date(year: 2019, month: 1, day: 1)
|
||||||
|
XCTAssertTrue(d.is(.january(1)))
|
||||||
|
|
||||||
XCTAssertEqual(Monthday.january(1).toDateComponents().month, 1)
|
XCTAssertEqual(Monthday.january(1).toDateComponents().month, 1)
|
||||||
XCTAssertEqual(Monthday.february(1).toDateComponents().month, 2)
|
XCTAssertEqual(Monthday.february(1).toDateComponents().month, 2)
|
||||||
XCTAssertEqual(Monthday.march(1).toDateComponents().month, 3)
|
XCTAssertEqual(Monthday.march(1).toDateComponents().month, 3)
|
||||||
|
@ -110,6 +113,14 @@ final class DateTimeTests: XCTestCase {
|
||||||
XCTAssertTrue(i.isAlmostEqual(to: (0.456.second.nanoseconds).nanoseconds, leeway: 0.001.seconds))
|
XCTAssertTrue(i.isAlmostEqual(to: (0.456.second.nanoseconds).nanoseconds, leeway: 0.001.seconds))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let components = t1?.toDateComponents()
|
||||||
|
XCTAssertEqual(components?.hour, 11)
|
||||||
|
XCTAssertEqual(components?.minute, 12)
|
||||||
|
XCTAssertEqual(components?.second, 13)
|
||||||
|
if let i = components?.nanosecond?.nanoseconds {
|
||||||
|
XCTAssertTrue(i.isAlmostEqual(to: (0.456.second.nanoseconds).nanoseconds, leeway: 0.001.seconds))
|
||||||
|
}
|
||||||
|
|
||||||
let t2 = Time("11 pm")
|
let t2 = Time("11 pm")
|
||||||
XCTAssertNotNil(t2)
|
XCTAssertNotNil(t2)
|
||||||
XCTAssertEqual(t2?.hour, 23)
|
XCTAssertEqual(t2?.hour, 23)
|
||||||
|
@ -121,10 +132,14 @@ final class DateTimeTests: XCTestCase {
|
||||||
let t4 = Time("schedule")
|
let t4 = Time("schedule")
|
||||||
XCTAssertNil(t4)
|
XCTAssertNil(t4)
|
||||||
|
|
||||||
XCTAssertEqual(Time(hour: 1)!.intervalSinceZeroClock, 1.hour)
|
XCTAssertEqual(Time(hour: 1)!.intervalSinceStartOfDay, 1.hour)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testWeekday() {
|
func testWeekday() {
|
||||||
|
// Be careful the time zone problem.
|
||||||
|
let d = Date(year: 2019, month: 1, day: 1)
|
||||||
|
XCTAssertTrue(d.is(.tuesday))
|
||||||
|
|
||||||
XCTAssertEqual(Weekday.monday.toDateComponents().weekday!, 2)
|
XCTAssertEqual(Weekday.monday.toDateComponents().weekday!, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,23 +7,23 @@ final class DeinitObserverTests: XCTestCase {
|
||||||
|
|
||||||
func testObserver() {
|
func testObserver() {
|
||||||
var i = 0
|
var i = 0
|
||||||
let b0 = {
|
var fn = {
|
||||||
let obj = NSObject()
|
let obj = NSObject()
|
||||||
DeinitObserver.observe(obj, onDeinit: {
|
DeinitObserver.observe(obj) {
|
||||||
i += 1
|
i += 1
|
||||||
})
|
|
||||||
}
|
}
|
||||||
b0()
|
}
|
||||||
|
fn()
|
||||||
XCTAssertEqual(i, 1)
|
XCTAssertEqual(i, 1)
|
||||||
|
|
||||||
let b1 = {
|
fn = {
|
||||||
let obj = NSObject()
|
let obj = NSObject()
|
||||||
let observer = DeinitObserver.observe(obj, onDeinit: {
|
let observer = DeinitObserver.observe(obj) {
|
||||||
i += 1
|
i += 1
|
||||||
})
|
|
||||||
observer.cancel()
|
|
||||||
}
|
}
|
||||||
b1()
|
observer.invalidate()
|
||||||
|
}
|
||||||
|
fn()
|
||||||
XCTAssertEqual(i, 1)
|
XCTAssertEqual(i, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,26 +4,30 @@ import XCTest
|
||||||
final class ExtensionsTests: XCTestCase {
|
final class ExtensionsTests: XCTestCase {
|
||||||
|
|
||||||
func testClampedToInt() {
|
func testClampedToInt() {
|
||||||
let a: Double = 0.1
|
let a = Double(Int.max) + 1
|
||||||
XCTAssertEqual(a.clampedToInt(), 0)
|
XCTAssertEqual(a.clampedToInt(), Int.max)
|
||||||
|
|
||||||
|
let b = Double(Int.min) - 1
|
||||||
|
XCTAssertEqual(b.clampedToInt(), Int.min)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testClampedAdding() {
|
func testClampedAdding() {
|
||||||
let a: Int = 1
|
let i = Int.max
|
||||||
let b: Int = .max
|
XCTAssertEqual(i.clampedAdding(1), Int.max)
|
||||||
XCTAssertEqual(a.clampedAdding(b), Int.max)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testClampedSubtracting() {
|
func testClampedSubtracting() {
|
||||||
let a: Int = .min
|
let i = Int.min
|
||||||
let b: Int = 1
|
XCTAssertEqual(i.clampedSubtracting(1), Int.min)
|
||||||
XCTAssertEqual(a.clampedSubtracting(b), Int.min)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testStart() {
|
func testStartOfToday() {
|
||||||
let start = Date().start
|
let components = Date().startOfToday.dateComponents
|
||||||
let components = start.dateComponents
|
guard
|
||||||
guard let h = components.hour, let m = components.minute, let s = components.second else {
|
let h = components.hour,
|
||||||
|
let m = components.minute,
|
||||||
|
let s = components.second
|
||||||
|
else {
|
||||||
XCTFail()
|
XCTFail()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -36,6 +40,6 @@ final class ExtensionsTests: XCTestCase {
|
||||||
("testClampedToInt", testClampedToInt),
|
("testClampedToInt", testClampedToInt),
|
||||||
("testClampedAdding", testClampedAdding),
|
("testClampedAdding", testClampedAdding),
|
||||||
("testClampedSubtracting", testClampedSubtracting),
|
("testClampedSubtracting", testClampedSubtracting),
|
||||||
("testStart", testStart)
|
("testStartOfToday", testStartOfToday)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,21 @@ import Foundation
|
||||||
extension Date {
|
extension Date {
|
||||||
|
|
||||||
var dateComponents: DateComponents {
|
var dateComponents: DateComponents {
|
||||||
return Calendar.standard.dateComponents(in: TimeZone.current, from: self)
|
return Calendar.gregorian.dateComponents(in: TimeZone.current, from: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0, second: Int = 0, nanosecond: Int = 0) {
|
init(
|
||||||
let components = DateComponents(calendar: Calendar.standard,
|
year: Int, month: Int, day: Int,
|
||||||
|
hour: Int = 0, minute: Int = 0, second: Int = 0,
|
||||||
|
nanosecond: Int = 0
|
||||||
|
) {
|
||||||
|
let components = DateComponents(
|
||||||
|
calendar: Calendar.gregorian,
|
||||||
timeZone: TimeZone.current,
|
timeZone: TimeZone.current,
|
||||||
year: year, month: month, day: day,
|
year: year, month: month, day: day,
|
||||||
hour: hour, minute: minute, second: second,
|
hour: hour, minute: minute, second: second,
|
||||||
nanosecond: nanosecond)
|
nanosecond: nanosecond
|
||||||
|
)
|
||||||
self = components.date ?? Date.distantPast
|
self = components.date ?? Date.distantPast
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,14 +40,17 @@ extension Double {
|
||||||
extension Sequence where Element == Interval {
|
extension Sequence where Element == Interval {
|
||||||
|
|
||||||
func isAlmostEqual<S>(to sequence: S, leeway: Interval) -> Bool where S: Sequence, S.Element == Element {
|
func isAlmostEqual<S>(to sequence: S, leeway: Interval) -> Bool where S: Sequence, S.Element == Element {
|
||||||
var i0 = self.makeIterator()
|
var it0 = self.makeIterator()
|
||||||
var i1 = sequence.makeIterator()
|
var it1 = sequence.makeIterator()
|
||||||
while let l = i0.next(), let r = i1.next() {
|
|
||||||
if (l - r).magnitude > leeway.magnitude {
|
while let l = it0.next(), let r = it1.next() {
|
||||||
|
if l.isAlmostEqual(to: r, leeway: leeway) {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return i0.next() == i1.next()
|
return it0.next() == it1.next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,14 +63,16 @@ extension Plan {
|
||||||
|
|
||||||
extension DispatchQueue {
|
extension DispatchQueue {
|
||||||
|
|
||||||
func async(after delay: Interval, execute body: @escaping () -> Void) {
|
func async(after interval: Interval, execute body: @escaping () -> Void) {
|
||||||
asyncAfter(wallDeadline: .now() + delay.asSeconds(), execute: body)
|
asyncAfter(wallDeadline: .now() + interval.asSeconds(), execute: body)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func `is`(_ queue: DispatchQueue) -> Bool {
|
static func `is`(_ queue: DispatchQueue) -> Bool {
|
||||||
let key = DispatchSpecificKey<()>()
|
let key = DispatchSpecificKey<()>()
|
||||||
|
|
||||||
queue.setSpecific(key: key, value: ())
|
queue.setSpecific(key: key, value: ())
|
||||||
defer { queue.setSpecific(key: key, value: nil) }
|
defer { queue.setSpecific(key: key, value: nil) }
|
||||||
|
|
||||||
return DispatchQueue.getSpecific(key: key) != nil
|
return DispatchQueue.getSpecific(key: key) != nil
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,9 +18,7 @@ final class PlanTests: XCTestCase {
|
||||||
let d3 = d2 + intervals[3]
|
let d3 = d2 + intervals[3]
|
||||||
|
|
||||||
let s2 = Plan.of(d0, d1, d2, d3)
|
let s2 = Plan.of(d0, d1, d2, d3)
|
||||||
let s3 = Plan.from([d0, d1, d2, d3])
|
|
||||||
XCTAssertTrue(s2.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
|
XCTAssertTrue(s2.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
|
||||||
XCTAssertTrue(s3.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
|
|
||||||
|
|
||||||
let longTime = (100 * 365).days
|
let longTime = (100 * 365).days
|
||||||
XCTAssertTrue(Plan.distantPast.makeIterator().next()!.isLonger(than: longTime))
|
XCTAssertTrue(Plan.distantPast.makeIterator().next()!.isLonger(than: longTime))
|
||||||
|
|
|
@ -8,61 +8,83 @@ final class TaskCenterTests: XCTestCase {
|
||||||
return Plan.never.do { }
|
return Plan.never.do { }
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultCenter: TaskCenter {
|
var center: TaskCenter {
|
||||||
return TaskCenter.default
|
return TaskCenter.default
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDefault() {
|
func testDefault() {
|
||||||
let task = makeTask()
|
let task = makeTask()
|
||||||
XCTAssertTrue(defaultCenter.allTasks.contains(task))
|
XCTAssertTrue(center.allTasks.contains(task))
|
||||||
defaultCenter.clear()
|
center.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAdd() {
|
func testAdd() {
|
||||||
let centerA = TaskCenter()
|
let c = TaskCenter()
|
||||||
|
|
||||||
let task = makeTask()
|
let task = makeTask()
|
||||||
centerA.add(task)
|
XCTAssertEqual(center.allTasks.count, 1)
|
||||||
|
|
||||||
XCTAssertEqual(defaultCenter.allTasks.count, 0)
|
c.add(task)
|
||||||
XCTAssertEqual(centerA.allTasks.count, 1)
|
XCTAssertEqual(center.allTasks.count, 0)
|
||||||
|
XCTAssertEqual(c.allTasks.count, 1)
|
||||||
|
|
||||||
centerA.add(task)
|
c.add(task)
|
||||||
XCTAssertEqual(centerA.allTasks.count, 1)
|
XCTAssertEqual(c.allTasks.count, 1)
|
||||||
|
|
||||||
|
center.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRemove() {
|
func testRemove() {
|
||||||
let task = makeTask()
|
let task = makeTask()
|
||||||
defaultCenter.remove(task)
|
center.remove(task)
|
||||||
XCTAssertFalse(defaultCenter.allTasks.contains(task))
|
XCTAssertFalse(center.allTasks.contains(task))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTag() {
|
func testTag() {
|
||||||
let task = makeTask()
|
let task = makeTask()
|
||||||
|
|
||||||
let tag0 = UUID().uuidString
|
let tag = UUID().uuidString
|
||||||
task.addTag(tag0)
|
|
||||||
XCTAssertTrue(defaultCenter.tasksWithTag(tag0).contains(task))
|
|
||||||
|
|
||||||
let tag1 = UUID().uuidString
|
center.addTag(tag, to: task)
|
||||||
task.addTag(tag1)
|
XCTAssertTrue(center.tasksForTag(tag).contains(task))
|
||||||
XCTAssertTrue(defaultCenter.tasksWithTag(tag1).contains(task))
|
XCTAssertTrue(center.tagsForTask(task).contains(tag))
|
||||||
|
|
||||||
task.removeTag(tag0)
|
center.removeTag(tag, from: task)
|
||||||
XCTAssertFalse(defaultCenter.tasksWithTag(tag0).contains(task))
|
XCTAssertFalse(center.tasksForTag(tag).contains(task))
|
||||||
|
XCTAssertFalse(center.tagsForTask(task).contains(tag))
|
||||||
|
|
||||||
defaultCenter.clear()
|
center.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCount() {
|
func testAll() {
|
||||||
XCTAssertEqual(defaultCenter.allTasks.count, 0)
|
|
||||||
|
|
||||||
let task = makeTask()
|
let task = makeTask()
|
||||||
XCTAssertEqual(defaultCenter.allTasks.count, 1)
|
|
||||||
|
|
||||||
_ = task
|
let tag = UUID().uuidString
|
||||||
|
|
||||||
defaultCenter.clear()
|
center.addTag(tag, to: task)
|
||||||
|
XCTAssertEqual(center.allTags, [tag])
|
||||||
|
XCTAssertEqual(center.allTasks, [task])
|
||||||
|
|
||||||
|
center.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testOperation() {
|
||||||
|
let task = makeTask()
|
||||||
|
|
||||||
|
let tag = UUID().uuidString
|
||||||
|
|
||||||
|
center.addTag(tag, to: task)
|
||||||
|
|
||||||
|
center.suspendByTag(tag)
|
||||||
|
XCTAssertEqual(task.suspensions, 1)
|
||||||
|
|
||||||
|
center.resumeByTag(tag)
|
||||||
|
XCTAssertEqual(task.suspensions, 0)
|
||||||
|
|
||||||
|
center.cancelByTag(tag)
|
||||||
|
XCTAssertTrue(task.isCancelled)
|
||||||
|
|
||||||
|
center.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testWeak() {
|
func testWeak() {
|
||||||
|
@ -71,7 +93,7 @@ final class TaskCenterTests: XCTestCase {
|
||||||
}
|
}
|
||||||
block()
|
block()
|
||||||
|
|
||||||
XCTAssertEqual(defaultCenter.allTasks.count, 0)
|
XCTAssertEqual(center.allTasks.count, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
static var allTests = [
|
static var allTests = [
|
||||||
|
@ -79,7 +101,8 @@ final class TaskCenterTests: XCTestCase {
|
||||||
("testAdd", testAdd),
|
("testAdd", testAdd),
|
||||||
("testRemove", testRemove),
|
("testRemove", testRemove),
|
||||||
("testTag", testTag),
|
("testTag", testTag),
|
||||||
("testCount", testCount),
|
("testAll", testAll),
|
||||||
|
("testOperation", testOperation),
|
||||||
("testWeak", testWeak)
|
("testWeak", testWeak)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,16 +61,6 @@ final class TaskTests: XCTestCase {
|
||||||
XCTAssertEqual(task1.suspensions, 3)
|
XCTAssertEqual(task1.suspensions, 3)
|
||||||
task1.resume()
|
task1.resume()
|
||||||
XCTAssertEqual(task1.suspensions, 2)
|
XCTAssertEqual(task1.suspensions, 2)
|
||||||
|
|
||||||
let tag = UUID().uuidString
|
|
||||||
let task2 = Plan.distantFuture.do { }
|
|
||||||
task2.addTag(tag)
|
|
||||||
Task.suspend(byTag: tag)
|
|
||||||
XCTAssertEqual(task2.suspensions, 1)
|
|
||||||
Task.resume(byTag: tag)
|
|
||||||
XCTAssertEqual(task2.suspensions, 0)
|
|
||||||
Task.cancel(byTag: tag)
|
|
||||||
XCTAssertTrue(task2.isCancelled)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAddAndRemoveActions() {
|
func testAddAndRemoveActions() {
|
||||||
|
@ -93,23 +83,6 @@ final class TaskTests: XCTestCase {
|
||||||
XCTAssertEqual(task.countOfActions, 0)
|
XCTAssertEqual(task.countOfActions, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAddAndRemoveTags() {
|
|
||||||
let task = Plan.never.do { }
|
|
||||||
let tagA = UUID().uuidString
|
|
||||||
let tagB = UUID().uuidString
|
|
||||||
let tagC = UUID().uuidString
|
|
||||||
task.addTag(tagA)
|
|
||||||
task.addTags(tagB, tagC)
|
|
||||||
XCTAssertTrue(task.tags.contains(tagA))
|
|
||||||
XCTAssertTrue(task.tags.contains(tagC))
|
|
||||||
task.removeTag(tagA)
|
|
||||||
XCTAssertFalse(task.tags.contains(tagA))
|
|
||||||
task.removeTags(tagB, tagC)
|
|
||||||
XCTAssertFalse(task.tags.contains(tagB))
|
|
||||||
XCTAssertFalse(task.tags.contains(tagC))
|
|
||||||
task.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testReschedule() {
|
func testReschedule() {
|
||||||
let e = expectation(description: "testReschedule")
|
let e = expectation(description: "testReschedule")
|
||||||
var i = 0
|
var i = 0
|
||||||
|
@ -171,7 +144,6 @@ final class TaskTests: XCTestCase {
|
||||||
("testDispatchQueue", testDispatchQueue),
|
("testDispatchQueue", testDispatchQueue),
|
||||||
("testThread", testThread),
|
("testThread", testThread),
|
||||||
("testAddAndRemoveActions", testAddAndRemoveActions),
|
("testAddAndRemoveActions", testAddAndRemoveActions),
|
||||||
("testAddAndRemoveTags", testAddAndRemoveTags),
|
|
||||||
("testReschedule", testReschedule),
|
("testReschedule", testReschedule),
|
||||||
("testHost", testHost),
|
("testHost", testHost),
|
||||||
("testLifetime", testLifetime)
|
("testLifetime", testLifetime)
|
||||||
|
|
|
@ -8,7 +8,7 @@ public func allTests() -> [XCTestCaseEntry] {
|
||||||
testCase(TaskCenterTests.allTests),
|
testCase(TaskCenterTests.allTests),
|
||||||
testCase(TaskTests.allTests),
|
testCase(TaskTests.allTests),
|
||||||
testCase(AtomicTests.allTests),
|
testCase(AtomicTests.allTests),
|
||||||
testCase(BucketTests.allTests),
|
testCase(CabinetTests.allTests),
|
||||||
testCase(CalendarTests.allTests),
|
testCase(CalendarTests.allTests),
|
||||||
testCase(ExtensionsTests.allTests)
|
testCase(ExtensionsTests.allTests)
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue