Make diction of docs and api naming more corresponding with official api.

This commit is contained in:
Quentin Jin 2019-04-04 12:34:34 +08:00
parent 4cd608d123
commit d4f25b6031
21 changed files with 344 additions and 388 deletions

View File

@ -24,7 +24,6 @@
OBJ_49 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* Atomic.swift */; };
OBJ_50 /* Bag.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* Bag.swift */; };
OBJ_51 /* DeinitObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* DeinitObserver.swift */; };
OBJ_52 /* Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* Deprecated.swift */; };
OBJ_53 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* Extensions.swift */; };
OBJ_54 /* Interval.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* Interval.swift */; };
OBJ_55 /* Monthday.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* Monthday.swift */; };
@ -70,7 +69,6 @@
/* Begin PBXFileReference section */
OBJ_10 /* Bag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bag.swift; sourceTree = "<group>"; };
OBJ_11 /* DeinitObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeinitObserver.swift; sourceTree = "<group>"; };
OBJ_12 /* Deprecated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deprecated.swift; sourceTree = "<group>"; };
OBJ_13 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
OBJ_14 /* Interval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interval.swift; sourceTree = "<group>"; };
OBJ_15 /* Monthday.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Monthday.swift; sourceTree = "<group>"; };
@ -157,7 +155,7 @@
name = Products;
sourceTree = BUILT_PRODUCTS_DIR;
};
OBJ_5 /* */ = {
OBJ_5 = {
isa = PBXGroup;
children = (
OBJ_6 /* Package.swift */,
@ -170,7 +168,6 @@
OBJ_42 /* README.md */,
OBJ_43 /* README.zh_cn.md */,
);
name = "";
sourceTree = "<group>";
};
OBJ_7 /* Sources */ = {
@ -187,7 +184,6 @@
OBJ_9 /* Atomic.swift */,
OBJ_10 /* Bag.swift */,
OBJ_11 /* DeinitObserver.swift */,
OBJ_12 /* Deprecated.swift */,
OBJ_13 /* Extensions.swift */,
OBJ_14 /* Interval.swift */,
OBJ_15 /* Monthday.swift */,
@ -271,7 +267,7 @@
English,
en,
);
mainGroup = OBJ_5 /* */;
mainGroup = OBJ_5;
productRefGroup = OBJ_36 /* Products */;
projectDirPath = "";
projectRoot = "";
@ -292,7 +288,6 @@
OBJ_49 /* Atomic.swift in Sources */,
OBJ_50 /* Bag.swift in Sources */,
OBJ_51 /* DeinitObserver.swift in Sources */,
OBJ_52 /* Deprecated.swift in Sources */,
OBJ_53 /* Extensions.swift in Sources */,
OBJ_54 /* Interval.swift in Sources */,
OBJ_55 /* Monthday.swift in Sources */,

View File

@ -1,31 +1,38 @@
import Foundation
/// Represents a box that can read and write the underlying value atomically.
/// An atomic box that can read and write the underlying value atomically.
final class Atomic<T> {
private var v: T
private var val: T
private let lock = NSLock()
/// Init with the underlying value.
init(_ value: T) {
self.v = value
}
/// Creates a snapshot of the value nonatomically.
/// Create an atomic box with the given initial value.
@inline(__always)
func snapshot() -> T {
return v
init(_ value: T) {
self.val = value
}
/// Reads the value atomically.
/// Reads the current value atomically.
@inline(__always)
func read<U>(_ body: (T) -> U) -> U {
return lock.withLock { body(v) }
return lock.withLock { body(val) }
}
/// Writes the value atomically.
/// Reads the current value atomically.
@inline(__always)
func readVoid(_ body: (T) -> Void) {
lock.withLockVoid { body(val) }
}
/// Writes the current value atomically.
@inline(__always)
func write<U>(_ body: (inout T) -> U) -> U {
return lock.withLock { body(&v) }
return lock.withLock { body(&val) }
}
/// Writes the current value atomically.
@inline(__always)
func writeVoid(_ body: (inout T) -> Void) {
lock.withLockVoid { body(&val) }
}
}

View File

@ -1,6 +1,6 @@
import Foundation
/// A unique key used to operate the corresponding element from a bag.
/// A unique key for removing an element from a bag.
struct BagKey: Equatable {
fileprivate let i: UInt64
@ -15,7 +15,7 @@ struct BagKey: Equatable {
}
}
/// A generator used to generate a sequence of unique `BagKey`.
/// A generator that can generate a sequence of unique `BagKey`.
///
/// let k1 = gen.next()
/// let k2 = gen.next()
@ -26,8 +26,8 @@ struct BagKeyGenerator: Sequence, IteratorProtocol {
private var k = BagKey(underlying: 0)
/// Advances to the next element and returns it, or nil if no next element exists.
mutating func next() -> Element? {
/// Advances to the next key and returns it, or nil if no next key exists.
mutating func next() -> BagKey? {
if k.i == UInt64.max {
return nil
}
@ -36,7 +36,7 @@ struct BagKeyGenerator: Sequence, IteratorProtocol {
}
}
/// A data structure used to store a sequence of elements.
/// An ordered sequence.
///
/// let k1 = bag.append(e1)
/// let k2 = bag.append(e2)
@ -46,48 +46,48 @@ struct BagKeyGenerator: Sequence, IteratorProtocol {
/// // -> e2
/// }
///
/// bag.delete(k1)
/// bag.removeValue(for: k1)
struct Bag<Element> {
private typealias Entry = (key: BagKey, element: Element)
private typealias Entry = (key: BagKey, val: Element)
private var keys = BagKeyGenerator()
private var keyGen = BagKeyGenerator()
private var entries: [Entry] = []
/// Pushes the given element on to the end of this container.
/// Appends a new element at the end of this bag.
@discardableResult
mutating func append(_ new: Element) -> BagKey {
let key = keys.next()!
let key = keyGen.next()!
let entry = (key: key, element: new)
let entry = (key: key, val: new)
entries.append(entry)
return key
}
/// Returns the element for key if key is in this container.
func get(_ key: BagKey) -> Element? {
/// Returns the element associated with a given key.
func value(for key: BagKey) -> Element? {
if let entry = entries.first(where: { $0.key == key }) {
return entry.element
return entry.val
}
return nil
}
/// Deletes the element with the given key and returns this element.
/// Removes the given key and its associated element from this bag.
@discardableResult
mutating func delete(_ key: BagKey) -> Element? {
mutating func removeValue(for key: BagKey) -> Element? {
if let i = entries.firstIndex(where: { $0.key == key }) {
return entries.remove(at: i).element
return entries.remove(at: i).val
}
return nil
}
/// Removes all elements from this containers.
mutating func clear() {
/// Removes all elements from this bag.
mutating func removeAll() {
entries.removeAll()
}
/// The number of elements in this containers.
/// The number of elements in this bag.
var count: Int {
return entries.count
}
@ -95,11 +95,12 @@ struct Bag<Element> {
extension Bag: Sequence {
/// Returns an iterator over the elements of this containers.
/// Returns an iterator over the elements of this bag.
@inline(__always)
func makeIterator() -> AnyIterator<Element> {
var iterator = entries.makeIterator()
return AnyIterator<Element> {
return iterator.next()?.element
return iterator.next()?.val
}
}
}

View File

@ -2,9 +2,7 @@ import Foundation
#if canImport(ObjectiveC)
private var DEINIT_OBSERVER_KEY: Void = ()
/// Used to observe object deinit.
/// An observer that receives deinit event of the object.
///
/// let observer = DeinitObserver.observe(target) {
/// print("\(target) deinit")
@ -13,38 +11,40 @@ private var DEINIT_OBSERVER_KEY: Void = ()
/// observer.cancel()
class DeinitObserver {
private var associateKey: Void = ()
private(set) weak var observed: AnyObject?
private var action: (() -> Void)?
private var block: (() -> Void)?
private init(_ action: @escaping () -> Void) {
self.action = action
private init(_ block: @escaping () -> Void) {
self.block = block
}
/// Add observer.
/// Observe deinit event of the object.
@discardableResult
static func observe(
_ object: AnyObject,
onDeinit action: @escaping () -> Void
using block: @escaping () -> Void
) -> DeinitObserver {
let observer = DeinitObserver(action)
let observer = DeinitObserver(block)
observer.observed = object
objc_setAssociatedObject(object, &DEINIT_OBSERVER_KEY, observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_setAssociatedObject(object, &observer.associateKey, observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return observer
}
/// Remove observer.
/// Cancel observing.
func cancel() {
action = nil
block = nil
if let o = observed {
objc_setAssociatedObject(o, &DEINIT_OBSERVER_KEY, nil, .OBJC_ASSOCIATION_ASSIGN)
objc_setAssociatedObject(o, &associateKey, nil, .OBJC_ASSOCIATION_ASSIGN)
}
}
deinit {
action?()
block?()
}
}

View File

@ -1,59 +0,0 @@
//
// Deprecated.swift
// Schedule
//
// Created by Quentin Jin on 2019/3/4.
// Copyright © 2019 Schedule. All rights reserved.
//
import Foundation
extension Monthday {
/// A Boolean value indicating whether today is the monthday.
@available(*, deprecated, message: "Use date.is(_:Monthday)")
public var isToday: Bool {
let components = toDateComponents()
let m = Calendar.gregorian.component(.month, from: Date())
let d = Calendar.gregorian.component(.day, from: Date())
return m == components.month && d == components.day
}
}
extension Weekday {
/// A Boolean value indicating whether today is the weekday.
@available(*, deprecated, message: "Use date.is(_:Weekday)")
public var isToday: Bool {
return Calendar.gregorian.component(.weekday, from: Date()) == rawValue
}
}
extension Time {
/// The interval between this time and zero o'clock.
@available(*, deprecated, renamed: "intervalSinceStartOfDay")
public var intervalSinceZeroClock: Interval {
return hour.hours + minute.minutes + second.seconds + nanosecond.nanoseconds
}
}
extension Plan {
/// Creates a plan from an interval sequence.
/// The task will be executed after each interval in the sequence.
@available(*, deprecated, message: "Use Plan.of")
public static func from<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Interval {
return Plan.of(sequence)
}
/// Creates a plan from a date sequence.
/// The task will be executed at each date in the sequence.
/// - Note: Returns `Plan.never` if given no parameters.
@available(*, deprecated, message: "Use Plan.of")
public static func from<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Date {
return Plan.make(sequence.makeIterator)
}
}

View File

@ -2,6 +2,7 @@ import Foundation
extension Double {
/// Returns a value of this number clamped to `Int.min...Int.max`.
func clampedToInt() -> Int {
if self >= Double(Int.max) { return Int.max }
if self <= Double(Int.min) { return Int.min }
@ -11,13 +12,11 @@ extension Double {
extension Int {
/// Returns the sum of the two given values, in case of any overflow,
/// the result will be clamped to int.
func clampedAdding(_ other: Int) -> Int {
return (Double(self) + Double(other)).clampedToInt()
}
func clampedSubtracting(_ other: Int) -> Int {
return clampedAdding(-other)
}
}
extension Calendar {
@ -47,9 +46,9 @@ extension NSLocking {
return try body()
}
/// Executes a closure returning a value while acquiring the lock.
/// Executes a closure while acquiring the lock.
@inline(__always)
func withLock(_ body: () throws -> Void) rethrows {
func withLockVoid(_ body: () throws -> Void) rethrows {
lock(); defer { unlock() }
try body()
}

View File

@ -1,6 +1,6 @@
import Foundation
/// `Interval` represents a length of time.
/// Type used to represent a time-based amount of time, such as '34.5 seconds'.
public struct Interval {
/// The length of this interval in nanoseconds.
@ -13,36 +13,26 @@ public struct Interval {
}
// MARK: - Describing
extension Interval {
/// A boolean value indicating whether this interval is less than zero.
/// A Boolean value indicating whether this interval is less than zero.
///
/// An interval can be negative:
///
/// - The interval from 6:00 to 7:00 is `1.hour`,
/// but the interval from 7:00 to 6:00 is `-1.hour`.
/// In this case, `-1.hour` means **one hour ago**.
///
/// - The interval comparing `3.hour` to `1.hour` is `2.hour`,
/// but the interval comparing `1.hour` to `3.hour` is `-2.hour`.
/// In this case, `-2.hour` means **two hours shorter**
/// An Interval represents a directed distance between two points
/// on the time-line and can therefore be positive, zero or negative.
public var isNegative: Bool {
return nanoseconds < 0
}
/// See `isNegative`.
public var isPositive: Bool {
return nanoseconds > 0
/// A copy of this duration with a positive length.
public var abs: Interval {
return Interval(nanoseconds: Swift.abs(nanoseconds))
}
/// The absolute value of the length of this interval in nanoseconds.
public var magnitude: Double {
return nanoseconds.magnitude
}
/// The additive inverse of this interval.
public var opposite: Interval {
return (-nanoseconds).nanoseconds
/// A copy of this interval with the length negated.
public var negated: Interval {
return Interval(nanoseconds: -nanoseconds)
}
}
@ -75,18 +65,22 @@ extension Interval: CustomDebugStringConvertible {
}
// MARK: - Comparing
extension Interval: Comparable {
/// Compares two intervals.
/// Compares two intervals and returns a comparison result value
/// that indicates the sort order of two intervals.
///
/// The comparison is magnitude independent, a positive interval is
/// always ordered ascending to a negative interval.
/// A positive interval is always ordered ascending to a negative interval.
public func compare(_ other: Interval) -> ComparisonResult {
let now = Date()
return now.adding(self).compare(now.adding(other))
let d = nanoseconds - other.nanoseconds
if d < 0 { return .orderedAscending }
if d > 0 { return .orderedDescending }
return .orderedSame
}
/// Returns a boolean value indicating whether the first interval is
/// Returns a Boolean value indicating whether the first interval is
/// less than the second interval.
///
/// A negative interval is always less than a positive interval.
@ -94,43 +88,23 @@ extension Interval: Comparable {
return lhs.compare(rhs) == .orderedAscending
}
/// Returns a boolean value indicating whether this interval is longer
/// than the given value.
/// Returns a Boolean value indicating whether this interval is longer
/// than the given interval.
public func isLonger(than other: Interval) -> Bool {
return magnitude > other.magnitude
return abs > other.abs
}
/// Returns a boolean value indicating whether this interval is shorter
/// than the given value.
/// Returns a Boolean value indicating whether this interval is shorter
/// than the given interval.
public func isShorter(than other: Interval) -> Bool {
return magnitude < other.magnitude
}
/// Returns the longest interval of the given values.
public static func longest(_ intervals: Interval...) -> Interval {
return longest(intervals)!
}
/// Returns the longest interval of the given values.
public static func longest(_ intervals: [Interval]) -> Interval? {
guard !intervals.isEmpty else { return nil }
return intervals.sorted(by: { $0.magnitude > $1.magnitude })[0]
}
/// Returns the shortest interval of the given values.
public static func shortest(_ intervals: Interval...) -> Interval {
return shortest(intervals)!
}
/// Returns the shortest interval of the given values.
public static func shortest(_ intervals: [Interval]) -> Interval? {
guard !intervals.isEmpty else { return nil }
return intervals.sorted(by: { $0.magnitude < $1.magnitude })[0]
return abs < other.abs
}
}
// MARK: - Adding & Subtracting
extension Interval {
/// Returns a new interval by multipling this interval by the given number.
///
/// 1.hour * 2 == 2.hours
@ -144,13 +118,6 @@ extension Interval {
public func adding(_ other: Interval) -> Interval {
return Interval(nanoseconds: nanoseconds + other.nanoseconds)
}
/// Returns a new interval by subtracting the given interval from this interval.
///
/// 2.hours - 1.hour == 1.hour
public func subtracting(_ other: Interval) -> Interval {
return Interval(nanoseconds: nanoseconds - other.nanoseconds)
}
}
// MARK: - Operators
@ -174,21 +141,22 @@ extension Interval {
///
/// 2.hours - 1.hour == 1.hour
public static func - (lhs: Interval, rhs: Interval) -> Interval {
return lhs.subtracting(rhs)
return lhs.adding(rhs.negated)
}
/// Adds two intervals and stores the result in the first interval.
/// Adds two intervals and stores the result in the left interval.
public static func += (lhs: inout Interval, rhs: Interval) {
lhs = lhs.adding(rhs)
}
/// Returns the additive inverse of the specified interval.
public prefix static func - (interval: Interval) -> Interval {
return interval.opposite
return interval.negated
}
}
// MARK: - Sugars
extension Interval {
/// Creates an interval from the given number of seconds.
@ -245,6 +213,7 @@ public protocol IntervalConvertible {
extension Int: IntervalConvertible {
/// Creates an interval from this amount of nanoseconds.
public var nanoseconds: Interval {
return Interval(nanoseconds: Double(self))
}
@ -252,6 +221,7 @@ extension Int: IntervalConvertible {
extension Double: IntervalConvertible {
/// Creates an interval from this amount of nanoseconds.
public var nanoseconds: Interval {
return Interval(nanoseconds: self)
}
@ -259,80 +229,96 @@ extension Double: IntervalConvertible {
extension IntervalConvertible {
// Alias for `nanoseconds`.
public var nanosecond: Interval {
return nanoseconds
}
// Alias for `microseconds`.
public var microsecond: Interval {
return microseconds
}
/// Creates an interval from this amount of microseconds.
public var microseconds: Interval {
return nanoseconds * pow(10, 3)
}
/// Alias for `milliseconds`.
public var millisecond: Interval {
return milliseconds
}
/// Creates an interval from this amount of milliseconds.
public var milliseconds: Interval {
return nanoseconds * pow(10, 6)
}
/// Alias for `second`.
public var second: Interval {
return seconds
}
/// Creates an interval from this amount of seconds.
public var seconds: Interval {
return nanoseconds * pow(10, 9)
}
/// Alias for `minute`.
public var minute: Interval {
return minutes
}
/// Creates an interval from this amount of minutes.
public var minutes: Interval {
return seconds * 60
}
/// Alias for `hours`.
public var hour: Interval {
return hours
}
/// Creates an interval from this amount of hours.
public var hours: Interval {
return minutes * 60
}
/// Alias for `days`.
public var day: Interval {
return days
}
/// Creates an interval from this amount of days.
public var days: Interval {
return hours * 24
}
/// Alias for `weeks`.
public var week: Interval {
return weeks
}
/// Creates an interval from this amount of weeks.
public var weeks: Interval {
return days * 7
}
}
// MARK: - Date
extension Date {
/// The interval between this date and the current date and time.
///
/// If this date is earlier than now, this interval will be negative.
/// If this date is earlier than now, the interval will be negative.
public var intervalSinceNow: Interval {
return timeIntervalSinceNow.seconds
}
/// Returns the interval between this date and the given date.
///
/// If this date is earlier than the given date, this interval will be negative.
/// If this date is earlier than the given date, the interval will be negative.
public func interval(since date: Date) -> Interval {
return timeIntervalSince(date).seconds
}
@ -342,18 +328,19 @@ extension Date {
return addingTimeInterval(interval.asSeconds())
}
/// Returns a date with an interval added to it.
/// Returns a new date by adding an interval to the date.
public static func + (lhs: Date, rhs: Interval) -> Date {
return lhs.adding(rhs)
}
}
// MARK: - DispatchSourceTimer
extension DispatchSourceTimer {
/// Schedule this timer later.
func schedule(after timeout: Interval) {
guard !timeout.isNegative else { return }
if timeout.isNegative { return }
let ns = timeout.nanoseconds.clampedToInt()
schedule(wallDeadline: .now() + DispatchTimeInterval.nanoseconds(ns))
}

View File

@ -1,6 +1,6 @@
import Foundation
/// `Monthday` represents a day of a month.
/// `Monthday` represents the combination of a month and day-of-month.
public enum Monthday {
case january(Int)
@ -27,9 +27,9 @@ public enum Monthday {
case december(Int)
/// Returns a dateComponenets of the monthday, using gregorian calender and
/// Returns a dateComponenets of this monthday, using gregorian calender and
/// current time zone.
public func toDateComponents() -> DateComponents {
public func asDateComponents() -> DateComponents {
var month, day: Int
switch self {
case .january(let n): month = 1; day = n
@ -49,16 +49,15 @@ public enum Monthday {
calendar: Calendar.gregorian,
timeZone: TimeZone.current,
month: month,
day: day
)
day: day)
}
}
extension Date {
/// Returns a boolean value indicating whether today is the monthday.
/// Returns a Boolean value indicating whether this date is the monthday.
public func `is`(_ monthday: Monthday) -> Bool {
let components = monthday.toDateComponents()
let components = monthday.asDateComponents()
let m = Calendar.gregorian.component(.month, from: self)
let d = Calendar.gregorian.component(.day, from: self)
@ -72,7 +71,7 @@ extension Monthday: CustomStringConvertible {
///
/// "Monthday: May 1st"
public var description: String {
let components = toDateComponents()
let components = asDateComponents()
let m = components.month!
let d = components.day!

View File

@ -1,6 +1,7 @@
import Foundation
/// `Period` represents a period of time defined in terms of fields.
/// Type used to represents a date-based amount of time in the ISO-8601 calendar system,
/// such as '2 years, 3 months and 4 days'.
///
/// It's a little different from `Interval`:
///
@ -51,104 +52,113 @@ public struct Period {
/// Period.registerQuantifier("fifty", for: 15)
/// let period = Period("fifty minutes")
public static func registerQuantifier(_ word: String, for number: Int) {
quantifiers.write { $0[word] = number }
quantifiers.writeVoid { $0[word] = number }
}
/// Creates a period from a natural expression.
/// Initializes a period from a natural expression.
///
/// Period("one second") => Period(seconds: 1)
/// Period("two hours and ten minutes") => Period(hours: 2, minutes: 10)
/// Period("1 year, 2 months and 3 days") => Period(years: 1, months: 2, days: 3)
/// Period("one second") -> Period(seconds: 1)
/// Period("two hours and ten minutes") -> Period(hours: 2, minutes: 10)
/// Period("1 year, 2 months and 3 days") -> Period(years: 1, months: 2, days: 3)
public init?(_ string: String) {
var period = string
var str = string
for (word, number) in Period.quantifiers.read({ $0 }) {
period = period.replacingOccurrences(of: word, with: "\(number)")
str = str.replacingOccurrences(of: word, with: "\(number)")
}
guard let regex = try? NSRegularExpression(pattern: "( and |, )") else {
guard let regexp = try? NSRegularExpression(pattern: "( and |, )") else {
return nil
}
period = regex.stringByReplacingMatches(in: period, range: NSRange(location: 0, length: period.count), withTemplate: "$")
str = regexp.stringByReplacingMatches(in: str, range: NSRange(location: 0, length: str.count), withTemplate: "$")
var period = 0.year
for pair in str.split(separator: "$").map({ $0.split(separator: " ") }) {
guard
pair.count == 2,
let number = Int(pair[0])
else {
return nil
}
var result = 0.year
for pair in period.split(separator: "$").map({ $0.split(separator: " ") }) {
guard pair.count == 2 else { return nil }
guard let number = Int(pair[0]) else { return nil }
var unit = String(pair[1])
if unit.last == "s" { unit.removeLast() }
switch unit {
case "year": result = result + number.years
case "month": result = result + number.months
case "day": result = result + number.days
case "week": result = result + (number * 7).days
case "hour": result = result + number.hours
case "minute": result = result + number.minutes
case "second": result = result + number.second
case "nanosecond": result = result + number.nanosecond
case "year": period = period + number.years
case "month": period = period + number.months
case "day": period = period + number.days
case "week": period = period + (number * 7).days
case "hour": period = period + number.hours
case "minute": period = period + number.minutes
case "second": period = period + number.second
case "nanosecond": period = period + number.nanosecond
default: break
}
}
self = result.tidied(to: .day)
self = period
}
/// Returns a new period by adding a period to this period.
/// Returns a new period by adding the given period to this period.
public func adding(_ other: Period) -> Period {
return Period(years: years.clampedAdding(other.years),
months: months.clampedAdding(other.months),
days: days.clampedAdding(other.days),
hours: hours.clampedAdding(other.hours),
minutes: minutes.clampedAdding(other.minutes),
seconds: seconds.clampedAdding(other.seconds),
nanoseconds: nanoseconds.clampedAdding(other.nanoseconds))
return Period(
years: years.clampedAdding(other.years),
months: months.clampedAdding(other.months),
days: days.clampedAdding(other.days),
hours: hours.clampedAdding(other.hours),
minutes: minutes.clampedAdding(other.minutes),
seconds: seconds.clampedAdding(other.seconds),
nanoseconds: nanoseconds.clampedAdding(other.nanoseconds))
}
/// Returns a new period by adding an interval to this period.
/// Returns a new period by adding an interval to the period.
public func adding(_ interval: Interval) -> Period {
return Period(
years: years, months: months, days: days,
hours: hours, minutes: minutes, seconds: seconds,
nanoseconds: nanoseconds.clampedAdding(interval.nanoseconds.clampedToInt())
).tidied(to: .day)
nanoseconds: nanoseconds.clampedAdding(interval.nanoseconds.clampedToInt()))
}
/// Adds two periods and produces their sum.
/// Returns a new period by adding the right period to the left period.
///
/// Period(days: 1) + Period(days: 1) -> Period(days: 2)
public static func + (lhs: Period, rhs: Period) -> Period {
return lhs.adding(rhs)
}
/// Returns a period with an interval added to it.
/// Returns a new period by adding an interval to the period.
public static func + (lhs: Period, rhs: Interval) -> Period {
return lhs.adding(rhs)
}
/// Type to be used as tidying parameter.
public enum Unit {
/// Represents the tidy level.
public enum TideLevel {
case day, hour, minute, second, nanosecond
}
/// Returns a tidied period.
/// Returns the tidied period.
///
/// Period(hours: 25).tidied(to .day) => Period(days: 1, hours: 1)
public func tidied(to unit: Unit) -> Period {
public func tidied(to level: TideLevel) -> Period {
var period = self
if case .nanosecond = unit { return period }
if case .nanosecond = level { return period }
if period.nanoseconds.magnitude >= UInt(1.second.nanoseconds) {
period.seconds += period.nanoseconds / Int(1.second.nanoseconds)
period.nanoseconds %= Int(1.second.nanoseconds)
}
if case .second = level { return period }
if case .second = unit { return period }
if period.seconds.magnitude >= 60 {
period.minutes += period.seconds / 60
period.seconds %= 60
}
if case .minute = level { return period }
if case .minute = unit { return period }
if period.minutes.magnitude >= 60 {
period.hours += period.minutes / 60
period.minutes %= 60
}
if case .hour = level { return period }
if case .hour = unit { return period }
if period.hours.magnitude >= 24 {
period.days += period.hours / 24
period.hours %= 24
@ -156,9 +166,9 @@ public struct Period {
return period
}
/// Returns a dateComponenets of the period, using gregorian calender and
/// Returns a dateComponenets of this period, using gregorian calender and
/// current time zone.
public func toDateComponents() -> DateComponents {
public func asDateComponents() -> DateComponents {
return DateComponents(year: years, month: months, day: days,
hour: hours, minute: minutes, second: seconds,
nanosecond: nanoseconds)
@ -169,10 +179,10 @@ extension Date {
/// Returns a new date by adding a period to this date.
public func adding(_ period: Period) -> Date {
return Calendar.gregorian.date(byAdding: period.toDateComponents(), to: self) ?? .distantFuture
return Calendar.gregorian.date(byAdding: period.asDateComponents(), to: self) ?? .distantFuture
}
/// Returns a date with a period added to it.
/// Returns a new date by adding a period to this date.
public static func + (lhs: Date, rhs: Period) -> Date {
return lhs.adding(rhs)
}
@ -180,18 +190,22 @@ extension Date {
extension Int {
/// Creates a period from this amount of years.
public var years: Period {
return Period(years: self)
}
/// Alias for `years`.
public var year: Period {
return years
}
/// Creates a period from this amount of month.
public var months: Period {
return Period(months: self)
}
/// Alias for `month`.
public var month: Period {
return months
}

View File

@ -302,7 +302,7 @@ extension Plan {
var last: Date!
return AnyIterator {
last = last ?? Date()
guard let next = calendar.date(byAdding: period.toDateComponents(),
guard let next = calendar.date(byAdding: period.asDateComponents(),
to: last) else {
return nil
}
@ -410,7 +410,7 @@ extension Plan {
} else if Date().is(weekday) {
date = Date().startOfToday
} else {
let components = weekday.toDateComponents()
let components = weekday.asDateComponents()
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
}
return date
@ -450,7 +450,7 @@ extension Plan {
} else if Date().is(monthday) {
date = Date().startOfToday
} else {
let components = monthday.toDateComponents()
let components = monthday.asDateComponents()
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
}
return date

View File

@ -94,7 +94,7 @@ open class Task {
}
private func scheduleNext() {
_mutex.withLock {
_mutex.withLockVoid {
let now = Date()
var estimated = _timeline.estimatedNextExecution ?? now
repeat {
@ -155,7 +155,7 @@ open class Task {
/// Reschedules this task with the new plan.
public func reschedule(_ new: Plan) {
_mutex.withLock {
_mutex.withLockVoid {
_iterator = new.makeIterator()
}
scheduleNext()
@ -170,7 +170,7 @@ open class Task {
/// Suspends this task.
public func suspend() {
_mutex.withLock {
_mutex.withLockVoid {
if _suspensions < UInt64.max {
_timer.suspend()
_suspensions += 1
@ -180,7 +180,7 @@ open class Task {
/// Resumes this task.
public func resume() {
_mutex.withLock {
_mutex.withLockVoid {
if _suspensions > 0 {
_timer.resume()
_suspensions -= 1
@ -190,7 +190,7 @@ open class Task {
/// Cancels this task.
public func cancel() {
_mutex.withLock {
_mutex.withLockVoid {
_timer.cancel()
}
TaskCenter.default.remove(self)
@ -234,7 +234,9 @@ open class Task {
/// - Returns: `true` if set successfully, `false` if not.
@discardableResult
public func setLifetime(_ interval: Interval) -> Bool {
guard restOfLifetime.isPositive else { return false }
if restOfLifetime.isNegative {
return false
}
_mutex.lock()
let age = Date().interval(since: _timeline.initialization)
@ -258,10 +260,10 @@ open class Task {
@discardableResult
public func addLifetime(_ interval: Interval) -> Bool {
var rest = restOfLifetime
guard rest.isPositive else { return false }
if rest.isNegative { return false }
rest += interval
guard rest.isPositive else { return false }
_mutex.withLock {
if rest.isNegative { return false }
_mutex.withLockVoid {
_lifetime += interval
_lifetimeTimer.schedule(after: rest)
}
@ -276,7 +278,7 @@ open class Task {
/// - Returns: `true` if set successfully, `false` if not.
@discardableResult
public func subtractLifetime(_ interval: Interval) -> Bool {
return addLifetime(interval.opposite)
return addLifetime(interval.negated)
}
// MARK: - Action
@ -298,15 +300,15 @@ open class Task {
/// Removes action by key from this task.
public func removeAction(byKey key: ActionKey) {
_mutex.withLock {
_ = _onElapseActions.delete(key.bagKey)
_mutex.withLockVoid {
_ = _onElapseActions.removeValue(for: key.bagKey)
}
}
/// Removes all actions from this task.
public func removeAllActions() {
_mutex.withLock {
_onElapseActions.clear()
_mutex.withLockVoid {
_onElapseActions.removeAll()
}
}

View File

@ -53,7 +53,7 @@ open class TaskCenter {
task.taskCenterMutex.unlock()
mutex.withLock {
mutex.withLockVoid {
let box = TaskBox(task)
tagMap[box] = []
}
@ -75,7 +75,7 @@ open class TaskCenter {
task.taskCenterMutex.unlock()
mutex.withLock {
mutex.withLockVoid {
let box = TaskBox(task)
if let tags = self.tagMap[box] {
for tag in tags {
@ -99,7 +99,7 @@ open class TaskCenter {
open func addTags(_ tags: [String], to task: Task) {
guard task.taskCenter === self else { return }
mutex.withLock {
mutex.withLockVoid {
let box = TaskBox(task)
if tagMap[box] == nil {
tagMap[box] = []
@ -127,7 +127,7 @@ open class TaskCenter {
open func removeTags(_ tags: [String], from task: Task) {
guard task.taskCenter === self else { return }
mutex.withLock {
mutex.withLockVoid {
let box = TaskBox(task)
for tag in tags {
tagMap[box]?.remove(tag)
@ -170,7 +170,7 @@ open class TaskCenter {
/// Removes all tasks in this center.
open func clear() {
mutex.withLock {
mutex.withLockVoid {
tagMap = [:]
taskMap = [:]
}

View File

@ -15,9 +15,7 @@ public struct Time {
/// Nanosecond of second.
public let nanosecond: Int
/// Creates a time with `hour`, `minute`, `second` and `nanosecond` fields.
///
/// If any parameter is illegal, return nil.
/// Initializes a time with `hour`, `minute`, `second` and `nanosecond`.
///
/// Time(hour: 11, minute: 11) => "11:11:00.000"
/// Time(hour: 25) => nil
@ -36,34 +34,29 @@ public struct Time {
self.nanosecond = nanosecond
}
/// Creates a time with a string.
/// Initializes a time with a string.
///
/// If the parameter is illegal, return nil.
///
/// Time("11") == Time(hour: 11)
/// Time("11:12") == Time(hour: 11, minute: 12)
/// Time("11:12:13") == Time(hour: 11, minute: 12, second: 13)
/// Time("11:12:13.123") == Time(hour: 11, minute: 12, second: 13, nanosecond: 123000000)
/// Time("11") -> Time(hour: 11)
/// Time("11:12") -> Time(hour: 11, minute: 12)
/// Time("11:12:13") -> Time(hour: 11, minute: 12, second: 13)
/// Time("11:12:13.123") -> Time(hour: 11, minute: 12, second: 13, nanosecond: 123000000)
///
/// Time("-1.0") == nil
///
/// Any of the previous examples can have a period suffix("am", "AM", "pm", "PM"),
/// separated by a space.
///
/// Time("11 pm") == Time(hour: 23)
/// Time("11:12:13 PM") == Time(hour: 23, minute: 12, second: 13)
public init?(_ string: String) {
let pattern = "^(\\d{1,2})(:(\\d{1,2})(:(\\d{1,2})(.(\\d{1,3}))?)?)?( (am|AM|pm|PM))?$"
guard let regexp = try? NSRegularExpression(pattern: pattern, options: []) else { return nil }
guard let result = regexp.matches(in: string, options: [], range: NSRange(location: 0, length: string.count)).first else { return nil }
guard let matches = regexp.matches(in: string, options: [], range: NSRange(location: 0, length: string.count)).first else { return nil }
var hasAM = false
var hasPM = false
var values: [Int] = []
for i in 0..<result.numberOfRanges {
let range = result.range(at: i)
for i in 0..<matches.numberOfRanges {
let range = matches.range(at: i)
if range.length == 0 { continue }
let captured = NSString(string: string).substring(with: range)
hasAM = ["am", "AM"].contains(captured)
@ -94,7 +87,7 @@ public struct Time {
/// Returns a dateComponenets of the time, using gregorian calender and
/// current time zone.
public func toDateComponents() -> DateComponents {
public func asDateComponents() -> DateComponents {
return DateComponents(calendar: Calendar.gregorian,
timeZone: TimeZone.current,
hour: hour,

View File

@ -7,18 +7,17 @@ public enum Weekday: Int {
/// Returns dateComponenets of the weekday, using gregorian calender and
/// current time zone.
public func toDateComponents() -> DateComponents {
public func asDateComponents() -> DateComponents {
return DateComponents(
calendar: Calendar.gregorian,
timeZone: TimeZone.current,
weekday: rawValue
)
weekday: rawValue)
}
}
extension Date {
/// Returns a boolean value indicating whether this day is the weekday.
/// Returns a Boolean value indicating whether this date is the weekday.
public func `is`(_ weekday: Weekday) -> Bool {
return Calendar.gregorian.component(.weekday, from: self) == weekday.rawValue
}

View File

@ -3,29 +3,42 @@ import XCTest
final class AtomicTests: XCTestCase {
func testSnapshot() {
let i = Atomic<Int>(1)
XCTAssertEqual(i.snapshot(), 1)
}
func testRead() {
let i = Atomic<Int>(1)
i.read {
XCTAssertEqual($0, 1)
}
let val = i.read { $0 }
XCTAssertEqual(val, 1)
}
func testReadVoid() {
let i = Atomic<Int>(1)
var val = 0
i.read { val = $0 }
XCTAssertEqual(val, 1)
}
func testWrite() {
let i = Atomic<Int>(1)
let val = i.write { v -> Int in
v += 1
return v
}
XCTAssertEqual(i.read { $0 }, val)
}
func testWriteVoid() {
let i = Atomic<Int>(1)
var val = 0
i.write {
$0 += 1
val = $0
}
XCTAssertEqual(i.snapshot(), 2)
XCTAssertEqual(i.read { $0 }, val)
}
static var allTests = [
("testSnapshot", testSnapshot),
("testRead", testRead),
("testWrite", testWrite)
("testReadVoid", testReadVoid),
("testWrite", testWrite),
("testWriteVoid", testWriteVoid),
]
}

View File

@ -15,66 +15,80 @@ final class BagTests: XCTestCase {
}
func testAppend() {
var cabinet = Bag<Fn>()
cabinet.append { 1 }
cabinet.append { 2 }
var bag = Bag<Fn>()
bag.append { 1 }
bag.append { 2 }
XCTAssertEqual(cabinet.count, 2)
XCTAssertEqual(bag.count, 2)
}
func testGet() {
var cabinet = Bag<Fn>()
let k1 = cabinet.append { 1 }
let k2 = cabinet.append { 2 }
func testValueForKey() {
var bag = Bag<Fn>()
let k1 = bag.append { 1 }
let k2 = bag.append { 2 }
guard
let fn1 = cabinet.get(k1),
let fn2 = cabinet.get(k2)
else {
XCTFail()
return
}
XCTAssertEqual(fn1(), 1)
XCTAssertEqual(fn2(), 2)
}
func testDelete() {
var cabinet = Bag<Fn>()
let k1 = cabinet.append { 1 }
let k2 = cabinet.append { 2 }
XCTAssertEqual(cabinet.count, 2)
let fn1 = cabinet.delete(k1)
let fn1 = bag.value(for: k1)
XCTAssertNotNil(fn1)
let fn2 = cabinet.delete(k2)
let fn2 = bag.value(for: k2)
XCTAssertNotNil(fn2)
XCTAssertEqual(cabinet.count, 0)
guard let _fn1 = fn1, let _fn2 = fn2 else { return }
XCTAssertEqual(_fn1(), 1)
XCTAssertEqual(_fn2(), 2)
}
func testClear() {
var cabinet = Bag<Fn>()
func testRemoveValueForKey() {
var bag = Bag<Fn>()
cabinet.append { 1 }
cabinet.append { 2 }
let k1 = bag.append { 1 }
let k2 = bag.append { 2 }
XCTAssertEqual(cabinet.count, 2)
let fn1 = bag.removeValue(for: k1)
XCTAssertNotNil(fn1)
cabinet.clear()
XCTAssertEqual(cabinet.count, 0)
let fn2 = bag.removeValue(for: k2)
XCTAssertNotNil(fn2)
guard let _fn1 = fn1, let _fn2 = fn2 else { return }
XCTAssertEqual(_fn1(), 1)
XCTAssertEqual(_fn2(), 2)
}
func testCount() {
var bag = Bag<Fn>()
let k1 = bag.append { 1 }
let k2 = bag.append { 2 }
XCTAssertEqual(bag.count, 2)
bag.removeValue(for: k1)
bag.removeValue(for: k2)
XCTAssertEqual(bag.count, 0)
}
func testRemoveAll() {
var bag = Bag<Fn>()
bag.append { 1 }
bag.append { 2 }
bag.removeAll()
XCTAssertEqual(bag.count, 0)
}
func testSequence() {
var cabinet = Bag<Fn>()
cabinet.append { 0 }
cabinet.append { 1 }
cabinet.append { 2 }
var bag = Bag<Fn>()
bag.append { 0 }
bag.append { 1 }
bag.append { 2 }
var i = 0
for fn in cabinet {
for fn in bag {
XCTAssertEqual(fn(), i)
i += 1
}
@ -83,9 +97,10 @@ final class BagTests: XCTestCase {
static var allTests = [
("testBagKey", testBagKey),
("testAppend", testAppend),
("testGet", testGet),
("testDelete", testDelete),
("testClear", testClear),
("testValueForKey", testValueForKey),
("testRemoveValueForKey", testRemoveValueForKey),
("testCount", testCount),
("testRemoveAll", testRemoveAll),
("testSequence", testSequence)
]
}

View File

@ -6,9 +6,8 @@ final class DateTimeTests: XCTestCase {
func testInterval() {
XCTAssertTrue((-1).second.isNegative)
XCTAssertTrue(1.second.isPositive)
XCTAssertEqual(1.1.second.magnitude, 1.1.second.nanoseconds)
XCTAssertEqual(1.second.opposite, (-1).second)
XCTAssertEqual(1.1.second.abs, 1.1.second)
XCTAssertEqual(1.second.negated, (-1).second)
XCTAssertEqual(7.day.hashValue, 1.week.hashValue)
XCTAssertEqual(7.day, 1.week)
@ -21,10 +20,6 @@ final class DateTimeTests: XCTestCase {
XCTAssertTrue(1.1.second.isLonger(than: 1.0.second))
XCTAssertTrue(3.days.isShorter(than: 1.week))
XCTAssertEqual(Interval.longest(1.hour, 1.day, 1.week), 1.week)
XCTAssertEqual(Interval.longest([]), nil)
XCTAssertEqual(Interval.shortest(1.hour, 59.minutes, 2999.seconds), 2999.seconds)
XCTAssertEqual(Interval.shortest([]), nil)
XCTAssertEqual(1.second * 60, 1.minute)
XCTAssertEqual(59.minutes + 60.seconds, 1.hour)
@ -55,18 +50,18 @@ final class DateTimeTests: XCTestCase {
let d = Date(year: 2019, month: 1, day: 1)
XCTAssertTrue(d.is(.january(1)))
XCTAssertEqual(Monthday.january(1).toDateComponents().month, 1)
XCTAssertEqual(Monthday.february(1).toDateComponents().month, 2)
XCTAssertEqual(Monthday.march(1).toDateComponents().month, 3)
XCTAssertEqual(Monthday.april(1).toDateComponents().month, 4)
XCTAssertEqual(Monthday.may(1).toDateComponents().month, 5)
XCTAssertEqual(Monthday.june(1).toDateComponents().month, 6)
XCTAssertEqual(Monthday.july(1).toDateComponents().month, 7)
XCTAssertEqual(Monthday.august(1).toDateComponents().month, 8)
XCTAssertEqual(Monthday.september(1).toDateComponents().month, 9)
XCTAssertEqual(Monthday.october(1).toDateComponents().month, 10)
XCTAssertEqual(Monthday.november(1).toDateComponents().month, 11)
XCTAssertEqual(Monthday.december(1).toDateComponents().month, 12)
XCTAssertEqual(Monthday.january(1).asDateComponents().month, 1)
XCTAssertEqual(Monthday.february(1).asDateComponents().month, 2)
XCTAssertEqual(Monthday.march(1).asDateComponents().month, 3)
XCTAssertEqual(Monthday.april(1).asDateComponents().month, 4)
XCTAssertEqual(Monthday.may(1).asDateComponents().month, 5)
XCTAssertEqual(Monthday.june(1).asDateComponents().month, 6)
XCTAssertEqual(Monthday.july(1).asDateComponents().month, 7)
XCTAssertEqual(Monthday.august(1).asDateComponents().month, 8)
XCTAssertEqual(Monthday.september(1).asDateComponents().month, 9)
XCTAssertEqual(Monthday.october(1).asDateComponents().month, 10)
XCTAssertEqual(Monthday.november(1).asDateComponents().month, 11)
XCTAssertEqual(Monthday.december(1).asDateComponents().month, 12)
}
func testPeriod() {
@ -113,7 +108,7 @@ final class DateTimeTests: XCTestCase {
XCTAssertTrue(i.isAlmostEqual(to: (0.456.second.nanoseconds).nanoseconds, leeway: 0.001.seconds))
}
let components = t1?.toDateComponents()
let components = t1?.asDateComponents()
XCTAssertEqual(components?.hour, 11)
XCTAssertEqual(components?.minute, 12)
XCTAssertEqual(components?.second, 13)
@ -140,7 +135,7 @@ final class DateTimeTests: XCTestCase {
let d = Date(year: 2019, month: 1, day: 1)
XCTAssertTrue(d.is(.tuesday))
XCTAssertEqual(Weekday.monday.toDateComponents().weekday!, 2)
XCTAssertEqual(Weekday.monday.asDateComponents().weekday!, 2)
}
static var allTests = [

View File

@ -5,9 +5,9 @@ import XCTest
final class DeinitObserverTests: XCTestCase {
func testObserver() {
func testObserve() {
var i = 0
var fn = {
let fn = {
let obj = NSObject()
DeinitObserver.observe(obj) {
i += 1
@ -15,8 +15,11 @@ final class DeinitObserverTests: XCTestCase {
}
fn()
XCTAssertEqual(i, 1)
}
fn = {
func testCancel() {
var i = 0
let fn = {
let obj = NSObject()
let observer = DeinitObserver.observe(obj) {
i += 1
@ -24,11 +27,12 @@ final class DeinitObserverTests: XCTestCase {
observer.cancel()
}
fn()
XCTAssertEqual(i, 1)
XCTAssertEqual(i, 0)
}
static var allTests = [
("testObserver", testObserver)
("testObserve", testObserve),
("testCancel", testCancel)
]
}

View File

@ -16,11 +16,6 @@ final class ExtensionsTests: XCTestCase {
XCTAssertEqual(i.clampedAdding(1), Int.max)
}
func testClampedSubtracting() {
let i = Int.min
XCTAssertEqual(i.clampedSubtracting(1), Int.min)
}
func testStartOfToday() {
let components = Date().startOfToday.dateComponents
guard
@ -39,7 +34,6 @@ final class ExtensionsTests: XCTestCase {
static var allTests = [
("testClampedToInt", testClampedToInt),
("testClampedAdding", testClampedAdding),
("testClampedSubtracting", testClampedSubtracting),
("testStartOfToday", testStartOfToday)
]
}

View File

@ -26,7 +26,7 @@ extension Date {
extension Interval {
func isAlmostEqual(to interval: Interval, leeway: Interval) -> Bool {
return (interval - self).magnitude <= leeway.magnitude
return (interval - self).abs <= leeway.abs
}
}

View File

@ -9,8 +9,6 @@ final class PlanTests: XCTestCase {
let intervals = [1.second, 2.hours, 3.days, 4.weeks]
let s0 = Plan.of(intervals[0], intervals[1], intervals[2], intervals[3])
XCTAssertTrue(s0.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
let s1 = Plan.from(intervals)
XCTAssertTrue(s1.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
let d0 = Date() + intervals[0]
let d1 = d0 + intervals[1]
@ -42,15 +40,15 @@ final class PlanTests: XCTestCase {
func testConcat() {
let s0: [Interval] = [1.second, 2.minutes, 3.hours]
let s1: [Interval] = [4.days, 5.weeks]
let s3 = Plan.from(s0).concat(Plan.from(s1))
let s4 = Plan.from(s0 + s1)
let s3 = Plan.of(s0).concat(Plan.of(s1))
let s4 = Plan.of(s0 + s1)
XCTAssertTrue(s3.isAlmostEqual(to: s4, leeway: leeway))
}
func testMerge() {
let intervals0: [Interval] = [1.second, 2.minutes, 1.hour]
let intervals1: [Interval] = [2.seconds, 1.minutes, 1.seconds]
let scheudle0 = Plan.from(intervals0).merge(Plan.from(intervals1))
let scheudle0 = Plan.of(intervals0).merge(Plan.of(intervals1))
let scheudle1 = Plan.of(1.second, 1.second, 1.minutes, 1.seconds, 58.seconds, 1.hour)
XCTAssertTrue(scheudle0.isAlmostEqual(to: scheudle1, leeway: leeway))
}