Skip to content

Commit

Permalink
Merge branch 'release/4.1.0' into versions
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeehut committed Mar 29, 2024
2 parents 0c7ecab + 1ea6ae3 commit 0a66272
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 10 deletions.
4 changes: 2 additions & 2 deletions Sources/HandySwift/Extensions/TimeIntervalExt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions Sources/HandySwift/Globals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
110 changes: 106 additions & 4 deletions Sources/HandySwift/Types/GregorianDay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ 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)
///
/// let startOfDay = GregorianDay(date: Date()).startOfDay()
/// ```
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 {
Expand Down Expand Up @@ -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 + 1 }
}

/// 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(byYears: -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.
Expand All @@ -96,6 +156,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.
Expand Down Expand Up @@ -180,3 +280,5 @@ extension GregorianDay {
/// The `GregorianDay` representing tomorrow's date.
public static var tomorrow: Self { GregorianDay(date: Date()).advanced(by: 1) }
}

extension GregorianDay: Withable {}
10 changes: 6 additions & 4 deletions Sources/HandySwift/Types/GregorianTimeOfDay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -141,3 +141,5 @@ extension GregorianTimeOfDay {
/// The current time of day.
public static var now: Self { GregorianTimeOfDay(date: Date()) }
}

extension GregorianTimeOfDay: Withable {}
6 changes: 6 additions & 0 deletions Tests/HandySwiftTests/Extensions/TimeIntervalExtTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
24 changes: 24 additions & 0 deletions Tests/HandySwiftTests/Structs/GregorianDayTests.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit 0a66272

Please sign in to comment.