| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See http://swift.org/LICENSE.txt for license information |
| // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| |
| extension EnergyFormatter { |
| public enum Unit: Int { |
| |
| case joule = 11 |
| case kilojoule = 14 |
| case calorie = 1793 // chemistry "calories", abbr "cal" |
| case kilocalorie = 1794 // kilocalories in general, abbr “kcal”, or “C” in some locales (e.g. US) when usesFoodEnergy is set to YES |
| |
| // Map Unit to UnitEnergy class to aid with conversions |
| fileprivate var unitEnergy: UnitEnergy { |
| switch self { |
| case .joule: |
| return UnitEnergy.joules |
| case .kilojoule: |
| return UnitEnergy.kilojoules |
| case .calorie: |
| return UnitEnergy.calories |
| case .kilocalorie: |
| return UnitEnergy.kilocalories |
| } |
| } |
| |
| // Reuse symbols defined in UnitEnergy, except for kilocalories, which is defined as "kCal" |
| fileprivate var symbol: String { |
| switch self { |
| case .kilocalorie: |
| return "kcal" |
| default: |
| return unitEnergy.symbol |
| } |
| } |
| |
| // Return singular, full string representation of the energy unit |
| fileprivate var singularString: String { |
| switch self { |
| case .joule: |
| return "joule" |
| case .kilojoule: |
| return "kilojoule" |
| case .calorie: |
| return "calorie" |
| case .kilocalorie: |
| return "kilocalorie" |
| } |
| } |
| |
| // Return plural, full string representation of the energy unit |
| fileprivate var pluralString: String { |
| return "\(self.singularString)s" |
| } |
| } |
| } |
| |
| open class EnergyFormatter: Formatter { |
| |
| public override init() { |
| numberFormatter = NumberFormatter() |
| numberFormatter.numberStyle = .decimal |
| unitStyle = .medium |
| isForFoodEnergyUse = false |
| super.init() |
| } |
| |
| public required init?(coder: NSCoder) { |
| numberFormatter = NumberFormatter() |
| numberFormatter.numberStyle = .decimal |
| unitStyle = .medium |
| isForFoodEnergyUse = false |
| super.init() |
| } |
| |
| /*@NSCopying*/ open var numberFormatter: NumberFormatter! // default is NumberFormatter with NumberFormatter.Style.decimal |
| open var unitStyle: UnitStyle // default is NSFormattingUnitStyleMedium |
| open var isForFoodEnergyUse: Bool // default is NO; if it is set to YES, EnergyFormatter.Unit.kilocalorie may be “C” instead of “kcal" |
| |
| // Format a combination of a number and an unit to a localized string. |
| open func string(fromValue value: Double, unit: Unit) -> String { |
| guard let formattedValue = numberFormatter.string(from:NSNumber(value: value)) else { |
| fatalError("Cannot format \(value) as string") |
| } |
| |
| let separator = unitStyle == EnergyFormatter.UnitStyle.short ? "" : " " |
| return "\(formattedValue)\(separator)\(unitString(fromValue: value, unit: unit))" |
| } |
| |
| // Format a number in joules to a localized string with the locale-appropriate unit and an appropriate scale (e.g. 10.3J = 2.46cal in the US locale). |
| open func string(fromJoules numberInJoules: Double) -> String { |
| |
| //Convert to the locale-appropriate unit |
| var unitFromJoules: EnergyFormatter.Unit = .joule |
| _ = self.unitString(fromJoules: numberInJoules, usedUnit: &unitFromJoules) |
| |
| //Map the unit to UnitLength type for conversion later |
| let unitEnergyFromJoules = unitFromJoules.unitEnergy |
| |
| //Create a measurement object based on the value in joules |
| let joulesMeasurement = Measurement<UnitEnergy>(value:numberInJoules, unit: .joules) |
| |
| //Convert the object to the locale-appropriate unit determined above |
| let unitMeasurement = joulesMeasurement.converted(to: unitEnergyFromJoules) |
| |
| //Extract the number from the measurement |
| let numberInUnit = unitMeasurement.value |
| |
| return string(fromValue: numberInUnit, unit: unitFromJoules) |
| } |
| |
| // Return a localized string of the given unit, and if the unit is singular or plural is based on the given number. |
| open func unitString(fromValue value: Double, unit: Unit) -> String { |
| |
| //Special case when isForFoodEnergyUse is true |
| if isForFoodEnergyUse && unit == .kilocalorie { |
| if unitStyle == .short { |
| return "C" |
| } else if unitStyle == .medium { |
| return "Cal" |
| } else { |
| return "Calories" |
| } |
| } |
| |
| if unitStyle == .short || unitStyle == .medium { |
| return unit.symbol |
| } else if value == 1.0 { |
| return unit.singularString |
| } else { |
| return unit.pluralString |
| } |
| } |
| |
| // Return the locale-appropriate unit, the same unit used by -stringFromJoules:. |
| open func unitString(fromJoules numberInJoules: Double, usedUnit unitp: UnsafeMutablePointer<Unit>?) -> String { |
| |
| //Convert to the locale-appropriate unit |
| let unitFromJoules: Unit |
| |
| if numberFormatter.locale.usesCalories { |
| if numberInJoules > 0 && numberInJoules <= 4184 { |
| unitFromJoules = .calorie |
| } else { |
| unitFromJoules = .kilocalorie |
| } |
| } else { |
| if numberInJoules > 0 && numberInJoules <= 1000 { |
| unitFromJoules = .joule |
| } else { |
| unitFromJoules = .kilojoule |
| } |
| } |
| unitp?.pointee = unitFromJoules |
| |
| //Map the unit to UnitEnergy type for conversion later |
| let unitEnergyFromJoules = unitFromJoules.unitEnergy |
| |
| //Create a measurement object based on the value in joules |
| let joulesMeasurement = Measurement<UnitEnergy>(value:numberInJoules, unit: .joules) |
| |
| //Convert the object to the locale-appropriate unit determined above |
| let unitMeasurement = joulesMeasurement.converted(to: unitEnergyFromJoules) |
| |
| //Extract the number from the measurement |
| let numberInUnit = unitMeasurement.value |
| |
| //Return the appropriate representation of the unit based on the selected unit style |
| return unitString(fromValue: numberInUnit, unit: unitFromJoules) |
| } |
| |
| /// - Experiment: This is a draft API currently under consideration for official import into Foundation as a suitable alternative |
| /// - Note: Since this API is under consideration it may be either removed or revised in the near future |
| open override func objectValue(_ string: String) throws -> Any? { return nil } |
| } |
| |
| /// TODO: Replace calls to the below function to use Locale.regionCode |
| /// Temporary workaround due to unpopulated Locale attributes |
| /// See https://bugs.swift.org/browse/SR-3202 |
| extension Locale { |
| public var usesCalories: Bool { |
| |
| switch self.identifier { |
| case "en_US": return true |
| case "en_US_POSIX": return true |
| case "haw_US": return true |
| case "es_US": return true |
| case "chr_US": return true |
| case "en_GB": return true |
| case "kw_GB": return true |
| case "cy_GB": return true |
| case "gv_GB": return true |
| default: return false |
| } |
| } |
| } |