From ff6d1c06845462cd294b97651aceeff6d0699e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20G=C3=BCnd=C3=BCz?= Date: Tue, 26 Mar 2024 02:49:59 +0100 Subject: [PATCH 1/9] Make GregorianDay & GegorianTimeOfDay conform to Withable --- Sources/HandySwift/Types/GregorianDay.swift | 2 ++ Sources/HandySwift/Types/GregorianTimeOfDay.swift | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Sources/HandySwift/Types/GregorianDay.swift b/Sources/HandySwift/Types/GregorianDay.swift index ee365aa..477ca67 100644 --- a/Sources/HandySwift/Types/GregorianDay.swift +++ b/Sources/HandySwift/Types/GregorianDay.swift @@ -180,3 +180,5 @@ extension GregorianDay { /// The `GregorianDay` representing tomorrow's date. public static var tomorrow: Self { GregorianDay(date: Date()).advanced(by: 1) } } + +extension GregorianDay: Withable {} diff --git a/Sources/HandySwift/Types/GregorianTimeOfDay.swift b/Sources/HandySwift/Types/GregorianTimeOfDay.swift index 733562a..af1cea1 100644 --- a/Sources/HandySwift/Types/GregorianTimeOfDay.swift +++ b/Sources/HandySwift/Types/GregorianTimeOfDay.swift @@ -141,3 +141,5 @@ extension GregorianTimeOfDay { /// The current time of day. public static var now: Self { GregorianTimeOfDay(date: Date()) } } + +extension GregorianTimeOfDay: Withable {} From 31b24a7693401f3359fa03d1453f17b769882ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20G=C3=BCnd=C3=BCz?= Date: Tue, 26 Mar 2024 02:51:43 +0100 Subject: [PATCH 2/9] Make properties of GregorianDay & GregorianTimeOfDay variable --- Sources/HandySwift/Types/GregorianDay.swift | 6 +++--- Sources/HandySwift/Types/GregorianTimeOfDay.swift | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/HandySwift/Types/GregorianDay.swift b/Sources/HandySwift/Types/GregorianDay.swift index 477ca67..7ae2d5d 100644 --- a/Sources/HandySwift/Types/GregorianDay.swift +++ b/Sources/HandySwift/Types/GregorianDay.swift @@ -14,11 +14,11 @@ import Foundation /// ``` public struct GregorianDay { /// The year component of the date. - public let year: Int + public var year: Int /// The month component of the date. - public let month: Int + public var month: Int /// The day component of the date. - public let day: Int + public var day: Int /// Returns an ISO 8601 formatted String representation of the date, e.g., `2024-02-24`. public var iso8601Formatted: String { diff --git a/Sources/HandySwift/Types/GregorianTimeOfDay.swift b/Sources/HandySwift/Types/GregorianTimeOfDay.swift index af1cea1..289341d 100644 --- a/Sources/HandySwift/Types/GregorianTimeOfDay.swift +++ b/Sources/HandySwift/Types/GregorianTimeOfDay.swift @@ -22,13 +22,13 @@ import Foundation /// ``` public struct GregorianTimeOfDay { /// The number of days beyond the current day. - public let overflowingDays: Int + public var overflowingDays: Int /// The hour component of the time. - public let hour: Int + public var hour: Int /// The minute component of the time. - public let minute: Int + public var minute: Int /// The second component of the time. - public let second: Int + public var second: Int /// Initializes a `GregorianTimeOfDay` instance from a given date. /// From 1de026ae5f751ed4f7fdb68aea0482be71515337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20G=C3=BCnd=C3=BCz?= Date: Tue, 26 Mar 2024 03:02:13 +0100 Subject: [PATCH 3/9] Add startOfMonth and startOfYear methods to GregorianDay --- Sources/HandySwift/Types/GregorianDay.swift | 40 +++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Sources/HandySwift/Types/GregorianDay.swift b/Sources/HandySwift/Types/GregorianDay.swift index 7ae2d5d..fcf2615 100644 --- a/Sources/HandySwift/Types/GregorianDay.swift +++ b/Sources/HandySwift/Types/GregorianDay.swift @@ -96,6 +96,46 @@ public struct GregorianDay { return components.date! } + /// Returns the start of the month represented by the date. + /// + /// - Parameter timeZone: The time zone for which to calculate the start of the month. Defaults to the users current timezone. + /// - Returns: A `Date` representing the start of the month. + /// + /// Example: + /// ```swift + /// let startOfThisMonth = GregorianDay.today.startOfMonth() + /// ``` + public func startOfMonth(timeZone: TimeZone = .current) -> Date { + let components = DateComponents( + calendar: Calendar(identifier: .gregorian), + timeZone: timeZone, + year: self.year, + month: self.month, + day: 1 + ) + return components.date! + } + + /// Returns the start of the year represented by the date. + /// + /// - Parameter timeZone: The time zone for which to calculate the start of the year. Defaults to the users current timezone. + /// - Returns: A `Date` representing the start of the year. + /// + /// Example: + /// ```swift + /// let startOfThisYear = GregorianDay.today.startOfYear() + /// ``` + public func startOfYear(timeZone: TimeZone = .current) -> Date { + let components = DateComponents( + calendar: Calendar(identifier: .gregorian), + timeZone: timeZone, + year: self.year, + month: 1, + day: 1 + ) + return components.date! + } + /// Returns the middle of the day represented by the date. /// /// - Parameter timeZone: The time zone for which to calculate the middle of the day. Defaults to UTC. From 1ea761e253cb05b5122cb08f9e29f27e82cd70dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20G=C3=BCnd=C3=BCz?= Date: Tue, 26 Mar 2024 03:11:49 +0100 Subject: [PATCH 4/9] Add startOfNextDay, startOfNextMonth, and startOfNextYear methods to GregorianDay --- Sources/HandySwift/Types/GregorianDay.swift | 44 ++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/Sources/HandySwift/Types/GregorianDay.swift b/Sources/HandySwift/Types/GregorianDay.swift index fcf2615..3b9b93c 100644 --- a/Sources/HandySwift/Types/GregorianDay.swift +++ b/Sources/HandySwift/Types/GregorianDay.swift @@ -6,7 +6,7 @@ import Foundation /// ```swift /// let yesterday = GregorianDay.yesterday /// print(yesterday.iso8601Formatted) // Prints the current date in ISO 8601 format, e.g. "2024-03-20" -/// +/// /// let tomorrow = yesterday.advanced(by: 2) /// let timCookBirthday = GregorianDay(year: 1960, month: 11, day: 01) /// @@ -158,6 +158,48 @@ public struct GregorianDay { ) return components.date! } + + /// Returns the start of the next day from the date. + /// + /// - Parameter timeZone: The time zone for which to calculate the start of the next day. Defaults to the user's current timezone. + /// - Returns: A `Date` representing the start of the next day. + /// + /// Example: + /// ```swift + /// let startOfNextDay = GregorianDay.today.startOfNextDay() + /// ``` + public func startOfNextDay(timeZone: TimeZone = .current) -> Date { + self.advanced(by: 1).startOfDay() + } + + + /// Returns the start of the next month from the date. + /// + /// - Parameter timeZone: The time zone for which to calculate the start of the next month. Defaults to the user's current timezone. + /// - Returns: A `Date` representing the start of the next month. + /// + /// Example: + /// ```swift + /// let startOfNextMonth = GregorianDay.today.startOfNextMonth() + /// ``` + public func startOfNextMonth(timeZone: TimeZone = .current) -> Date { + guard self.month < 12 else { return self.startOfNextYear() } + return self.with { $0.month += 1 }.startOfDay() + } + + /// Returns the start of the next year from the date. + /// + /// - Parameter timeZone: The time zone for which to calculate the start of the next year. Defaults to the user's current timezone. + /// - Returns: A `Date` representing the start of the next year. + /// + /// Example: + /// ```swift + /// let startOfNextYear = GregorianDay.today.startOfNextYear() + /// ``` + public func startOfNextYear(timeZone: TimeZone = .current) -> Date { + self.with { $0.year += 1; $0.month = 1 }.startOfMonth() + } + } extension GregorianDay: Codable { From 970dad7abe465642bd31f4d91dd7cdb8cf9456cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20G=C3=BCnd=C3=BCz?= Date: Tue, 26 Mar 2024 03:18:03 +0100 Subject: [PATCH 5/9] Fix GregorianDay.startOfMonth implementation --- Sources/HandySwift/Types/GregorianDay.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HandySwift/Types/GregorianDay.swift b/Sources/HandySwift/Types/GregorianDay.swift index 3b9b93c..97591dc 100644 --- a/Sources/HandySwift/Types/GregorianDay.swift +++ b/Sources/HandySwift/Types/GregorianDay.swift @@ -184,7 +184,7 @@ public struct GregorianDay { /// ``` public func startOfNextMonth(timeZone: TimeZone = .current) -> Date { guard self.month < 12 else { return self.startOfNextYear() } - return self.with { $0.month += 1 }.startOfDay() + return self.with { $0.month += 1; $0.day = 1 }.startOfDay() } /// Returns the start of the next year from the date. From 6eeec4892e6dc4afcaf047c364b5c51409f791e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20G=C3=BCnd=C3=BCz?= Date: Tue, 26 Mar 2024 13:29:07 +0100 Subject: [PATCH 6/9] Fix a bug in the implementation of TimeInterval.duration() with attoseconds --- Sources/HandySwift/Extensions/TimeIntervalExt.swift | 4 ++-- Tests/HandySwiftTests/Extensions/TimeIntervalExtTests.swift | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/HandySwift/Extensions/TimeIntervalExt.swift b/Sources/HandySwift/Extensions/TimeIntervalExt.swift index a9cea3d..e69b9e1 100644 --- a/Sources/HandySwift/Extensions/TimeIntervalExt.swift +++ b/Sources/HandySwift/Extensions/TimeIntervalExt.swift @@ -150,10 +150,10 @@ extension TimeInterval { @available(iOS 16, macOS 13, tvOS 16, visionOS 1, watchOS 9, *) public func duration() -> Duration { let fullSeconds = Int64(self.seconds) - let remainingInterval = self - .seconds(Double(fullSeconds)) + let remainingInterval = self - Double(fullSeconds) let attosecondsPerNanosecond = Double(1_000 * 1_000 * 1_000) - let fullAttoseconds = Int64(remainingInterval.nanoseconds / attosecondsPerNanosecond) + let fullAttoseconds = Int64(remainingInterval.nanoseconds * attosecondsPerNanosecond) return Duration(secondsComponent: fullSeconds, attosecondsComponent: fullAttoseconds) } diff --git a/Tests/HandySwiftTests/Extensions/TimeIntervalExtTests.swift b/Tests/HandySwiftTests/Extensions/TimeIntervalExtTests.swift index 3e04691..9cbc9ef 100644 --- a/Tests/HandySwiftTests/Extensions/TimeIntervalExtTests.swift +++ b/Tests/HandySwiftTests/Extensions/TimeIntervalExtTests.swift @@ -24,4 +24,10 @@ class TimeIntervalExtTests: XCTestCase { XCTAssertEqual(multipledTimeInterval.microseconds, 12 * 60 * 60 * 1_000_000, accuracy: 0.001) XCTAssertEqual(multipledTimeInterval.nanoseconds, 12 * 60 * 60 * 1_000_000_000, accuracy: 0.001) } + + func testDurationConversion() { + XCTAssertEqual(TimeInterval.milliseconds(0.999).duration().timeInterval.milliseconds, 0.999, accuracy: 0.000001) + XCTAssertEqual(TimeInterval.seconds(2.5).duration().timeInterval.seconds, 2.5, accuracy: 0.001) + XCTAssertEqual(TimeInterval.days(5).duration().timeInterval.days, 5, accuracy: 0.001) + } } From 6beb09886ad68cf78b23a09ef8850d2f86d65bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20G=C3=BCnd=C3=BCz?= Date: Tue, 26 Mar 2024 14:29:26 +0100 Subject: [PATCH 7/9] =?UTF-8?q?Add=20advance=20&=20reverse=20methods=20by?= =?UTF-8?q?=20months=20&=20years=20(with=20warning=20=E2=80=93=20use=20wit?= =?UTF-8?q?h=20caution!)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/HandySwift/Types/GregorianDay.swift | 102 ++++++++++++-------- 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/Sources/HandySwift/Types/GregorianDay.swift b/Sources/HandySwift/Types/GregorianDay.swift index 97591dc..d0c4db0 100644 --- a/Sources/HandySwift/Types/GregorianDay.swift +++ b/Sources/HandySwift/Types/GregorianDay.swift @@ -76,6 +76,66 @@ public struct GregorianDay { self.advanced(by: -days) } + /// Advances the date by the specified number of months. + /// + /// - Parameter months: The number of months to advance the date by. + /// - Returns: A new `GregorianDay` instance advanced by the specified number of months. + /// + /// - Warning: This may return an invalid date such as February 31st. Only use in combination with a method like ``startOfMonth(timeZone:)`` that removes the day. + /// + /// Example: + /// ```swift + /// let tomorrow = GregorianDay.today.advanced(byMonths: 1) + /// ``` + public func advanced(byMonths months: Int) -> Self { + let (overflowingYears, newMonth) = (self.month + months - 1).quotientAndRemainder(dividingBy: 12) + return self.with { $0.year += overflowingYears; $0.month = newMonth } + } + + /// Reverses the date by the specified number of months. + /// + /// - Parameter months: The number of months to reverse the date by. + /// - Returns: A new `GregorianDay` instance reversed by the specified number of months. + /// + /// - Warning: This may return an invalid date such as February 31st. Only use in combination with a method like ``startOfMonth(timeZone:)`` that removes the day. + /// + /// Example: + /// ```swift + /// let yesterday = GregorianDay.today.reversed(byMonths: 1) + /// ``` + public func reversed(byMonths months: Int) -> Self { + self.advanced(byMonths: -months) + } + + /// Advances the date by the specified number of years. + /// + /// - Parameter years: The number of years to advance the date by. + /// - Returns: A new `GregorianDay` instance advanced by the specified number of years. The day and month stay the same. + /// + /// - Warning: This may return an invalid date such as February 31st. Only use in combination with a method like ``startOfMonth(timeZone:)`` that removes the day. + /// + /// Example: + /// ```swift + /// let tomorrow = GregorianDay.today.advanced(byYears: 1) + /// ``` + public func advanced(byYears years: Int) -> Self { + self.with { $0.year += years } + } + + /// Reverses the date by the specified number of years. + /// + /// - Parameter years: The number of years to reverse the date by. + /// - Returns: A new `GregorianDay` instance reversed by the specified number of years. The day and month stay the same. + /// + /// - Warning: This may return an invalid date such as February 31st. Only use in combination with a method like ``startOfMonth(timeZone:)`` that removes the day. + /// Example: + /// ```swift + /// let yesterday = GregorianDay.today.reversed(byYears: 1) + /// ``` + public func reversed(byYears years: Int) -> Self { + self.advanced(byMonths: -years) + } + /// Returns the start of the day represented by the date. /// /// - Parameter timeZone: The time zone for which to calculate the start of the day. Defaults to the users current timezone. @@ -158,48 +218,6 @@ public struct GregorianDay { ) return components.date! } - - /// Returns the start of the next day from the date. - /// - /// - Parameter timeZone: The time zone for which to calculate the start of the next day. Defaults to the user's current timezone. - /// - Returns: A `Date` representing the start of the next day. - /// - /// Example: - /// ```swift - /// let startOfNextDay = GregorianDay.today.startOfNextDay() - /// ``` - public func startOfNextDay(timeZone: TimeZone = .current) -> Date { - self.advanced(by: 1).startOfDay() - } - - - /// Returns the start of the next month from the date. - /// - /// - Parameter timeZone: The time zone for which to calculate the start of the next month. Defaults to the user's current timezone. - /// - Returns: A `Date` representing the start of the next month. - /// - /// Example: - /// ```swift - /// let startOfNextMonth = GregorianDay.today.startOfNextMonth() - /// ``` - public func startOfNextMonth(timeZone: TimeZone = .current) -> Date { - guard self.month < 12 else { return self.startOfNextYear() } - return self.with { $0.month += 1; $0.day = 1 }.startOfDay() - } - - /// Returns the start of the next year from the date. - /// - /// - Parameter timeZone: The time zone for which to calculate the start of the next year. Defaults to the user's current timezone. - /// - Returns: A `Date` representing the start of the next year. - /// - /// Example: - /// ```swift - /// let startOfNextYear = GregorianDay.today.startOfNextYear() - /// ``` - public func startOfNextYear(timeZone: TimeZone = .current) -> Date { - self.with { $0.year += 1; $0.month = 1 }.startOfMonth() - } - } extension GregorianDay: Codable { From 8d18f32edbe3afc3e906454afe2404d56fff0789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20G=C3=BCnd=C3=BCz?= Date: Tue, 26 Mar 2024 14:45:07 +0100 Subject: [PATCH 8/9] Add tests for new GregorianDay methods & fix bugs --- Sources/HandySwift/Types/GregorianDay.swift | 4 ++-- .../Structs/GregorianDayTests.swift | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 Tests/HandySwiftTests/Structs/GregorianDayTests.swift diff --git a/Sources/HandySwift/Types/GregorianDay.swift b/Sources/HandySwift/Types/GregorianDay.swift index d0c4db0..c61cceb 100644 --- a/Sources/HandySwift/Types/GregorianDay.swift +++ b/Sources/HandySwift/Types/GregorianDay.swift @@ -89,7 +89,7 @@ public struct GregorianDay { /// ``` public func advanced(byMonths months: Int) -> Self { let (overflowingYears, newMonth) = (self.month + months - 1).quotientAndRemainder(dividingBy: 12) - return self.with { $0.year += overflowingYears; $0.month = newMonth } + return self.with { $0.year += overflowingYears; $0.month = newMonth + 1 } } /// Reverses the date by the specified number of months. @@ -133,7 +133,7 @@ public struct GregorianDay { /// let yesterday = GregorianDay.today.reversed(byYears: 1) /// ``` public func reversed(byYears years: Int) -> Self { - self.advanced(byMonths: -years) + self.advanced(byYears: -years) } /// Returns the start of the day represented by the date. diff --git a/Tests/HandySwiftTests/Structs/GregorianDayTests.swift b/Tests/HandySwiftTests/Structs/GregorianDayTests.swift new file mode 100644 index 0000000..15da86a --- /dev/null +++ b/Tests/HandySwiftTests/Structs/GregorianDayTests.swift @@ -0,0 +1,24 @@ +import Foundation + +@testable import HandySwift +import XCTest + +final class GregorianDayTests: XCTestCase { + func testAdvancedByMonths() { + let day = GregorianDay(year: 2024, month: 03, day: 26) + let advancedByAMonth = day.advanced(byMonths: 1) + + XCTAssertEqual(advancedByAMonth.year, 2024) + XCTAssertEqual(advancedByAMonth.month, 04) + XCTAssertEqual(advancedByAMonth.day, 26) + } + + func testReversedByYears() { + let day = GregorianDay(year: 2024, month: 03, day: 26) + let reversedByTwoYears = day.reversed(byYears: 2) + + XCTAssertEqual(reversedByTwoYears.year, 2022) + XCTAssertEqual(reversedByTwoYears.month, 03) + XCTAssertEqual(reversedByTwoYears.day, 26) + } +} From 1ea6ae35bffb736b5a702b289efd7e00d764c5c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20G=C3=BCnd=C3=BCz?= Date: Fri, 29 Mar 2024 20:39:44 +0100 Subject: [PATCH 9/9] Fix 'ambiguous use of X' building error on delay function --- Sources/HandySwift/Globals.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/HandySwift/Globals.swift b/Sources/HandySwift/Globals.swift index c0e942f..6d90289 100644 --- a/Sources/HandySwift/Globals.swift +++ b/Sources/HandySwift/Globals.swift @@ -17,6 +17,7 @@ public func delay(by timeInterval: TimeInterval, qosClass: DispatchQoS.QoSClass? /// - duration: The duration of the delay. E.g., `.seconds(1)` or `.milliseconds(200)`. /// - qosClass: The global QoS class to be used or `nil` to use the main thread. Defaults to `nil`. /// - closure: The code to run with a delay. +@_disfavoredOverload @available(iOS 16, macOS 13, tvOS 16, visionOS 1, watchOS 9, *) public func delay(by duration: Duration, qosClass: DispatchQoS.QoSClass? = nil, _ closure: @escaping () -> Void) { let dispatchQueue = qosClass != nil ? DispatchQueue.global(qos: qosClass!) : DispatchQueue.main