diff --git a/README.md b/README.md index 8bb2b45..88c9764 100755 --- a/README.md +++ b/README.md @@ -26,13 +26,6 @@ init(components: DayComponents) init(_ year: Int, _ month: Int, _ day: Int) init(year: Int, month: Int, day: Int) ``` - -# Codable - -`Day` is fully `Codable`. - -It's base value is an `Int` representing the number of days since 1 January 1970 which can accessed via the `.daysSince1970` property. - # Properties ## .daysSince1970 @@ -181,12 +174,59 @@ Day(2000,1,10) - Day(2000,1,5) // -> 5 days duration. Similar to the way `Date` has a matching `DateComponents`, `Day` has a matching `DayComponents`. In this case mostly as a convenient wrapper for passing the individual values for a year, month and day. +# Conformance + +## Codable + +`Day` is fully `Codable`. + +It's base value is an `Int` representing the number of days since 1 January 1970 which can accessed via the `.daysSince1970` property. + + +## Equatable + +`Day` is `Equatable` so + +```swift +Day(2001,2,3) == Day(2001,2,3) // true +``` + +## Comparable + +`Day` is `Comparable` which lets you use all the comparable operators to compare dates. ie. `>`, `<`, `>=` and `<=`. + +## Hashable + +`Day` is `Hashable` so it can be used as dictionary keys and in sets. + +## Stridable + +`Day` is `Stridable` which means you can use it in for loops as well as with the `stride(from:to:by:)` function. + +```swift +for day in Day(2000,1,1)...Day(2000,1,5) { + /// do something with the 1st, 2nd, 3rd, 4th and 5th. +} + +for day in Day(2000,1,1).. Date Using a passed `Calendar` and `TimeZone`, this function coverts a `Day` to a Swift `Date` with the `Day`'s year, month and day, and a time of `00:00` (midnight). With no arguments this function uses the current calendar and time zone. +## .day(byAdding:, value:) -> Day + +Lets you add any number of years, months or days to a `Day` and get a new `day` back. This is convenient for doing things like producing a sequence of dates for the same day on each month. + ## .formatted(_:) -> String Wrapping `Date.formatted(date:time:)` this function formats a day using the standard formatting specified by the `Date.FormatStyle.DateStyle` styles. The time component of `Date.formatted(date:time:)` is omitted. diff --git a/Sources/Day+Core.swift b/Sources/Day+Core.swift new file mode 100644 index 0000000..0aba4ef --- /dev/null +++ b/Sources/Day+Core.swift @@ -0,0 +1,26 @@ +// +// Day+Hashable.swift +// +// +// Created by Derek Clarkson on 10/1/2024. +// + +import Foundation + +extension Day: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(daysSince1970) + } +} + +extension Day: Equatable { + public static func == (lhs: Day, rhs: Day) -> Bool { + lhs.daysSince1970 == rhs.daysSince1970 + } +} + +extension Day: Comparable { + public static func < (lhs: Day, rhs: Day) -> Bool { + lhs.daysSince1970 < rhs.daysSince1970 + } +} diff --git a/Sources/Day+CustomStringConvertable.swift b/Sources/Day+CustomStringConvertable.swift new file mode 100644 index 0000000..e1bc2a2 --- /dev/null +++ b/Sources/Day+CustomStringConvertable.swift @@ -0,0 +1,14 @@ +// +// Day+CustomStringConvertable.swift +// +// +// Created by Derek Clarkson on 15/1/2024. +// + +import Foundation + +extension Day: CustomStringConvertible { + public var description: String { + self.formatted() + } +} diff --git a/Sources/Day+Functions.swift b/Sources/Day+Functions.swift new file mode 100644 index 0000000..9118c6b --- /dev/null +++ b/Sources/Day+Functions.swift @@ -0,0 +1,24 @@ +// +// Day+Functions.swift +// +// +// Created by Derek Clarkson on 16/1/2024. +// + +import Foundation + +public extension Day { + + /// Adds the specific value to the current `Day` and returns a new `Day`. + /// + /// This is convenient for doing things like generating dates for the same day of each month. Note though that + /// if the added value would produce an invaid date, the actual `Day` returned contains a valid date produced by + /// rolling the values through the subsequent days and months. ie. if you do `Day(2001,2,3).day(byAdding: .day, value: 55)` you will get + /// + func day(byAdding component: Day.Component, value: Int) -> Day { + let components = self.dayComponents() + return Day(components.year + (component == .year ? value : 0), components.month + (component == .month ? value : 0), components.day + (component == .day ? value : 0)) + } + + +} diff --git a/Sources/Day+Hashable.swift b/Sources/Day+Hashable.swift deleted file mode 100644 index bbe38be..0000000 --- a/Sources/Day+Hashable.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Day+Hashable.swift -// -// -// Created by Derek Clarkson on 10/1/2024. -// - -import Foundation - -extension Day: Hashable { - - public func hash(into hasher: inout Hasher) { - hasher.combine(daysSince1970) - } -} diff --git a/Sources/Day+Operations.swift b/Sources/Day+Operations.swift index 6593375..934b2db 100644 --- a/Sources/Day+Operations.swift +++ b/Sources/Day+Operations.swift @@ -35,14 +35,3 @@ public extension Day { } } -extension Day: Equatable { - public static func == (lhs: Day, rhs: Day) -> Bool { - lhs.daysSince1970 == rhs.daysSince1970 - } -} - -extension Day: Comparable { - public static func < (lhs: Day, rhs: Day) -> Bool { - lhs.daysSince1970 < rhs.daysSince1970 - } -} diff --git a/Sources/Day+Strideable.swift b/Sources/Day+Strideable.swift new file mode 100644 index 0000000..ecfd657 --- /dev/null +++ b/Sources/Day+Strideable.swift @@ -0,0 +1,19 @@ +// +// Day+Stridable.swift +// +// +// Created by Derek Clarkson on 15/1/2024. +// + +import Foundation + +extension Day: Strideable { + + public func distance(to other: Day) -> Int { + other.daysSince1970 - self.daysSince1970 + } + + public func advanced(by n: Int) -> Day { + self + n + } +} diff --git a/Sources/Day.swift b/Sources/Day.swift index 4612dee..43e5a26 100755 --- a/Sources/Day.swift +++ b/Sources/Day.swift @@ -20,6 +20,13 @@ public typealias DayInterval = Int /// day is consistent for that timezone. public struct Day { + /// Used by functions to modify days. + public enum Component { + case year + case month + case day + } + public let daysSince1970: Int // MARK: - Initialisers diff --git a/Tests/DayCoreTests.swift b/Tests/DayCoreTests.swift new file mode 100644 index 0000000..0f2703b --- /dev/null +++ b/Tests/DayCoreTests.swift @@ -0,0 +1,68 @@ +// +// DayOperationsTests.swift +// +// +// Created by Derek Clarkson on 12/12/2023. +// + +import DayType +import Nimble +import XCTest + +class DayHashableTests: XCTestCase { + + func testHash() { + var days: Set = [Day(2020, 01, 11), Day(2020, 01, 12)] + expect(days.contains(Day(2020, 01, 13))) == false + expect(days.contains(Day(2020, 01, 12))) == true + + // Modify and try again. + days.insert(Day(2020, 01, 13)) + expect(days.contains(Day(2020, 01, 13))) == true + expect(days.contains(Day(2020, 01, 12))) == true + + // Duplicate check. + days.insert(Day(2020, 01, 11)) + expect(days.count) == 3 + } +} + +class DayEquatableTests: XCTestCase { + + func testEquals() { + expect(Day(2020, 3, 12) == Day(2020, 3, 12)) == true + expect(Day(2020, 3, 12) == Day(2001, 1, 5)) == false + } + + func testNotEquals() { + expect(Day(2020, 3, 12) != Day(2001, 1, 5)) == true + expect(Day(2020, 3, 12) != Day(2020, 3, 12)) == false + } +} + +class DayComparableTests: XCTestCase { + + func testGreaterThan() { + expect(Day(2020, 3, 12) > Day(2020, 3, 11)) == true + expect(Day(2020, 3, 12) > Day(2020, 3, 12)) == false + expect(Day(2020, 3, 12) > Day(2020, 3, 13)) == false + } + + func testGreaterThanEquals() { + expect(Day(2020, 3, 12) >= Day(2020, 3, 11)) == true + expect(Day(2020, 3, 12) >= Day(2020, 3, 12)) == true + expect(Day(2020, 3, 12) >= Day(2020, 3, 13)) == false + } + + func testLessThan() { + expect(Day(2020, 3, 12) < Day(2020, 3, 11)) == false + expect(Day(2020, 3, 12) < Day(2020, 3, 12)) == false + expect(Day(2020, 3, 12) < Day(2020, 3, 13)) == true + } + + func testLessThanEquals() { + expect(Day(2020, 3, 12) <= Day(2020, 3, 11)) == false + expect(Day(2020, 3, 12) <= Day(2020, 3, 12)) == true + expect(Day(2020, 3, 12) <= Day(2020, 3, 13)) == true + } +} diff --git a/Tests/DayCustomStringConvertableTests.swift b/Tests/DayCustomStringConvertableTests.swift new file mode 100644 index 0000000..5d9fcca --- /dev/null +++ b/Tests/DayCustomStringConvertableTests.swift @@ -0,0 +1,17 @@ +// +// DayCustomStringConvertableTests.swift +// +// +// Created by Derek Clarkson on 15/1/2024. +// + +import DayType +import Nimble +import XCTest + +class DayCustomStringConvertableTests: XCTestCase { + func testyDescription() { + let date = DateComponents(calendar: .current, year: 2001, month: 2, day: 3).date! + expect(Day(2001, 2, 3).description) == date.formatted(date: .abbreviated, time: .omitted) + } +} diff --git a/Tests/DayFunctionsTests.swift b/Tests/DayFunctionsTests.swift new file mode 100644 index 0000000..c169478 --- /dev/null +++ b/Tests/DayFunctionsTests.swift @@ -0,0 +1,24 @@ +// +// DayFunctionsTests.swift +// +// +// Created by Derek Clarkson on 16/1/2024. +// + +import DayType +import Nimble +import XCTest + +class DayFunctionsTests: XCTestCase { + + func testDayByAdding() { + expect(Day(2001, 2, 3).day(byAdding: .day, value: 3)) == Day(2001, 2, 6) + expect(Day(2001, 2, 3).day(byAdding: .month, value: 3)) == Day(2001, 5, 3) + expect(Day(2001, 2, 3).day(byAdding: .year, value: 3)) == Day(2004, 2, 3) + } + + func testDayByAddingRolling() { + expect(Day(2001, 2, 3).day(byAdding: .day, value: 55)) == Day(2001, 3, 30) + expect(Day(2001, 2, 3).day(byAdding: .month, value: 55)) == Day(2005, 9, 10) + } +} diff --git a/Tests/DayHashableTests.swift b/Tests/DayHashableTests.swift deleted file mode 100644 index 274a236..0000000 --- a/Tests/DayHashableTests.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// DayHashableTests.swift -// -// -// Created by Derek Clarkson on 10/1/2024. -// - -import Nimble -import XCTest -import DayType - -class DayHasableTests: XCTestCase { - - func testHash() { - var days: Set = [Day(2020, 01, 11), Day(2020, 01, 12)] - expect(days.contains(Day(2020, 01, 13))) == false - expect(days.contains(Day(2020, 01, 12))) == true - - // Modify and try again. - days.insert(Day(2020, 01, 13)) - expect(days.contains(Day(2020, 01, 13))) == true - expect(days.contains(Day(2020, 01, 12))) == true - - // Duplicate check. - days.insert(Day(2020, 01, 11)) - expect(days.count) == 3 - } -} diff --git a/Tests/DayOperationsTests.swift b/Tests/DayOperationsTests.swift index 1896c73..48b4ce7 100644 --- a/Tests/DayOperationsTests.swift +++ b/Tests/DayOperationsTests.swift @@ -32,47 +32,6 @@ class DayOperationTests: XCTestCase { } func testDayDiff() { - expect(Day(2020, 3, 12) - Day(2020, 3, 6)) == 6 + expect(Day(2020, 3, 12) - Day(2020, 3, 6)) == 6 } } - -class DayEquatableTests: XCTestCase { - - func testEquals() { - expect(Day(2020, 3, 12) == Day(2020, 3, 12)) == true - expect(Day(2020, 3, 12) == Day(2001, 1, 5)) == false - } - - func testNotEquals() { - expect(Day(2020, 3, 12) != Day(2001, 1, 5)) == true - expect(Day(2020, 3, 12) != Day(2020, 3, 12)) == false - } -} - -class DayComparableTests: XCTestCase { - - func testGreaterThan() { - expect(Day(2020, 3, 12) > Day(2020, 3, 11)) == true - expect(Day(2020, 3, 12) > Day(2020, 3, 12)) == false - expect(Day(2020, 3, 12) > Day(2020, 3, 13)) == false - } - - func testGreaterThanEquals() { - expect(Day(2020, 3, 12) >= Day(2020, 3, 11)) == true - expect(Day(2020, 3, 12) >= Day(2020, 3, 12)) == true - expect(Day(2020, 3, 12) >= Day(2020, 3, 13)) == false - } - - func testLessThan() { - expect(Day(2020, 3, 12) < Day(2020, 3, 11)) == false - expect(Day(2020, 3, 12) < Day(2020, 3, 12)) == false - expect(Day(2020, 3, 12) < Day(2020, 3, 13)) == true - } - - func testLessThanEquals() { - expect(Day(2020, 3, 12) <= Day(2020, 3, 11)) == false - expect(Day(2020, 3, 12) <= Day(2020, 3, 12)) == true - expect(Day(2020, 3, 12) <= Day(2020, 3, 13)) == true - } - -} diff --git a/Tests/DayStrideableTests.swift b/Tests/DayStrideableTests.swift new file mode 100644 index 0000000..e1bc4fe --- /dev/null +++ b/Tests/DayStrideableTests.swift @@ -0,0 +1,42 @@ +// +// Day+Stridable.swift +// +// +// Created by Derek Clarkson on 15/1/2024. +// + +import Foundation +import DayType +import XCTest +import Nimble + +class DayStrideableTests: XCTestCase { + + func testForEachOpenRange() { + var days: [Day] = [] + for day in Day(2000,1,1)...Day(2000,1,5) { + days.append(day) + } + expect(days) == [Day(2000,1,1), Day(2000,1,2), Day(2000,1,3), Day(2000,1,4), Day(2000,1,5)] + } + + func testForEachHalfOpenRange() { + var days: [Day] = [] + for day in Day(2000,1,1).. Bool { + + let dayComponents = day.dayComponents() + let dateComponents = Calendar.current.dateComponents([.year, .month, .day], from: date) + + guard dayComponents.year == dateComponents.year, + dayComponents.month == dateComponents.month, + dayComponents.day == dateComponents.day else { + print("Date components: \(dateComponents)") + print("Day components : \(dayComponents)") + fail("Day from date and back to date failed", file: file, line: line) + return false + } + return true + } }