From ec47a20b29c319a6110f16d10b3953ccf7cf2938 Mon Sep 17 00:00:00 2001 From: cdricms <36056008+cdricms@users.noreply.github.com> Date: Mon, 28 Jul 2025 20:24:13 +0200 Subject: [PATCH] Any numeric --- .swift-format | 69 ++++++ Sources/Units/Units.swift | 344 ++++++++++++++++++------------ Tests/UnitsTests/UnitsTests.swift | 18 +- 3 files changed, 291 insertions(+), 140 deletions(-) create mode 100644 .swift-format diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..d226c08 --- /dev/null +++ b/.swift-format @@ -0,0 +1,69 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "indentation" : { + "tabs" : 1 + }, + "indentConditionalCompilationBlocks" : true, + "indentSwitchCaseLabels" : false, + "lineBreakAroundMultilineExpressionChainComponents" : false, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : false, + "lineBreakBeforeEachGenericRequirement" : false, + "lineLength" : 80, + "maximumBlankLines" : 1, + "multiElementCollectionTrailingCommas" : true, + "noAssignmentInExpressions" : { + "allowedFunctions" : [ + "XCTAssertNoThrow" + ] + }, + "prioritizeKeepingFunctionOutputTogether" : false, + "respectsExistingLineBreaks" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLiteralForEmptyCollectionInit" : false, + "AlwaysUseLowerCamelCase" : true, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : true, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : false, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : true, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : true, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : true, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoPlaygroundLiterals" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : false, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : true, + "ReplaceForEachWithForLoop" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "TypeNamesShouldBeCapitalized" : true, + "UseEarlyExits" : false, + "UseLetInEveryBoundCaseVariable" : true, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : true, + "UseSynthesizedInitializer" : true, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : false + }, + "spacesAroundRangeFormationOperators" : false, + "tabWidth" : 4, + "version" : 1 +} diff --git a/Sources/Units/Units.swift b/Sources/Units/Units.swift index 09a5d92..f55f00a 100644 --- a/Sources/Units/Units.swift +++ b/Sources/Units/Units.swift @@ -1,9 +1,32 @@ +import CoreGraphics // For CGFloat, often used in UI import Foundation +public protocol ConvertibleToDouble: Numeric { + var doubleValue: Double { get } +} + +// MARK: - Conformance for Standard Numeric Types +// Make common Swift numeric types conform to our new protocol. +extension Int: ConvertibleToDouble { + public var doubleValue: Double { Double(self) } +} +extension UInt: ConvertibleToDouble { + public var doubleValue: Double { Double(self) } +} +extension Float: ConvertibleToDouble { + public var doubleValue: Double { Double(self) } +} +extension Double: ConvertibleToDouble { + public var doubleValue: Double { self } +} +extension CGFloat: ConvertibleToDouble { + public var doubleValue: Double { Double(self) } +} + // MARK: - 1. UnitCategory Enum -// Defines the broad categories of units. This is crucial for preventing -// invalid conversions (e.g., mass to length). -public enum UnitCategory: String, CaseIterable, CustomStringConvertible { +public enum UnitCategory: String, CaseIterable, CustomStringConvertible, + Codable, Equatable +{ case mass case length case volume @@ -18,34 +41,28 @@ public enum UnitCategory: String, CaseIterable, CustomStringConvertible { } // MARK: - 2. Unit Enum -// Defines all the specific units, grouped by their category. -// Each unit has a 'rawValue' for its common abbreviation and a 'toBaseFactor' -// that converts its value to the base unit of its category. -// -// Base Units: -// - Mass: Gram (g) -// - Length: Meter (m) -// - Volume: Liter (L) -// - Time: Second (s) -// - Temperature: Celsius (C) (Note: Temperature conversions are more complex, -// this example uses a simple factor relative to Celsius for demonstration. -// For real-world, a dedicated temperature conversion function is needed.) -// - Speed: Meters per second (mps) -public enum Unit: String, CaseIterable, CustomStringConvertible { +public enum Unit: String, CaseIterable, CustomStringConvertible, Codable, + Equatable, Identifiable +{ + public var id: String { rawValue } // Conformance for SwiftUI's ForEach + // MARK: Mass Units + // Metric case kilogram = "kg" case gram = "g" case milligram = "mg" case microgram = "µg" + // Imperial case pound = "lbs" case ounce = "oz" - case metricTon = "ton" // Metric ton (1000 kg) + case metricTon = "ton" // MARK: Length Units case meter = "m" case centimeter = "cm" case millimeter = "mm" case kilometer = "km" + // Imperial case inch = "in" case foot = "ft" case yard = "yd" @@ -53,11 +70,13 @@ public enum Unit: String, CaseIterable, CustomStringConvertible { case nauticalMile = "nmi" // MARK: Volume Units + // Metric case liter = "L" case milliliter = "mL" case cubicMeter = "m³" case cubicCentimeter = "cm³" - case gallon = "gal" // US Liquid Gallon + // Imperial + case gallon = "gal" case quart = "qt" case pint = "pt" case fluidOunce = "fl oz" @@ -71,7 +90,7 @@ public enum Unit: String, CaseIterable, CustomStringConvertible { case day = "day" case week = "wk" - // MARK: Temperature Units (Note: Simplified for factor-based conversion) + // MARK: Temperature Units case celsius = "°C" case fahrenheit = "°F" case kelvin = "K" @@ -82,29 +101,25 @@ public enum Unit: String, CaseIterable, CustomStringConvertible { case milesPerHour = "mph" case knots = "kn" - // Add more units as needed - - // Returns the category for the current unit. public var category: UnitCategory { switch self { case .kilogram, .gram, .milligram, .microgram, .pound, .ounce, .metricTon: return .mass - case .meter, .centimeter, .millimeter, .kilometer, .inch, .foot, .yard, .mile, - .nauticalMile: + case .meter, .centimeter, .millimeter, .kilometer, .inch, .foot, .yard, + .mile, .nauticalMile: return .length - case .liter, .milliliter, .cubicMeter, .cubicCentimeter, .gallon, .quart, .pint, - .fluidOunce: + case .liter, .milliliter, .cubicMeter, .cubicCentimeter, .gallon, + .quart, .pint, .fluidOunce: return .volume case .second, .millisecond, .microsecond, .minute, .hour, .day, .week: return .time case .celsius, .fahrenheit, .kelvin: return .temperature - case .metersPerSecond, .kilometersPerHour, .milesPerHour, .knots: return .speed + case .metersPerSecond, .kilometersPerHour, .milesPerHour, .knots: + return .speed } } - // Returns the conversion factor to the base unit of its category. - // All conversions happen via the base unit. public var toBaseFactor: Double { switch self { // Mass (Base: Gram) @@ -114,7 +129,7 @@ public enum Unit: String, CaseIterable, CustomStringConvertible { case .microgram: 0.000001 case .pound: 453.59237 case .ounce: 28.349523125 - case .metricTon: 1_000_000.0 // 1000 kg = 1,000,000 g + case .metricTon: 1_000_000.0 // Length (Base: Meter) case .meter: 1.0 @@ -130,8 +145,8 @@ public enum Unit: String, CaseIterable, CustomStringConvertible { // Volume (Base: Liter) case .liter: 1.0 case .milliliter: 0.001 - case .cubicMeter: 1000.0 // 1 m³ = 1000 L - case .cubicCentimeter: 0.001 // 1 cm³ = 1 mL = 0.001 L + case .cubicMeter: 1000.0 + case .cubicCentimeter: 0.001 case .gallon: 3.78541 case .quart: 0.946353 case .pint: 0.473176 @@ -152,39 +167,45 @@ public enum Unit: String, CaseIterable, CustomStringConvertible { // Speed (Base: Meters per second) case .metersPerSecond: 1.0 - case .kilometersPerHour: 1000.0 / 3600.0 // km/h to m/s - case .milesPerHour: 1609.344 / 3600.0 // mph to m/s - case .knots: 0.514444 // nautical miles per hour to m/s + case .kilometersPerHour: 1000.0 / 3600.0 + case .milesPerHour: 1609.344 / 3600.0 + case .knots: 0.514444 } } public var description: String { rawValue } - // MARK: - Subscripts for Unit Enum - /// Allows retrieving a Unit by its raw string value. - /// Example: `Unit(rawValue: "kg")` or `Unit["kg"]` public static subscript(rawValue: String) -> Unit? { .init(rawValue: rawValue) } - /// Allows retrieving all Units belonging to a specific UnitCategory. - /// Example: `Unit[.mass]` will return `[.kilogram, .gram, ...]` public static subscript(category: UnitCategory) -> [Unit] { Unit.allCases.filter { $0.category == category } } - } // MARK: - 3. UnitValue Struct -// This struct holds a numeric value and its associated unit. -// It provides the core logic for converting between units. -public struct UnitValue: CustomStringConvertible, Equatable { - public let value: T +// The generic parameter `ValueType` now refers to the *input type*, +// but the internal `value` will be stored as a `Double`. +public struct UnitValue: + CustomStringConvertible, Equatable, + Comparable +{ + // Store the value internally as a Double for consistent calculations + public let value: Double public let unit: Unit // MARK: Initialization - public init(value: T, unit: Unit) { - self.value = value + /// Initializes UnitValue, converting the input value to Double. + // MARK: Initialization + public init(value: ValueType, unit: Unit) { + self.value = value.doubleValue // Use our new protocol requirement! + self.unit = unit + } + + // Internal initializer for conversions, directly accepting a Double + private init(doubleValue: Double, unit: Unit) { + self.value = doubleValue self.unit = unit } @@ -192,8 +213,8 @@ public struct UnitValue: CustomStringConvertible, Equata /// Converts the current UnitValue to a specified target unit. /// - Parameter targetUnit: The unit to convert to. /// - Returns: A new `UnitValue` in the target unit, or `nil` if the units - /// are of different categories (e.g., mass to length). - func converted(to targetUnit: Unit) -> UnitValue? { + /// are of different categories (e.g., mass to length). + public func converted(to targetUnit: Unit) -> UnitValue? { // Returns UnitValue after conversion // Ensure units are of the same category for valid conversion guard self.unit.category == targetUnit.category else { print( @@ -203,98 +224,121 @@ public struct UnitValue: CustomStringConvertible, Equata } // Special handling for Temperature, as it's not a simple multiplicative factor. - // This demonstrates how to handle more complex conversions. if self.unit.category == .temperature { return convertTemperature(to: targetUnit) } // For other categories, convert via the base unit - let valueInBaseUnit = value * T(self.unit.toBaseFactor) - let convertedValue = valueInBaseUnit / T(targetUnit.toBaseFactor) - return .init(value: convertedValue, unit: targetUnit) + let valueInBaseUnit = value * self.unit.toBaseFactor + let convertedValue = valueInBaseUnit / targetUnit.toBaseFactor + return .init(doubleValue: convertedValue, unit: targetUnit) } // MARK: Temperature Conversion Helper - private func convertTemperature(to targetUnit: Unit) -> UnitValue? { + private func convertTemperature(to targetUnit: Unit) -> UnitValue? + { // Returns UnitValue // Convert current value to Celsius first - var celsiusValue: T + var celsiusValue: Double switch self.unit { case .celsius: celsiusValue = self.value case .fahrenheit: - celsiusValue = (self.value - 32) * (5 / 9) + celsiusValue = (self.value - 32.0) * (5.0 / 9.0) case .kelvin: celsiusValue = self.value - 273.15 default: - // This case should ideally not be reached if category check is correct print("Error: Unknown temperature unit \(self.unit.rawValue)") return nil } // Convert from Celsius to target unit - var finalValue: T + var finalValue: Double switch targetUnit { case .celsius: finalValue = celsiusValue case .fahrenheit: - finalValue = (celsiusValue * (9 / 5)) + 32 + finalValue = (celsiusValue * (9.0 / 5.0)) + 32.0 case .kelvin: finalValue = celsiusValue + 273.15 default: - print("Error: Target unit \(targetUnit.rawValue) is not a valid temperature unit.") + print( + "Error: Target unit \(targetUnit.rawValue) is not a valid temperature unit." + ) return nil } - return .init(value: finalValue, unit: targetUnit) + return .init(doubleValue: finalValue, unit: targetUnit) } // MARK: CustomStringConvertible Conformance public var description: String { - // Format the value to avoid excessive decimal places for readability let formatter = NumberFormatter() - formatter.maximumFractionDigits = 4 // Adjust as needed + formatter.maximumFractionDigits = 4 formatter.minimumFractionDigits = 0 formatter.numberStyle = .decimal - if let formattedValue = formatter.string(from: NSNumber(value: Double(value))) { + if let formattedValue = formatter.string(from: NSNumber(value: value)) { return "\(formattedValue) \(unit.rawValue)" } else { return "\(value) \(unit.rawValue)" } } - // MARK: Equatable Conformance - // Allows comparing two UnitValue instances for equality. - // Considers both value and unit. - public static func == (lhs: UnitValue, rhs: UnitValue) -> Bool { - lhs.value == rhs.value && lhs.unit == rhs.unit + // MARK: Equatable & Comparable Conformance + public static func == (lhs: UnitValue, rhs: UnitValue) + -> Bool + { + // For equality, convert both to base unit for comparison + guard let lhsBase = lhs.converted(to: lhs.unit.category.baseUnit()), + let rhsBase = rhs.converted(to: rhs.unit.category.baseUnit()) + else { + return false // Or handle error appropriately + } + return lhsBase.value == rhsBase.value // Compare their base values } - public static func >= (lhs: UnitValue, rhs: UnitValue) -> Bool { - lhs.value >= rhs.value && lhs.unit == rhs.unit + public static func < (lhs: UnitValue, rhs: UnitValue) + -> Bool + { + guard lhs.unit.category == rhs.unit.category else { + fatalError( + "Cannot compare UnitValues of different categories (\(lhs.unit.category) vs \(rhs.unit.category))" + ) + } + guard let lhsBase = lhs.converted(to: lhs.unit.category.baseUnit()), + let rhsBase = rhs.converted(to: rhs.unit.category.baseUnit()) + else { + return false + } + return lhsBase.value < rhsBase.value } - public static func > (lhs: UnitValue, rhs: UnitValue) -> Bool { - lhs.value > rhs.value && lhs.unit == rhs.unit + // Implement other Comparable operators using < and == + public static func <= (lhs: UnitValue, rhs: UnitValue) + -> Bool + { + lhs < rhs || lhs == rhs } - - public static func <= (lhs: UnitValue, rhs: UnitValue) -> Bool { - lhs.value <= rhs.value && lhs.unit == rhs.unit + public static func >= (lhs: UnitValue, rhs: UnitValue) + -> Bool + { + !(lhs < rhs) } - - public static func < (lhs: UnitValue, rhs: UnitValue) -> Bool { - lhs.value < rhs.value && lhs.unit == rhs.unit + public static func > (lhs: UnitValue, rhs: UnitValue) + -> Bool + { + !(lhs < rhs) && !(lhs == rhs) } // MARK: - Subscripts for UnitValue /// Allows converting the UnitValue to another Unit using subscript syntax. /// Example: `tenKilos[.pound]` - public subscript(targetUnit: Unit) -> UnitValue? { + public subscript(targetUnit: Unit) -> UnitValue? { converted(to: targetUnit) } /// Allows converting the UnitValue to another Unit using its raw string value. /// Example: `tenKilos["lbs"]` - public subscript(targetUnitString: String) -> UnitValue? { + public subscript(targetUnitString: String) -> UnitValue? { guard let targetUnit = Unit(rawValue: targetUnitString) else { print("Conversion Error: Unknown unit string '\(targetUnitString)'") return nil @@ -302,12 +346,36 @@ public struct UnitValue: CustomStringConvertible, Equata return converted(to: targetUnit) } + // MARK: - Convenience for getting integer values + /// Returns the value as an Int, potentially truncating decimal places. + public var intValue: Int { + Int(value) + } + + /// Returns the value as a UInt, potentially truncating decimal places. + public var uintValue: UInt { + UInt(value) + } } -// MARK: - 4. Extension on BinaryFloatingPoint -// This extension adds computed properties to numeric types (like Double, Float, CGFloat) -// allowing you to write `10.kg` or `5.5.m`. -extension BinaryFloatingPoint { +// Helper to get the base unit for a category +extension UnitCategory { + func baseUnit() -> Unit { + switch self { + case .mass: return .gram + case .length: return .meter + case .volume: return .liter + case .time: return .second + case .temperature: return .celsius + case .speed: return .metersPerSecond + } + } +} + +// MARK: - 4. Extension on Numeric for Initializers +// This extension allows any Numeric type (Int, Double, Float, UInt, etc.) +// to directly create a UnitValue. +extension ConvertibleToDouble { // MARK: Mass Initializers public var kg: UnitValue { .init(value: self, unit: .kilogram) } public var g: UnitValue { .init(value: self, unit: .gram) } @@ -322,7 +390,7 @@ extension BinaryFloatingPoint { public var cm: UnitValue { .init(value: self, unit: .centimeter) } public var mm: UnitValue { .init(value: self, unit: .millimeter) } public var km: UnitValue { .init(value: self, unit: .kilometer) } - public var `in`: UnitValue { .init(value: self, unit: .inch) } // 'in' is a keyword, so use backticks + public var `in`: UnitValue { .init(value: self, unit: .inch) } public var ft: UnitValue { .init(value: self, unit: .foot) } public var yd: UnitValue { .init(value: self, unit: .yard) } public var mi: UnitValue { .init(value: self, unit: .mile) } @@ -332,7 +400,9 @@ extension BinaryFloatingPoint { public var L: UnitValue { .init(value: self, unit: .liter) } public var mL: UnitValue { .init(value: self, unit: .milliliter) } public var m3: UnitValue { .init(value: self, unit: .cubicMeter) } - public var cm3: UnitValue { .init(value: self, unit: .cubicCentimeter) } + public var cm3: UnitValue { + .init(value: self, unit: .cubicCentimeter) + } public var gal: UnitValue { .init(value: self, unit: .gallon) } public var qt: UnitValue { .init(value: self, unit: .quart) } public var pt: UnitValue { .init(value: self, unit: .pint) } @@ -353,12 +423,16 @@ extension BinaryFloatingPoint { public var K: UnitValue { .init(value: self, unit: .kelvin) } // MARK: Speed Initializers - public var mps: UnitValue { .init(value: self, unit: .metersPerSecond) } - public var kmh: UnitValue { .init(value: self, unit: .kilometersPerHour) } + public var mps: UnitValue { + .init(value: self, unit: .metersPerSecond) + } + public var kmh: UnitValue { + .init(value: self, unit: .kilometersPerHour) + } public var mph: UnitValue { .init(value: self, unit: .milesPerHour) } public var kn: UnitValue { .init(value: self, unit: .knots) } - // MARK: - Subscripts for BinaryFloatingPoint + // MARK: - Subscripts for Numeric /// Allows creating a UnitValue directly from a numeric literal using a Unit enum case. /// Example: `10[.kg]` public subscript(unit: Unit) -> UnitValue { @@ -369,66 +443,66 @@ extension BinaryFloatingPoint { /// Example: `10["kg"]` public subscript(unitString: String) -> UnitValue? { guard let unit = Unit(rawValue: unitString) else { - print("Initialization Error: Unknown unit string '\(unitString)' for value \(self)") + print( + "Initialization Error: Unknown unit string '\(unitString)' for value \(self)" + ) return nil } return .init(value: self, unit: unit) } - } // MARK: - 5. Extension on UnitValue for Direct Conversions -// This extension adds computed properties to `UnitValue` instances, -// allowing you to write `myValue.lbs` or `myValue.g`. -// Each property attempts to convert the value to the specified unit. -extension UnitValue { +// Now, these return UnitValue? because the conversion +// always results in a Double value. +extension UnitValue where ValueType: ConvertibleToDouble { // Apply constraints to the extension // MARK: Mass Conversions - public var kg: UnitValue? { converted(to: .kilogram) } - public var g: UnitValue? { converted(to: .gram) } - public var mg: UnitValue? { converted(to: .milligram) } - public var µg: UnitValue? { converted(to: .microgram) } - public var lbs: UnitValue? { converted(to: .pound) } - public var oz: UnitValue? { converted(to: .ounce) } - public var ton: UnitValue? { converted(to: .metricTon) } + public var kg: UnitValue? { converted(to: .kilogram) } + public var g: UnitValue? { converted(to: .gram) } + public var mg: UnitValue? { converted(to: .milligram) } + public var µg: UnitValue? { converted(to: .microgram) } + public var lbs: UnitValue? { converted(to: .pound) } + public var oz: UnitValue? { converted(to: .ounce) } + public var ton: UnitValue? { converted(to: .metricTon) } // MARK: Length Conversions - public var m: UnitValue? { converted(to: .meter) } - public var cm: UnitValue? { converted(to: .centimeter) } - public var mm: UnitValue? { converted(to: .millimeter) } - public var km: UnitValue? { converted(to: .kilometer) } - public var `in`: UnitValue? { converted(to: .inch) } - public var ft: UnitValue? { converted(to: .foot) } - public var yd: UnitValue? { converted(to: .yard) } - public var mi: UnitValue? { converted(to: .mile) } - public var nmi: UnitValue? { converted(to: .nauticalMile) } + public var m: UnitValue? { converted(to: .meter) } + public var cm: UnitValue? { converted(to: .centimeter) } + public var mm: UnitValue? { converted(to: .millimeter) } + public var km: UnitValue? { converted(to: .kilometer) } + public var `in`: UnitValue? { converted(to: .inch) } + public var ft: UnitValue? { converted(to: .foot) } + public var yd: UnitValue? { converted(to: .yard) } + public var mi: UnitValue? { converted(to: .mile) } + public var nmi: UnitValue? { converted(to: .nauticalMile) } // MARK: Volume Conversions - public var L: UnitValue? { converted(to: .liter) } - public var mL: UnitValue? { converted(to: .milliliter) } - public var m3: UnitValue? { converted(to: .cubicMeter) } - public var cm3: UnitValue? { converted(to: .cubicCentimeter) } - public var gal: UnitValue? { converted(to: .gallon) } - public var qt: UnitValue? { converted(to: .quart) } - public var pt: UnitValue? { converted(to: .pint) } - public var fl_oz: UnitValue? { converted(to: .fluidOunce) } + public var L: UnitValue? { converted(to: .liter) } + public var mL: UnitValue? { converted(to: .milliliter) } + public var m3: UnitValue? { converted(to: .cubicMeter) } + public var cm3: UnitValue? { converted(to: .cubicCentimeter) } + public var gal: UnitValue? { converted(to: .gallon) } + public var qt: UnitValue? { converted(to: .quart) } + public var pt: UnitValue? { converted(to: .pint) } + public var fl_oz: UnitValue? { converted(to: .fluidOunce) } // MARK: Time Conversions - public var s: UnitValue? { converted(to: .second) } - public var ms: UnitValue? { converted(to: .millisecond) } - public var µs: UnitValue? { converted(to: .microsecond) } - public var min: UnitValue? { converted(to: .minute) } - public var hr: UnitValue? { converted(to: .hour) } - public var day: UnitValue? { converted(to: .day) } - public var wk: UnitValue? { converted(to: .week) } + public var s: UnitValue? { converted(to: .second) } + public var ms: UnitValue? { converted(to: .millisecond) } + public var µs: UnitValue? { converted(to: .microsecond) } + public var min: UnitValue? { converted(to: .minute) } + public var hr: UnitValue? { converted(to: .hour) } + public var day: UnitValue? { converted(to: .day) } + public var wk: UnitValue? { converted(to: .week) } // MARK: Temperature Conversions - public var C: UnitValue? { converted(to: .celsius) } - public var F: UnitValue? { converted(to: .fahrenheit) } - public var K: UnitValue? { converted(to: .kelvin) } + public var C: UnitValue? { converted(to: .celsius) } + public var F: UnitValue? { converted(to: .fahrenheit) } + public var K: UnitValue? { converted(to: .kelvin) } // MARK: Speed Conversions - public var mps: UnitValue? { converted(to: .metersPerSecond) } - public var kmh: UnitValue? { converted(to: .kilometersPerHour) } - public var mph: UnitValue? { converted(to: .milesPerHour) } - public var kn: UnitValue? { converted(to: .knots) } + public var mps: UnitValue? { converted(to: .metersPerSecond) } + public var kmh: UnitValue? { converted(to: .kilometersPerHour) } + public var mph: UnitValue? { converted(to: .milesPerHour) } + public var kn: UnitValue? { converted(to: .knots) } } diff --git a/Tests/UnitsTests/UnitsTests.swift b/Tests/UnitsTests/UnitsTests.swift index 07393af..f94582f 100644 --- a/Tests/UnitsTests/UnitsTests.swift +++ b/Tests/UnitsTests/UnitsTests.swift @@ -1,6 +1,6 @@ import Testing -@testable import Units // Assuming your library is in a module named 'Units' +@testable import Units @Test func unitCreationSubscriptTests() async throws { // Test creating UnitValue using string subscript on numeric literals @@ -8,13 +8,17 @@ import Testing #expect(tenKilosByString != nil) #expect(tenKilosByString?.value == 10.0) #expect(tenKilosByString?.unit == .kilogram) - print("Created 10 kg by string subscript: \(String(describing: tenKilosByString))") + print( + "Created 10 kg by string subscript: \(String(describing: tenKilosByString))" + ) let fiveMetersByString = 5.0["m"] #expect(fiveMetersByString != nil) #expect(fiveMetersByString?.value == 5.0) #expect(fiveMetersByString?.unit == .meter) - print("Created 5 m by string subscript: \(String(describing: fiveMetersByString))") + print( + "Created 5 m by string subscript: \(String(describing: fiveMetersByString))" + ) // Test creating UnitValue using enum subscript on numeric literals let twentyLitersByEnum = 20.0[.liter] @@ -61,13 +65,17 @@ import Testing #expect(miles != nil) #expect(miles?.value == 62.13711922373339) // Exact conversion value #expect(miles?.unit == .mile) - print("100 km to mi (chained string subscript): \(String(describing: miles))") + print( + "100 km to mi (chained string subscript): \(String(describing: miles))" + ) let feetFromMiles = miles?["ft"] #expect(feetFromMiles != nil) #expect(feetFromMiles?.value == 328083.9895013123) // Exact conversion value (100km -> mi -> ft) #expect(feetFromMiles?.unit == .foot) - print("100 km to mi to ft (chained string subscript): \(String(describing: feetFromMiles))") + print( + "100 km to mi to ft (chained string subscript): \(String(describing: feetFromMiles))" + ) } else { #expect(false, "Failed to create initial 100 km unit.") }