diff --git a/ios/Packages/PrimitivesComponents/Package.swift b/ios/Packages/PrimitivesComponents/Package.swift index 18223b86f..9555071cf 100644 --- a/ios/Packages/PrimitivesComponents/Package.swift +++ b/ios/Packages/PrimitivesComponents/Package.swift @@ -60,6 +60,7 @@ let package = Package( "PrimitivesComponents", "PrimitivesComponentsTestKit", "GemstonePrimitives", + "Formatters", ], ), ], diff --git a/ios/Packages/PrimitivesComponents/Sources/Formatters/ChartDateFormatter.swift b/ios/Packages/PrimitivesComponents/Sources/Formatters/ChartDateFormatter.swift new file mode 100644 index 000000000..eb4147cc5 --- /dev/null +++ b/ios/Packages/PrimitivesComponents/Sources/Formatters/ChartDateFormatter.swift @@ -0,0 +1,34 @@ +// Copyright (c). Gem Wallet. All rights reserved. + +import Foundation +import Formatters +import Primitives + +public struct ChartDateFormatter: Sendable { + private let relative: RelativeDateFormatter + private let locale: Locale + private let timeZone: TimeZone + + public init( + relative: RelativeDateFormatter = RelativeDateFormatter(), + locale: Locale = .current, + timeZone: TimeZone = .current, + ) { + self.relative = relative + self.locale = locale + self.timeZone = timeZone + } + + public func string(for date: Date, period: ChartPeriod) -> String { + switch period { + case .hour: date.formatted(dateTime.hour().minute()) + case .day: relative.string(from: date) + case .week, .month: date.formatted(dateTime.month(.abbreviated).day().hour().minute()) + case .year, .all: date.formatted(dateTime.year().month(.abbreviated).day()) + } + } + + private var dateTime: Date.FormatStyle { + Date.FormatStyle(locale: locale, timeZone: timeZone) + } +} diff --git a/ios/Packages/PrimitivesComponents/Sources/Types/ChartHeaderViewModel.swift b/ios/Packages/PrimitivesComponents/Sources/Types/ChartHeaderViewModel.swift index 627d156a8..a5351037c 100644 --- a/ios/Packages/PrimitivesComponents/Sources/Types/ChartHeaderViewModel.swift +++ b/ios/Packages/PrimitivesComponents/Sources/Types/ChartHeaderViewModel.swift @@ -16,6 +16,7 @@ public struct ChartHeaderViewModel { public let type: ChartValueType private let formatter: CurrencyFormatter + private let dateFormatter: ChartDateFormatter public init( period: ChartPeriod, @@ -24,6 +25,7 @@ public struct ChartHeaderViewModel { priceChangePercentage: Double, headerValue: Double? = nil, formatter: CurrencyFormatter, + dateFormatter: ChartDateFormatter = ChartDateFormatter(), type: ChartValueType = .price, ) { self.period = period @@ -33,6 +35,7 @@ public struct ChartHeaderViewModel { self.headerValue = headerValue self.type = type self.formatter = formatter + self.dateFormatter = dateFormatter } private var valueChange: PriceChangeViewModel? { @@ -40,17 +43,7 @@ public struct ChartHeaderViewModel { } public var dateText: String? { - guard let date else { return nil } - switch period { - case .hour: - return date.formatted(.dateTime.hour().minute()) - case .day: - return date.formatted(.dateTime.weekday(.abbreviated).hour().minute()) - case .week, .month: - return date.formatted(.dateTime.month(.abbreviated).day().hour().minute()) - case .year, .all: - return date.formatted(.dateTime.year().month(.abbreviated).day()) - } + date.map { dateFormatter.string(for: $0, period: period) } } public var headerValueText: String? { diff --git a/ios/Packages/PrimitivesComponents/Tests/PrimitivesComponentsTests/ChartDateFormatterTests.swift b/ios/Packages/PrimitivesComponents/Tests/PrimitivesComponentsTests/ChartDateFormatterTests.swift new file mode 100644 index 000000000..835b67b8c --- /dev/null +++ b/ios/Packages/PrimitivesComponents/Tests/PrimitivesComponentsTests/ChartDateFormatterTests.swift @@ -0,0 +1,37 @@ +// Copyright (c). Gem Wallet. All rights reserved. + +import Formatters +import Foundation +import Primitives +@testable import PrimitivesComponents +import Testing + +struct ChartDateFormatterTests { + @Test + func stringForPeriod() throws { + let timeZone = TimeZone.NewYork! + let today = try #require(Calendar.current.date(bySettingHour: 14, minute: 30, second: 0, of: Date())) + let fixed = Date(timeIntervalSince1970: 1_745_505_000) + + let us = ChartDateFormatter( + relative: RelativeDateFormatter(locale: .US, timeZone: timeZone), + locale: .US, + timeZone: timeZone, + ) + #expect(us.string(for: today, period: .day) == "Today, 2:30 PM") + #expect(us.string(for: fixed, period: .hour) == "9:30 AM") + #expect(us.string(for: fixed, period: .week) == "Apr 24, 9:30 AM") + #expect(us.string(for: fixed, period: .month) == "Apr 24, 9:30 AM") + #expect(us.string(for: fixed, period: .year) == "Apr 24, 2025") + #expect(us.string(for: fixed, period: .all) == "Apr 24, 2025") + + let de = ChartDateFormatter( + relative: RelativeDateFormatter(locale: Locale(identifier: "de_DE"), timeZone: timeZone), + locale: Locale(identifier: "de_DE"), + timeZone: timeZone, + ) + #expect(de.string(for: today, period: .day) == "Heute, 14:30") + #expect(de.string(for: fixed, period: .hour) == "09:30") + #expect(de.string(for: fixed, period: .year) == "24. Apr. 2025") + } +}