initial version
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
import Foundation
|
||||
|
||||
extension Decimal {
|
||||
// MARK: - Performance: Shared formatters (avoid creating on every call)
|
||||
private static let percentFormatter: NumberFormatter = {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .percent
|
||||
formatter.maximumFractionDigits = 2
|
||||
formatter.multiplier = 1
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private static let decimalFormatter: NumberFormatter = {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .decimal
|
||||
formatter.maximumFractionDigits = 2
|
||||
return formatter
|
||||
}()
|
||||
|
||||
// MARK: - Performance: Cached currency symbol
|
||||
private static var _cachedCurrencySymbol: String?
|
||||
private static var currencySymbolCacheTime: Date?
|
||||
|
||||
private static var cachedCurrencySymbol: String {
|
||||
// Refresh cache every 60 seconds to pick up settings changes
|
||||
let now = Date()
|
||||
if let cached = _cachedCurrencySymbol,
|
||||
let cacheTime = currencySymbolCacheTime,
|
||||
now.timeIntervalSince(cacheTime) < 60 {
|
||||
return cached
|
||||
}
|
||||
let symbol = AppSettings.getOrCreate(in: CoreDataStack.shared.viewContext).currencySymbol
|
||||
_cachedCurrencySymbol = symbol
|
||||
currencySymbolCacheTime = now
|
||||
return symbol
|
||||
}
|
||||
|
||||
/// Call this when currency settings change to invalidate the cache
|
||||
static func invalidateCurrencyCache() {
|
||||
_cachedCurrencySymbol = nil
|
||||
currencySymbolCacheTime = nil
|
||||
}
|
||||
|
||||
// MARK: - Formatting
|
||||
|
||||
var currencyString: String {
|
||||
CurrencyFormatter.format(self, style: .currency, maximumFractionDigits: 2)
|
||||
}
|
||||
|
||||
var compactCurrencyString: String {
|
||||
CurrencyFormatter.format(self, style: .currency, maximumFractionDigits: 0)
|
||||
}
|
||||
|
||||
var shortCurrencyString: String {
|
||||
let value = NSDecimalNumber(decimal: self).doubleValue
|
||||
let symbol = Self.cachedCurrencySymbol
|
||||
|
||||
switch Swift.abs(value) {
|
||||
case 1_000_000...:
|
||||
return String(format: "%@%.1fM", symbol, value / 1_000_000)
|
||||
case 1_000...:
|
||||
return String(format: "%@%.1fK", symbol, value / 1_000)
|
||||
default:
|
||||
return compactCurrencyString
|
||||
}
|
||||
}
|
||||
|
||||
var percentageString: String {
|
||||
Self.percentFormatter.string(from: self as NSDecimalNumber) ?? "0%"
|
||||
}
|
||||
|
||||
var signedPercentageString: String {
|
||||
let prefix = self >= 0 ? "+" : ""
|
||||
return prefix + percentageString
|
||||
}
|
||||
|
||||
var decimalString: String {
|
||||
Self.decimalFormatter.string(from: self as NSDecimalNumber) ?? "0"
|
||||
}
|
||||
|
||||
// MARK: - Conversions
|
||||
|
||||
var doubleValue: Double {
|
||||
NSDecimalNumber(decimal: self).doubleValue
|
||||
}
|
||||
|
||||
var intValue: Int {
|
||||
NSDecimalNumber(decimal: self).intValue
|
||||
}
|
||||
|
||||
// MARK: - Math Operations
|
||||
|
||||
var abs: Decimal {
|
||||
self < 0 ? -self : self
|
||||
}
|
||||
|
||||
func rounded(scale: Int = 2) -> Decimal {
|
||||
var result = Decimal()
|
||||
var mutableSelf = self
|
||||
NSDecimalRound(&result, &mutableSelf, scale, .plain)
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - Comparisons
|
||||
|
||||
var isPositive: Bool {
|
||||
self > 0
|
||||
}
|
||||
|
||||
var isNegative: Bool {
|
||||
self < 0
|
||||
}
|
||||
|
||||
var isZero: Bool {
|
||||
self == 0
|
||||
}
|
||||
|
||||
// MARK: - Static Helpers
|
||||
|
||||
static func from(_ double: Double) -> Decimal {
|
||||
Decimal(double)
|
||||
}
|
||||
|
||||
static func from(_ string: String) -> Decimal? {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .decimal
|
||||
return formatter.number(from: string)?.decimalValue
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSDecimalNumber Extension
|
||||
|
||||
extension NSDecimalNumber {
|
||||
var currencyString: String {
|
||||
decimalValue.currencyString
|
||||
}
|
||||
|
||||
var compactCurrencyString: String {
|
||||
decimalValue.compactCurrencyString
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Optional Decimal
|
||||
|
||||
extension Optional where Wrapped == Decimal {
|
||||
var orZero: Decimal {
|
||||
self ?? Decimal.zero
|
||||
}
|
||||
|
||||
var currencyString: String {
|
||||
(self ?? Decimal.zero).currencyString
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user