1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
|
// 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, Sendable {
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 .joules
case .kilojoule:
return .kilojoules
case .calorie:
return .calories
case .kilocalorie:
return .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"
}
}
}
@available(*, unavailable)
extension EnergyFormatter : @unchecked Sendable { }
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 == .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 self.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)
}
/// Regions that use calories
private static let caloriesRegions: Set<String> = ["en_US", "en_US_POSIX", "haw_US", "es_US", "chr_US", "en_GB", "kw_GB", "cy_GB", "gv_GB"]
/// Whether the region uses calories
private var usesCalories: Bool {
return EnergyFormatter.caloriesRegions.contains(numberFormatter.locale.identifier)
}
}
|