From 357f7f717fb7468ff522031d2e9a9c607aaccac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Valenta?= Date: Thu, 12 Sep 2024 12:01:29 +0200 Subject: [PATCH 1/6] Sendability & Inlinable --- Sources/NaiveDate.swift | 32 +++++++++++++++++++++++++------- Sources/NaiveDateFormatter.swift | 20 +++++++++++++++++--- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/Sources/NaiveDate.swift b/Sources/NaiveDate.swift index 927849d..eec40cb 100644 --- a/Sources/NaiveDate.swift +++ b/Sources/NaiveDate.swift @@ -3,18 +3,20 @@ import Foundation // MARK: - NaiveDate /// Calendar date without a timezone. -public struct NaiveDate: Equatable, Hashable, Comparable, LosslessStringConvertible, Codable, _DateComponentsConvertible { +public struct NaiveDate: Sendable, Equatable, Hashable, Comparable, LosslessStringConvertible, Codable, _DateComponentsConvertible { public let year: Int, month: Int, day: Int /// Initializes the naive date with a given date components. /// - important: The naive types don't validate input components. For any /// precise manipulations with time use native `Date` and `Calendar` types. + @inlinable public init(year: Int, month: Int, day: Int) { self.year = year; self.month = month; self.day = day } // MARK: Comparable + @inlinable public static func <(lhs: NaiveDate, rhs: NaiveDate) -> Bool { return (lhs.year, lhs.month, lhs.day) < (rhs.year, rhs.month, rhs.day) } @@ -22,6 +24,7 @@ public struct NaiveDate: Equatable, Hashable, Comparable, LosslessStringConverti // MARK: LosslessStringConvertible /// Creates a naive date from a given string (e.g. "2017-12-30"). + @inlinable public init?(_ string: String) { // Not using `ISO8601DateFormatter` because it only works with `Date` guard let cmps = _components(from: string, separator: "-"), cmps.count == 3 else { return nil } @@ -29,22 +32,26 @@ public struct NaiveDate: Equatable, Hashable, Comparable, LosslessStringConverti } /// Returns a string representation of a naive date (e.g. "2017-12-30"). + @inlinable public var description: String { return String(format: "%i-%.2i-%.2i", year, month, day) } // MARK: Codable + @inlinable public init(from decoder: Decoder) throws { self = try _decode(from: decoder) } + @inlinable public func encode(to encoder: Encoder) throws { try _encode(self, to: encoder) } // MARK: _DateComponentsConvertible + @inlinable public var dateComponents: DateComponents { return DateComponents(year: year, month: month, day: day) } @@ -53,7 +60,7 @@ public struct NaiveDate: Equatable, Hashable, Comparable, LosslessStringConverti // MARK: - NaiveTime /// Time without a timezone. Allows for second precision. -public struct NaiveTime: Equatable, Hashable, Comparable, LosslessStringConvertible, Codable, _DateComponentsConvertible { +public struct NaiveTime: Sendable, Equatable, Hashable, Comparable, LosslessStringConvertible, Codable, _DateComponentsConvertible { public let hour: Int, minute: Int, second: Int /// Initializes the naive time with a given date components. @@ -116,7 +123,7 @@ public struct NaiveTime: Equatable, Hashable, Comparable, LosslessStringConverti // MARK: - NaiveDateTime /// Combined date and time without timezone. -public struct NaiveDateTime: Equatable, Hashable, Comparable, LosslessStringConvertible, Codable, _DateComponentsConvertible { +public struct NaiveDateTime: Sendable, Equatable, Hashable, Comparable, LosslessStringConvertible, Codable, _DateComponentsConvertible { public let date: NaiveDate public let time: NaiveTime @@ -181,20 +188,24 @@ public extension Calendar { // MARK: Naive* -> Date /// Returns a date in calendar's time zone created from the naive date. + @inlinable func date(from date: NaiveDate, in timeZone: TimeZone? = nil) -> Date? { return _date(from: date, in: timeZone) } /// Returns a date in calendar's time zone created from the naive time. + @inlinable func date(from time: NaiveTime, in timeZone: TimeZone? = nil) -> Date? { return _date(from: time, in: timeZone) } /// Returns a date in calendar's time zone created from the naive datetime. + @inlinable func date(from dateTime: NaiveDateTime, in timeZone: TimeZone? = nil) -> Date? { return _date(from: dateTime, in: timeZone) } + @usableFromInline internal func _date(from value: T, in timeZone: TimeZone? = nil) -> Date? { var components = value.dateComponents components.timeZone = timeZone @@ -205,6 +216,7 @@ public extension Calendar { /// Returns naive date from a date, as if in a given time zone. User calendar's time zone. /// - parameter timeZone: By default uses calendar's time zone. + @inlinable func naiveDate(from date: Date, in timeZone: TimeZone? = nil) -> NaiveDate { let components = self.dateComponents(in: timeZone ?? self.timeZone, from: date) return NaiveDate(year: components.year!, month: components.month!, day: components.day!) @@ -212,6 +224,7 @@ public extension Calendar { /// Returns naive time from a date, as if in a given time zone. User calendar's time zone. /// - parameter timeZone: By default uses calendar's time zone. + @inlinable func naiveTime(from date: Date, in timeZone: TimeZone? = nil) -> NaiveTime { let components = self.dateComponents(in: timeZone ?? self.timeZone, from: date) return NaiveTime(hour: components.hour!, minute: components.minute!, second: components.second!) @@ -219,6 +232,7 @@ public extension Calendar { /// Returns naive time from a date, as if in a given time zone. User calendar's time zone. /// - parameter timeZone: By default uses calendar's time zone. + @inlinable func naiveDateTime(from date: Date, in timeZone: TimeZone? = nil) -> NaiveDateTime { let components = self.dateComponents(in: timeZone ?? self.timeZone, from: date) return NaiveDateTime( @@ -231,11 +245,13 @@ public extension Calendar { // MARK: - Private /// A type that can be converted to DateComponents (and in turn to Date). -internal protocol _DateComponentsConvertible { +@usableFromInline +protocol _DateComponentsConvertible { var dateComponents: DateComponents { get } } -private func _decode(from decoder: Decoder) throws -> T { +@usableFromInline +func _decode(from decoder: Decoder) throws -> T { let container = try decoder.singleValueContainer() let string = try container.decode(String.self) guard let value = T(string) else { @@ -244,12 +260,14 @@ private func _decode(from decoder: Decoder) throws return value } -private func _encode(_ value: T, to encoder: Encoder) throws { +@usableFromInline +func _encode(_ value: T, to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(value.description) } -private func _components(from string: String, separator: String) -> [Int]? { +@usableFromInline +func _components(from string: String, separator: String) -> [Int]? { let substrings = string.components(separatedBy: separator) let components = substrings.compactMap(Int.init) return components.count == substrings.count ? components : nil diff --git a/Sources/NaiveDateFormatter.swift b/Sources/NaiveDateFormatter.swift index beb3d4f..25c4a34 100644 --- a/Sources/NaiveDateFormatter.swift +++ b/Sources/NaiveDateFormatter.swift @@ -4,18 +4,22 @@ import Foundation /// Formatting without time zones. public final class NaiveDateFormatter { - private let formatter = DateFormatter() + @usableFromInline + let formatter = DateFormatter() + @inlinable public init(_ closure: (_ formatter: DateFormatter) -> Void) { closure(formatter) } + @inlinable public convenience init(format: String) { self.init { $0.dateFormat = format } } + @inlinable public convenience init(dateStyle: DateFormatter.Style = .none, timeStyle: DateFormatter.Style = .none) { self.init { $0.dateStyle = dateStyle @@ -27,10 +31,12 @@ public final class NaiveDateFormatter { return formatter.calendar._date(from: value).map { formatter.string(from: $0) } } + @inlinable public func string(from value: NaiveTime) -> String? { return formatter.calendar._date(from: value).map { formatter.string(from: $0) } } + @inlinable public func string(from value: NaiveDateTime) -> String? { return formatter.calendar._date(from: value).map { formatter.string(from: $0) } } @@ -40,18 +46,22 @@ public final class NaiveDateFormatter { /// Formatting without time zones. public final class NaiveDateRangeFormatter { - private let formatter = DateIntervalFormatter() + @usableFromInline + let formatter = DateIntervalFormatter() + @inlinable public init(_ closure: (_ formatter: DateIntervalFormatter) -> Void) { closure(formatter) } + @inlinable public convenience init(format: String) { self.init { $0.dateTemplate = format } } + @inlinable public convenience init(dateStyle: DateIntervalFormatter.Style = .none, timeStyle: DateIntervalFormatter.Style = .none) { self.init { $0.dateStyle = dateStyle @@ -59,14 +69,17 @@ public final class NaiveDateRangeFormatter { } } + @inlinable public func string(from start: NaiveDate, to end: NaiveDate) -> String? { return formatter.calendar._dateRange(from: start, to: end).map { formatter.string(from: $0, to: $1) } } + @inlinable public func string(from start: NaiveTime, to end: NaiveTime) -> String? { return formatter.calendar._dateRange(from: start, to: end).map { formatter.string(from: $0, to: $1) } } + @inlinable public func string(from start: NaiveDateTime, to end: NaiveDateTime) -> String? { return formatter.calendar._dateRange(from: start, to: end).map { formatter.string(from: $0, to: $1) } } @@ -74,7 +87,8 @@ public final class NaiveDateRangeFormatter { // MARK: - Private -private extension Calendar { +extension Calendar { + @inlinable func _dateRange(from start: T, to end: T) -> (Date, Date)? { guard let start = _date(from: start), let end = _date(from: end) else { return nil } return (start, end) From b7a58397d2aebb1135439424e0370fa56575b72d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Valenta?= Date: Thu, 12 Sep 2024 12:04:56 +0200 Subject: [PATCH 2/6] Add parameter mutation possibility --- Sources/NaiveDate.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/NaiveDate.swift b/Sources/NaiveDate.swift index eec40cb..9dfab3f 100644 --- a/Sources/NaiveDate.swift +++ b/Sources/NaiveDate.swift @@ -4,7 +4,7 @@ import Foundation /// Calendar date without a timezone. public struct NaiveDate: Sendable, Equatable, Hashable, Comparable, LosslessStringConvertible, Codable, _DateComponentsConvertible { - public let year: Int, month: Int, day: Int + public var year: Int, month: Int, day: Int /// Initializes the naive date with a given date components. /// - important: The naive types don't validate input components. For any @@ -61,7 +61,7 @@ public struct NaiveDate: Sendable, Equatable, Hashable, Comparable, LosslessStri /// Time without a timezone. Allows for second precision. public struct NaiveTime: Sendable, Equatable, Hashable, Comparable, LosslessStringConvertible, Codable, _DateComponentsConvertible { - public let hour: Int, minute: Int, second: Int + public var hour: Int, minute: Int, second: Int /// Initializes the naive time with a given date components. /// - important: The naive types don't validate input components. For any @@ -124,8 +124,8 @@ public struct NaiveTime: Sendable, Equatable, Hashable, Comparable, LosslessStri /// Combined date and time without timezone. public struct NaiveDateTime: Sendable, Equatable, Hashable, Comparable, LosslessStringConvertible, Codable, _DateComponentsConvertible { - public let date: NaiveDate - public let time: NaiveTime + public var date: NaiveDate + public var time: NaiveTime /// Initializes the naive datetime with a given date components. /// - important: The naive types don't validate input components. For any From 37282176f8be4d511a740420e44555f292343f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Valenta?= Date: Thu, 12 Sep 2024 13:20:55 +0200 Subject: [PATCH 3/6] Support Foundation FormatStyle --- Sources/NaiveDate.swift | 1 + Sources/NaiveDateFormatStyle.swift | 195 ++++++++++++++++++++++++++ Tests/NaiveDateFormatStyleTests.swift | 45 ++++++ 3 files changed, 241 insertions(+) create mode 100644 Sources/NaiveDateFormatStyle.swift create mode 100644 Tests/NaiveDateFormatStyleTests.swift diff --git a/Sources/NaiveDate.swift b/Sources/NaiveDate.swift index 9dfab3f..4404a30 100644 --- a/Sources/NaiveDate.swift +++ b/Sources/NaiveDate.swift @@ -182,6 +182,7 @@ public struct NaiveDateTime: Sendable, Equatable, Hashable, Comparable, Lossless } + // MARK: - Calendar Extensions public extension Calendar { diff --git a/Sources/NaiveDateFormatStyle.swift b/Sources/NaiveDateFormatStyle.swift new file mode 100644 index 0000000..d5f03e2 --- /dev/null +++ b/Sources/NaiveDateFormatStyle.swift @@ -0,0 +1,195 @@ +// +// FormatStyle.swift +// NaiveDate +// +// Created by Lukáš Valenta on 12.09.2024. +// + +import Foundation + +@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) +public extension NaiveDate { + struct FormatStyle: Foundation.FormatStyle { + var date: Date.FormatStyle.DateStyle? + var time: Date.FormatStyle.TimeStyle? + var locale: Locale + var calendar: Calendar + var timeZone: TimeZone + var capitalizationContext: FormatStyleCapitalizationContext + + public init(date: Date.FormatStyle.DateStyle? = nil, + time: Date.FormatStyle.TimeStyle? = nil, + locale: Locale = .autoupdatingCurrent, + calendar: Calendar = .autoupdatingCurrent, + timeZone: TimeZone = .autoupdatingCurrent, + capitalizationContext: FormatStyleCapitalizationContext = .unknown) { + self.date = date + self.locale = locale + self.calendar = calendar + self.timeZone = timeZone + self.capitalizationContext = capitalizationContext + } + + public func format(_ value: NaiveDate) -> String { + calendar.date(from: value).map { date in + let dateStyle = Date.FormatStyle( + date: self.date, + time: time, + locale: locale, + calendar: calendar, + timeZone: timeZone, + capitalizationContext: capitalizationContext + ) + + return date.formatted(dateStyle) + } ?? "" + } + + public func locale(_ locale: Locale) -> NaiveDate.FormatStyle { + .init( + date: date, + locale: locale, + calendar: calendar, + timeZone: timeZone, + capitalizationContext: capitalizationContext + ) + } + } + + func formatted(_ format: F) -> F.FormatOutput where F.FormatInput == NaiveDate { + format.format(self) + } + + func formatted() -> String { + formatted(FormatStyle()) + } + + func formatted(date: Date.FormatStyle.DateStyle, time: Date.FormatStyle.TimeStyle = .omitted) -> String { + formatted(FormatStyle(date: date, time: time)) + } +} + +@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) +public extension NaiveTime { + struct FormatStyle: Foundation.FormatStyle { + var date: Date.FormatStyle.DateStyle? + var time: Date.FormatStyle.TimeStyle? + var locale: Locale + var calendar: Calendar + var timeZone: TimeZone + var capitalizationContext: FormatStyleCapitalizationContext + + public init(date: Date.FormatStyle.DateStyle? = nil, + time: Date.FormatStyle.TimeStyle? = nil, + locale: Locale = .autoupdatingCurrent, + calendar: Calendar = .autoupdatingCurrent, + timeZone: TimeZone = .autoupdatingCurrent, + capitalizationContext: FormatStyleCapitalizationContext = .unknown) { + self.date = date + self.locale = locale + self.calendar = calendar + self.timeZone = timeZone + self.capitalizationContext = capitalizationContext + } + + public func format(_ value: NaiveTime) -> String { + calendar.date(from: value).map { date in + let dateStyle = Date.FormatStyle( + date: self.date, + time: time, + locale: locale, + calendar: calendar, + timeZone: timeZone, + capitalizationContext: capitalizationContext + ) + + return date.formatted(dateStyle) + } ?? "" + } + + public func locale(_ locale: Locale) -> NaiveDate.FormatStyle { + .init( + date: date, + locale: locale, + calendar: calendar, + timeZone: timeZone, + capitalizationContext: capitalizationContext + ) + } + } + + func formatted(_ format: F) -> F.FormatOutput where F.FormatInput == NaiveTime { + format.format(self) + } + + func formatted() -> String { + formatted(FormatStyle()) + } + + func formatted(date: Date.FormatStyle.DateStyle = .omitted, time: Date.FormatStyle.TimeStyle) -> String { + formatted(FormatStyle(date: date, time: time)) + } +} + +@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) +public extension NaiveDateTime { + struct FormatStyle: Foundation.FormatStyle { + var date: Date.FormatStyle.DateStyle? + var time: Date.FormatStyle.TimeStyle? + var locale: Locale + var calendar: Calendar + var timeZone: TimeZone + var capitalizationContext: FormatStyleCapitalizationContext + + public init(date: Date.FormatStyle.DateStyle? = nil, + time: Date.FormatStyle.TimeStyle? = nil, + locale: Locale = .autoupdatingCurrent, + calendar: Calendar = .autoupdatingCurrent, + timeZone: TimeZone = .autoupdatingCurrent, + capitalizationContext: FormatStyleCapitalizationContext = .unknown) { + self.date = date + self.time = time + self.locale = locale + self.calendar = calendar + self.timeZone = timeZone + self.capitalizationContext = capitalizationContext + } + + public func format(_ value: NaiveDateTime) -> String { + calendar.date(from: value).map { date in + let dateStyle = Date.FormatStyle( + date: self.date, + time: time, + locale: locale, + calendar: calendar, + timeZone: timeZone, + capitalizationContext: capitalizationContext + ) + + return date.formatted(dateStyle) + } ?? "" + } + + public func locale(_ locale: Locale) -> NaiveDate.FormatStyle { + .init( + date: date, + locale: locale, + calendar: calendar, + timeZone: timeZone, + capitalizationContext: capitalizationContext + ) + } + } + + func formatted(_ format: F) -> F.FormatOutput where F.FormatInput == NaiveDateTime { + format.format(self) + } + + func formatted() -> String { + formatted(FormatStyle()) + } + + func formatted(date: Date.FormatStyle.DateStyle, time: Date.FormatStyle.TimeStyle) -> String { + formatted(FormatStyle(date: date, time: time)) + } +} diff --git a/Tests/NaiveDateFormatStyleTests.swift b/Tests/NaiveDateFormatStyleTests.swift new file mode 100644 index 0000000..1444579 --- /dev/null +++ b/Tests/NaiveDateFormatStyleTests.swift @@ -0,0 +1,45 @@ +import Foundation +import XCTest +import NaiveDate + +final class NaiveDateFormatStyleTest: XCTestCase { + func testFormattedNaiveDateAgainstDate_withoutParameters() throws { + let naiveDate = NaiveDate(year: 2024, month: 8, day: 12) + let foundationDate = try XCTUnwrap(Calendar.current.date(from: naiveDate)) + + let formattedFoundationDate = foundationDate.formatted() + let formattedNaiveDate = naiveDate.formatted() + + XCTAssertEqual(formattedNaiveDate, formattedFoundationDate) + } + + func testFormattedNaiveDateAgainstDate_numeric() throws { + let naiveDate = NaiveDate(year: 2024, month: 8, day: 12) + let foundationDate = try XCTUnwrap(Calendar.current.date(from: naiveDate)) + + let formattedFoundationDate = foundationDate.formatted(date: .numeric, time: .omitted) + let formattedNaiveDate = naiveDate.formatted(date: .numeric) + + XCTAssertEqual(formattedNaiveDate, formattedFoundationDate) + } + + func testFormattedNaiveDateTimeAgainstDate_withoutParameters() throws { + let naiveDate = NaiveDateTime(date: .init(year: 2024, month: 8, day: 12), time: .init(hour: 5, minute: 3, second: 1)) + let foundationDate = try XCTUnwrap(Calendar.current.date(from: naiveDate)) + + let formattedFoundationDate = foundationDate.formatted() + let formattedNaiveDate = naiveDate.formatted() + + XCTAssertEqual(formattedNaiveDate, formattedFoundationDate) + } + + func testFormattedNaiveDateTimeAgainstDate_numeric() throws { + let naiveDate = NaiveDateTime(date: .init(year: 2024, month: 8, day: 12), time: .init(hour: 5, minute: 3, second: 1)) + let foundationDate = try XCTUnwrap(Calendar.current.date(from: naiveDate)) + + let formattedFoundationDate = foundationDate.formatted(date: .numeric, time: .standard) + let formattedNaiveDate = naiveDate.formatted(date: .numeric, time: .standard) + + XCTAssertEqual(formattedNaiveDate, formattedFoundationDate) + } +} From 91fb06256a6ccb7955356bb27c98c808b425e6aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Valenta?= Date: Thu, 12 Sep 2024 13:24:44 +0200 Subject: [PATCH 4/6] Removed file header --- Sources/NaiveDateFormatStyle.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Sources/NaiveDateFormatStyle.swift b/Sources/NaiveDateFormatStyle.swift index d5f03e2..5e930fa 100644 --- a/Sources/NaiveDateFormatStyle.swift +++ b/Sources/NaiveDateFormatStyle.swift @@ -1,10 +1,3 @@ -// -// FormatStyle.swift -// NaiveDate -// -// Created by Lukáš Valenta on 12.09.2024. -// - import Foundation @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) From e761250a0ab41bfa14dc17a8ff2a9a3c64e0abb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Valenta?= Date: Thu, 12 Sep 2024 13:35:14 +0200 Subject: [PATCH 5/6] Fix tests --- Tests/NaiveDateFormatStyleTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/NaiveDateFormatStyleTests.swift b/Tests/NaiveDateFormatStyleTests.swift index 1444579..74cbecc 100644 --- a/Tests/NaiveDateFormatStyleTests.swift +++ b/Tests/NaiveDateFormatStyleTests.swift @@ -2,6 +2,7 @@ import Foundation import XCTest import NaiveDate +@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) final class NaiveDateFormatStyleTest: XCTestCase { func testFormattedNaiveDateAgainstDate_withoutParameters() throws { let naiveDate = NaiveDate(year: 2024, month: 8, day: 12) From 9c4f9be8f55335c4995672edaa7c351a10d0c785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Valenta?= Date: Thu, 12 Sep 2024 13:35:25 +0200 Subject: [PATCH 6/6] Add documentation --- Sources/NaiveDateFormatStyle.swift | 104 +++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 6 deletions(-) diff --git a/Sources/NaiveDateFormatStyle.swift b/Sources/NaiveDateFormatStyle.swift index 5e930fa..ca62d55 100644 --- a/Sources/NaiveDateFormatStyle.swift +++ b/Sources/NaiveDateFormatStyle.swift @@ -10,6 +10,15 @@ public extension NaiveDate { var timeZone: TimeZone var capitalizationContext: FormatStyleCapitalizationContext + /// Creates a format style for a NaiveDate. + /// + /// - Parameters: + /// - date: The style to use for formatting the date component. Defaults to `nil`. + /// - time: The style to use for formatting the time component. Defaults to `nil`. + /// - locale: The locale to use for formatting. Defaults to `autoupdatingCurrent`. + /// - calendar: The calendar to use for formatting. Defaults to `autoupdatingCurrent`. + /// - timeZone: The time zone to use for formatting. Defaults to `autoupdatingCurrent`. + /// - capitalizationContext: The context for capitalization. Defaults to `unknown`. public init(date: Date.FormatStyle.DateStyle? = nil, time: Date.FormatStyle.TimeStyle? = nil, locale: Locale = .autoupdatingCurrent, @@ -17,30 +26,39 @@ public extension NaiveDate { timeZone: TimeZone = .autoupdatingCurrent, capitalizationContext: FormatStyleCapitalizationContext = .unknown) { self.date = date + self.time = time self.locale = locale self.calendar = calendar self.timeZone = timeZone self.capitalizationContext = capitalizationContext } + /// Formats the given NaiveDate value. + /// + /// - Parameter value: The NaiveDate value to format. + /// - Returns: A formatted string representing the NaiveDate. public func format(_ value: NaiveDate) -> String { calendar.date(from: value).map { date in let dateStyle = Date.FormatStyle( date: self.date, - time: time, + time: self.time, locale: locale, calendar: calendar, timeZone: timeZone, capitalizationContext: capitalizationContext ) - return date.formatted(dateStyle) } ?? "" } + /// Returns a new format style with the specified locale. + /// + /// - Parameter locale: The locale to apply to the format style. + /// - Returns: A new `NaiveDate.FormatStyle` with the given locale. public func locale(_ locale: Locale) -> NaiveDate.FormatStyle { .init( date: date, + time: time, locale: locale, calendar: calendar, timeZone: timeZone, @@ -49,14 +67,27 @@ public extension NaiveDate { } } + /// Formats the NaiveDate using the provided format style. + /// + /// - Parameter format: The format style to apply. + /// - Returns: The formatted string output. func formatted(_ format: F) -> F.FormatOutput where F.FormatInput == NaiveDate { format.format(self) } + /// Formats the NaiveDate using the default format style. + /// + /// - Returns: A formatted string representation of the NaiveDate. func formatted() -> String { formatted(FormatStyle()) } + /// Formats the NaiveDate with specified date and time styles. + /// + /// - Parameters: + /// - date: The style to use for formatting the date component. + /// - time: The style to use for formatting the time component. + /// - Returns: A formatted string representation of the NaiveDate. func formatted(date: Date.FormatStyle.DateStyle, time: Date.FormatStyle.TimeStyle = .omitted) -> String { formatted(FormatStyle(date: date, time: time)) } @@ -72,6 +103,15 @@ public extension NaiveTime { var timeZone: TimeZone var capitalizationContext: FormatStyleCapitalizationContext + /// Creates a format style for a NaiveTime. + /// + /// - Parameters: + /// - dateStyle: The style to use for formatting the date component. Defaults to `nil`. + /// - timeStyle: The style to use for formatting the time component. Defaults to `nil`. + /// - locale: The locale to use for formatting. Defaults to `autoupdatingCurrent`. + /// - calendar: The calendar to use for formatting. Defaults to `autoupdatingCurrent`. + /// - timeZone: The time zone to use for formatting. Defaults to `autoupdatingCurrent`. + /// - capitalizationContext: The context for capitalization. Defaults to `unknown`. public init(date: Date.FormatStyle.DateStyle? = nil, time: Date.FormatStyle.TimeStyle? = nil, locale: Locale = .autoupdatingCurrent, @@ -79,30 +119,39 @@ public extension NaiveTime { timeZone: TimeZone = .autoupdatingCurrent, capitalizationContext: FormatStyleCapitalizationContext = .unknown) { self.date = date + self.time = time self.locale = locale self.calendar = calendar self.timeZone = timeZone self.capitalizationContext = capitalizationContext } + /// Formats the given NaiveTime value. + /// + /// - Parameter value: The NaiveTime value to format. + /// - Returns: A formatted string representing the NaiveTime. public func format(_ value: NaiveTime) -> String { calendar.date(from: value).map { date in let dateStyle = Date.FormatStyle( date: self.date, - time: time, + time: self.time, locale: locale, calendar: calendar, timeZone: timeZone, capitalizationContext: capitalizationContext ) - return date.formatted(dateStyle) } ?? "" } - public func locale(_ locale: Locale) -> NaiveDate.FormatStyle { + /// Returns a new format style with the specified locale. + /// + /// - Parameter locale: The locale to apply to the format style. + /// - Returns: A new `NaiveTime.FormatStyle` with the given locale. + public func locale(_ locale: Locale) -> NaiveTime.FormatStyle { .init( date: date, + time: time, locale: locale, calendar: calendar, timeZone: timeZone, @@ -111,14 +160,27 @@ public extension NaiveTime { } } + /// Formats the NaiveTime using the provided format style. + /// + /// - Parameter format: The format style to apply. + /// - Returns: The formatted string output. func formatted(_ format: F) -> F.FormatOutput where F.FormatInput == NaiveTime { format.format(self) } + /// Formats the NaiveTime using the default format style. + /// + /// - Returns: A formatted string representation of the NaiveTime. func formatted() -> String { formatted(FormatStyle()) } + /// Formats the NaiveTime with specified date and time styles. + /// + /// - Parameters: + /// - date: The style to use for formatting the date component. + /// - time: The style to use for formatting the time component. + /// - Returns: A formatted string representation of the NaiveTime. func formatted(date: Date.FormatStyle.DateStyle = .omitted, time: Date.FormatStyle.TimeStyle) -> String { formatted(FormatStyle(date: date, time: time)) } @@ -134,6 +196,15 @@ public extension NaiveDateTime { var timeZone: TimeZone var capitalizationContext: FormatStyleCapitalizationContext + /// Creates a format style for a NaiveDateTime. + /// + /// - Parameters: + /// - dateStyle: The style to use for formatting the date component. Defaults to `nil`. + /// - timeStyle: The style to use for formatting the time component. Defaults to `nil`. + /// - locale: The locale to use for formatting. Defaults to `autoupdatingCurrent`. + /// - calendar: The calendar to use for formatting. Defaults to `autoupdatingCurrent`. + /// - timeZone: The time zone to use for formatting. Defaults to `autoupdatingCurrent`. + /// - capitalizationContext: The context for capitalization. Defaults to `unknown`. public init(date: Date.FormatStyle.DateStyle? = nil, time: Date.FormatStyle.TimeStyle? = nil, locale: Locale = .autoupdatingCurrent, @@ -147,7 +218,11 @@ public extension NaiveDateTime { self.timeZone = timeZone self.capitalizationContext = capitalizationContext } - + + /// Formats the given NaiveDateTime value. + /// + /// - Parameter value: The NaiveDateTime value to format. + /// - Returns: A formatted string representing the NaiveDateTime. public func format(_ value: NaiveDateTime) -> String { calendar.date(from: value).map { date in let dateStyle = Date.FormatStyle( @@ -163,6 +238,10 @@ public extension NaiveDateTime { } ?? "" } + /// Returns a new format style with the specified locale. + /// + /// - Parameter locale: The locale to apply to the format style. + /// - Returns: A new `NaiveDateTime.FormatStyle` with the given locale. public func locale(_ locale: Locale) -> NaiveDate.FormatStyle { .init( date: date, @@ -174,14 +253,27 @@ public extension NaiveDateTime { } } + /// Formats the NaiveDateTime using the provided format style. + /// + /// - Parameter format: The format style to apply. + /// - Returns: The formatted string output. func formatted(_ format: F) -> F.FormatOutput where F.FormatInput == NaiveDateTime { format.format(self) } + /// Formats the NaiveDateTime using the default format style. + /// + /// - Returns: A formatted string representation of the NaiveDateTime. func formatted() -> String { formatted(FormatStyle()) } + /// Formats the NaiveDateTime with specified date and time styles. + /// + /// - Parameters: + /// - date: The style to use for formatting the date component. + /// - time: The style to use for formatting the time component. + /// - Returns: A formatted string representation of the NaiveDateTime. func formatted(date: Date.FormatStyle.DateStyle, time: Date.FormatStyle.TimeStyle) -> String { formatted(FormatStyle(date: date, time: time)) }