Skip to content

Commit 0a66272

Browse files
committed
Merge branch 'release/4.1.0' into versions
2 parents 0c7ecab + 1ea6ae3 commit 0a66272

File tree

6 files changed

+145
-10
lines changed

6 files changed

+145
-10
lines changed

Sources/HandySwift/Extensions/TimeIntervalExt.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,10 @@ extension TimeInterval {
150150
@available(iOS 16, macOS 13, tvOS 16, visionOS 1, watchOS 9, *)
151151
public func duration() -> Duration {
152152
let fullSeconds = Int64(self.seconds)
153-
let remainingInterval = self - .seconds(Double(fullSeconds))
153+
let remainingInterval = self - Double(fullSeconds)
154154

155155
let attosecondsPerNanosecond = Double(1_000 * 1_000 * 1_000)
156-
let fullAttoseconds = Int64(remainingInterval.nanoseconds / attosecondsPerNanosecond)
156+
let fullAttoseconds = Int64(remainingInterval.nanoseconds * attosecondsPerNanosecond)
157157

158158
return Duration(secondsComponent: fullSeconds, attosecondsComponent: fullAttoseconds)
159159
}

Sources/HandySwift/Globals.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public func delay(by timeInterval: TimeInterval, qosClass: DispatchQoS.QoSClass?
1717
/// - duration: The duration of the delay. E.g., `.seconds(1)` or `.milliseconds(200)`.
1818
/// - qosClass: The global QoS class to be used or `nil` to use the main thread. Defaults to `nil`.
1919
/// - closure: The code to run with a delay.
20+
@_disfavoredOverload
2021
@available(iOS 16, macOS 13, tvOS 16, visionOS 1, watchOS 9, *)
2122
public func delay(by duration: Duration, qosClass: DispatchQoS.QoSClass? = nil, _ closure: @escaping () -> Void) {
2223
let dispatchQueue = qosClass != nil ? DispatchQueue.global(qos: qosClass!) : DispatchQueue.main

Sources/HandySwift/Types/GregorianDay.swift

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ import Foundation
66
/// ```swift
77
/// let yesterday = GregorianDay.yesterday
88
/// print(yesterday.iso8601Formatted) // Prints the current date in ISO 8601 format, e.g. "2024-03-20"
9-
///
9+
///
1010
/// let tomorrow = yesterday.advanced(by: 2)
1111
/// let timCookBirthday = GregorianDay(year: 1960, month: 11, day: 01)
1212
///
1313
/// let startOfDay = GregorianDay(date: Date()).startOfDay()
1414
/// ```
1515
public struct GregorianDay {
1616
/// The year component of the date.
17-
public let year: Int
17+
public var year: Int
1818
/// The month component of the date.
19-
public let month: Int
19+
public var month: Int
2020
/// The day component of the date.
21-
public let day: Int
21+
public var day: Int
2222

2323
/// Returns an ISO 8601 formatted String representation of the date, e.g., `2024-02-24`.
2424
public var iso8601Formatted: String {
@@ -76,6 +76,66 @@ public struct GregorianDay {
7676
self.advanced(by: -days)
7777
}
7878

79+
/// Advances the date by the specified number of months.
80+
///
81+
/// - Parameter months: The number of months to advance the date by.
82+
/// - Returns: A new `GregorianDay` instance advanced by the specified number of months.
83+
///
84+
/// - 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.
85+
///
86+
/// Example:
87+
/// ```swift
88+
/// let tomorrow = GregorianDay.today.advanced(byMonths: 1)
89+
/// ```
90+
public func advanced(byMonths months: Int) -> Self {
91+
let (overflowingYears, newMonth) = (self.month + months - 1).quotientAndRemainder(dividingBy: 12)
92+
return self.with { $0.year += overflowingYears; $0.month = newMonth + 1 }
93+
}
94+
95+
/// Reverses the date by the specified number of months.
96+
///
97+
/// - Parameter months: The number of months to reverse the date by.
98+
/// - Returns: A new `GregorianDay` instance reversed by the specified number of months.
99+
///
100+
/// - 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.
101+
///
102+
/// Example:
103+
/// ```swift
104+
/// let yesterday = GregorianDay.today.reversed(byMonths: 1)
105+
/// ```
106+
public func reversed(byMonths months: Int) -> Self {
107+
self.advanced(byMonths: -months)
108+
}
109+
110+
/// Advances the date by the specified number of years.
111+
///
112+
/// - Parameter years: The number of years to advance the date by.
113+
/// - Returns: A new `GregorianDay` instance advanced by the specified number of years. The day and month stay the same.
114+
///
115+
/// - 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.
116+
///
117+
/// Example:
118+
/// ```swift
119+
/// let tomorrow = GregorianDay.today.advanced(byYears: 1)
120+
/// ```
121+
public func advanced(byYears years: Int) -> Self {
122+
self.with { $0.year += years }
123+
}
124+
125+
/// Reverses the date by the specified number of years.
126+
///
127+
/// - Parameter years: The number of years to reverse the date by.
128+
/// - Returns: A new `GregorianDay` instance reversed by the specified number of years. The day and month stay the same.
129+
///
130+
/// - 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.
131+
/// Example:
132+
/// ```swift
133+
/// let yesterday = GregorianDay.today.reversed(byYears: 1)
134+
/// ```
135+
public func reversed(byYears years: Int) -> Self {
136+
self.advanced(byYears: -years)
137+
}
138+
79139
/// Returns the start of the day represented by the date.
80140
///
81141
/// - Parameter timeZone: The time zone for which to calculate the start of the day. Defaults to the users current timezone.
@@ -96,6 +156,46 @@ public struct GregorianDay {
96156
return components.date!
97157
}
98158

159+
/// Returns the start of the month represented by the date.
160+
///
161+
/// - Parameter timeZone: The time zone for which to calculate the start of the month. Defaults to the users current timezone.
162+
/// - Returns: A `Date` representing the start of the month.
163+
///
164+
/// Example:
165+
/// ```swift
166+
/// let startOfThisMonth = GregorianDay.today.startOfMonth()
167+
/// ```
168+
public func startOfMonth(timeZone: TimeZone = .current) -> Date {
169+
let components = DateComponents(
170+
calendar: Calendar(identifier: .gregorian),
171+
timeZone: timeZone,
172+
year: self.year,
173+
month: self.month,
174+
day: 1
175+
)
176+
return components.date!
177+
}
178+
179+
/// Returns the start of the year represented by the date.
180+
///
181+
/// - Parameter timeZone: The time zone for which to calculate the start of the year. Defaults to the users current timezone.
182+
/// - Returns: A `Date` representing the start of the year.
183+
///
184+
/// Example:
185+
/// ```swift
186+
/// let startOfThisYear = GregorianDay.today.startOfYear()
187+
/// ```
188+
public func startOfYear(timeZone: TimeZone = .current) -> Date {
189+
let components = DateComponents(
190+
calendar: Calendar(identifier: .gregorian),
191+
timeZone: timeZone,
192+
year: self.year,
193+
month: 1,
194+
day: 1
195+
)
196+
return components.date!
197+
}
198+
99199
/// Returns the middle of the day represented by the date.
100200
///
101201
/// - Parameter timeZone: The time zone for which to calculate the middle of the day. Defaults to UTC.
@@ -180,3 +280,5 @@ extension GregorianDay {
180280
/// The `GregorianDay` representing tomorrow's date.
181281
public static var tomorrow: Self { GregorianDay(date: Date()).advanced(by: 1) }
182282
}
283+
284+
extension GregorianDay: Withable {}

Sources/HandySwift/Types/GregorianTimeOfDay.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ import Foundation
2222
/// ```
2323
public struct GregorianTimeOfDay {
2424
/// The number of days beyond the current day.
25-
public let overflowingDays: Int
25+
public var overflowingDays: Int
2626
/// The hour component of the time.
27-
public let hour: Int
27+
public var hour: Int
2828
/// The minute component of the time.
29-
public let minute: Int
29+
public var minute: Int
3030
/// The second component of the time.
31-
public let second: Int
31+
public var second: Int
3232

3333
/// Initializes a `GregorianTimeOfDay` instance from a given date.
3434
///
@@ -141,3 +141,5 @@ extension GregorianTimeOfDay {
141141
/// The current time of day.
142142
public static var now: Self { GregorianTimeOfDay(date: Date()) }
143143
}
144+
145+
extension GregorianTimeOfDay: Withable {}

Tests/HandySwiftTests/Extensions/TimeIntervalExtTests.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,10 @@ class TimeIntervalExtTests: XCTestCase {
2424
XCTAssertEqual(multipledTimeInterval.microseconds, 12 * 60 * 60 * 1_000_000, accuracy: 0.001)
2525
XCTAssertEqual(multipledTimeInterval.nanoseconds, 12 * 60 * 60 * 1_000_000_000, accuracy: 0.001)
2626
}
27+
28+
func testDurationConversion() {
29+
XCTAssertEqual(TimeInterval.milliseconds(0.999).duration().timeInterval.milliseconds, 0.999, accuracy: 0.000001)
30+
XCTAssertEqual(TimeInterval.seconds(2.5).duration().timeInterval.seconds, 2.5, accuracy: 0.001)
31+
XCTAssertEqual(TimeInterval.days(5).duration().timeInterval.days, 5, accuracy: 0.001)
32+
}
2733
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Foundation
2+
3+
@testable import HandySwift
4+
import XCTest
5+
6+
final class GregorianDayTests: XCTestCase {
7+
func testAdvancedByMonths() {
8+
let day = GregorianDay(year: 2024, month: 03, day: 26)
9+
let advancedByAMonth = day.advanced(byMonths: 1)
10+
11+
XCTAssertEqual(advancedByAMonth.year, 2024)
12+
XCTAssertEqual(advancedByAMonth.month, 04)
13+
XCTAssertEqual(advancedByAMonth.day, 26)
14+
}
15+
16+
func testReversedByYears() {
17+
let day = GregorianDay(year: 2024, month: 03, day: 26)
18+
let reversedByTwoYears = day.reversed(byYears: 2)
19+
20+
XCTAssertEqual(reversedByTwoYears.year, 2022)
21+
XCTAssertEqual(reversedByTwoYears.month, 03)
22+
XCTAssertEqual(reversedByTwoYears.day, 26)
23+
}
24+
}

0 commit comments

Comments
 (0)