forked from swiftlang/swift-corelibs-foundation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathEnergyFormatter.swift
185 lines (152 loc) · 6.8 KB
/
EnergyFormatter.swift
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
// 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 .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"
}
}
}
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)
}
}