7
7
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
8
8
//
9
9
10
-
11
10
extension LengthFormatter {
12
- public enum Unit : Int {
13
-
14
- case millimeter
15
- case centimeter
16
- case meter
17
- case kilometer
18
- case inch
19
- case foot
20
- case yard
21
- case mile
11
+ public enum Unit : Int {
12
+ case millimeter = 8
13
+ case centimeter = 9
14
+ case meter = 11
15
+ case kilometer = 14
16
+ case inch = 1281
17
+ case foot = 1282
18
+ case yard = 1283
19
+ case mile = 1284
22
20
}
23
21
}
24
22
25
23
open class LengthFormatter : Formatter {
26
24
25
+ public override init ( ) {
26
+ numberFormatter = NumberFormatter ( )
27
+ numberFormatter. numberStyle = . decimal
28
+ unitStyle = . medium
29
+ isForPersonHeightUse = false
30
+ super. init ( )
31
+ }
32
+
27
33
public required init ? ( coder: NSCoder ) {
28
- NSUnimplemented ( )
34
+ numberFormatter = NumberFormatter ( )
35
+ numberFormatter. numberStyle = . decimal
36
+ unitStyle = . medium
37
+ isForPersonHeightUse = false
38
+ super. init ( coder: coder)
29
39
}
30
40
31
41
/*@NSCopying*/ open var numberFormatter : NumberFormatter ! // default is NSNumberFormatter with NSNumberFormatterDecimalStyle
@@ -34,20 +44,183 @@ open class LengthFormatter : Formatter {
34
44
open var isForPersonHeightUse : Bool // default is NO; if it is set to YES, the number argument for -stringFromMeters: and -unitStringFromMeters: is considered as a person's height
35
45
36
46
// Format a combination of a number and an unit to a localized string.
37
- open func string( fromValue value: Double , unit: LengthFormatter . Unit ) -> String { NSUnimplemented ( ) }
47
+ open func string( fromValue value: Double , unit: LengthFormatter . Unit ) -> String {
48
+ guard let formattedValue = numberFormatter. string ( from: NSNumber ( value: value) ) else {
49
+ fatalError ( " Cannot format \( value) as string " )
50
+ }
51
+ let separator = unitStyle == LengthFormatter . UnitStyle. short ? " " : " "
52
+ return " \( formattedValue) \( separator) \( unitString ( fromValue: value, unit: unit) ) "
53
+ }
38
54
39
55
// Format a number in meters to a localized string with the locale-appropriate unit and an appropriate scale (e.g. 4.3m = 14.1ft in the US locale).
40
- open func string( fromMeters numberInMeters: Double ) -> String { NSUnimplemented ( ) }
56
+ open func string( fromMeters numberInMeters: Double ) -> String {
57
+ //Convert to the locale-appropriate unit
58
+ let unitFromMeters = unit ( fromMeters: numberInMeters)
59
+
60
+ //Map the unit to UnitLength type for conversion later
61
+ let unitLengthFromMeters = LengthFormatter . unitLength [ unitFromMeters] !
62
+
63
+ //Create a measurement object based on the value in meters
64
+ let meterMeasurement = Measurement < UnitLength > ( value: numberInMeters, unit: . meters)
65
+
66
+ //Convert the object to the locale-appropriate unit determined above
67
+ let unitMeasurement = meterMeasurement. converted ( to: unitLengthFromMeters)
68
+
69
+ //Extract the number from the measurement
70
+ let numberInUnit = unitMeasurement. value
71
+
72
+ if isForPersonHeightUse && !LengthFormatter. isMetricSystemLocale ( numberFormatter. locale) {
73
+ let feet = numberInUnit. rounded ( . towardZero)
74
+ let feetString = string ( fromValue: feet, unit: . foot)
75
+
76
+ let inches = abs ( numberInUnit. truncatingRemainder ( dividingBy: 1.0 ) ) * 12
77
+ let inchesString = string ( fromValue: inches, unit: . inch)
78
+
79
+ return ( " \( feetString) , \( inchesString) " )
80
+ }
81
+ return string ( fromValue: numberInUnit, unit: unitFromMeters)
82
+ }
41
83
42
84
// Return a localized string of the given unit, and if the unit is singular or plural is based on the given number.
43
- open func unitString( fromValue value: Double , unit: Unit ) -> String { NSUnimplemented ( ) }
85
+ open func unitString( fromValue value: Double , unit: Unit ) -> String {
86
+ if unitStyle == . short {
87
+ return LengthFormatter . shortSymbol [ unit] !
88
+ } else if unitStyle == . medium {
89
+ return LengthFormatter . mediumSymbol [ unit] !
90
+ } else if value == 1.0 {
91
+ return LengthFormatter . largeSingularSymbol [ unit] !
92
+ } else {
93
+ return LengthFormatter . largePluralSymbol [ unit] !
94
+ }
95
+ }
44
96
45
97
// Return the locale-appropriate unit, the same unit used by -stringFromMeters:.
46
- open func unitString( fromMeters numberInMeters: Double , usedUnit unitp: UnsafeMutablePointer < Unit > ? ) -> String { NSUnimplemented ( ) }
98
+ open func unitString( fromMeters numberInMeters: Double , usedUnit unitp: UnsafeMutablePointer < Unit > ? ) -> String {
99
+
100
+ //Convert to the locale-appropriate unit
101
+ let unitFromMeters = unit ( fromMeters: numberInMeters)
102
+ unitp? . pointee = unitFromMeters
103
+
104
+ //Map the unit to UnitLength type for conversion later
105
+ let unitLengthFromMeters = LengthFormatter . unitLength [ unitFromMeters] !
106
+
107
+ //Create a measurement object based on the value in meters
108
+ let meterMeasurement = Measurement < UnitLength > ( value: numberInMeters, unit: . meters)
109
+
110
+ //Convert the object to the locale-appropriate unit determined above
111
+ let unitMeasurement = meterMeasurement. converted ( to: unitLengthFromMeters)
112
+
113
+ //Extract the number from the measurement
114
+ let numberInUnit = unitMeasurement. value
115
+
116
+ //Return the appropriate representation of the unit based on the selected unit style
117
+ return unitString ( fromValue: numberInUnit, unit: unitFromMeters)
118
+ }
119
+
120
+ /// This method selects the appropriate unit based on the formatter’s locale,
121
+ /// the magnitude of the value, and isForPersonHeightUse property.
122
+ ///
123
+ /// - Parameter numberInMeters: the magnitude in terms of meters
124
+ /// - Returns: Returns the appropriate unit
125
+ private func unit( fromMeters numberInMeters: Double ) -> Unit {
126
+ if LengthFormatter . isMetricSystemLocale ( numberFormatter. locale) {
127
+ //Person height is always returned in cm for metric system
128
+ if isForPersonHeightUse { return . centimeter }
129
+
130
+ if numberInMeters > 1000 || numberInMeters < 0.0 {
131
+ return . kilometer
132
+ } else if numberInMeters > 1.0 {
133
+ return . meter
134
+ } else if numberInMeters > 0.01 {
135
+ return . centimeter
136
+ } else { return . millimeter }
137
+ } else {
138
+ //Person height is always returned in ft for U.S. system
139
+ if isForPersonHeightUse { return . foot }
140
+
141
+ let metricMeasurement = Measurement < UnitLength > ( value: numberInMeters, unit: . meters)
142
+ let usMeasurement = metricMeasurement. converted ( to: . feet)
143
+ let numberInFeet = usMeasurement. value
144
+
145
+ if numberInFeet < 0.0 || numberInFeet > 5280 {
146
+ return . mile
147
+ } else if numberInFeet > 3 || numberInFeet == 0.0 {
148
+ return . yard
149
+ } else if numberInFeet >= 1.0 {
150
+ return . foot
151
+ } else { return . inch }
152
+ }
153
+ }
154
+
155
+ /// TODO: Replace calls to the below function to use Locale.usesMetricSystem
156
+ /// Temporary workaround due to unpopulated Locale attributes
157
+ /// See https://bugs.swift.org/browse/SR-3202
158
+ private static func isMetricSystemLocale( _ locale: Locale ) -> Bool {
159
+ switch locale. identifier {
160
+ case " en_US " : return false
161
+ case " en_US_POSIX " : return false
162
+ case " haw_US " : return false
163
+ case " es_US " : return false
164
+ case " chr_US " : return false
165
+ case " my_MM " : return false
166
+ case " en_LR " : return false
167
+ case " vai_LR " : return false
168
+ default : return true
169
+ }
170
+ }
47
171
48
172
/// - Experiment: This is a draft API currently under consideration for official import into Foundation as a suitable alternative
49
173
/// - Note: Since this API is under consideration it may be either removed or revised in the near future
50
174
open override func objectValue( _ string: String ) throws -> Any ? { return nil }
51
- }
52
-
175
+
176
+
177
+ /// Maps NSLengthFormatter.Unit enum to UnitLength class. Used for measurement conversion.
178
+ private static let unitLength : [ Unit : UnitLength ] = [ . millimeter: . millimeters,
179
+ . centimeter: . centimeters,
180
+ . meter: . meters,
181
+ . kilometer: . kilometers,
182
+ . inch: . inches,
183
+ . foot: . feet,
184
+ . yard: . yards,
185
+ . mile: . miles]
53
186
187
+ /// Maps a unit to its short symbol. Reuses strings from UnitLength wherever possible.
188
+ private static let shortSymbol : [ Unit : String ] = [ . millimeter: UnitLength . millimeters. symbol,
189
+ . centimeter: UnitLength . centimeters. symbol,
190
+ . meter: UnitLength . meters. symbol,
191
+ . kilometer: UnitLength . kilometers. symbol,
192
+ . inch: " \" " ,
193
+ . foot: " ′ " ,
194
+ . yard: UnitLength . yards. symbol,
195
+ . mile: UnitLength . miles. symbol]
196
+
197
+ /// Maps a unit to its medium symbol. Reuses strings from UnitLength.
198
+ private static let mediumSymbol : [ Unit : String ] = [ . millimeter: UnitLength . millimeters. symbol,
199
+ . centimeter: UnitLength . centimeters. symbol,
200
+ . meter: UnitLength . meters. symbol,
201
+ . kilometer: UnitLength . kilometers. symbol,
202
+ . inch: UnitLength . inches. symbol,
203
+ . foot: UnitLength . feet. symbol,
204
+ . yard: UnitLength . yards. symbol,
205
+ . mile: UnitLength . miles. symbol]
206
+
207
+ /// Maps a unit to its large, singular symbol.
208
+ private static let largeSingularSymbol : [ Unit : String ] = [ . millimeter: " millimeter " ,
209
+ . centimeter: " centimeter " ,
210
+ . meter: " meter " ,
211
+ . kilometer: " kilometer " ,
212
+ . inch: " inch " ,
213
+ . foot: " foot " ,
214
+ . yard: " yard " ,
215
+ . mile: " mile " ]
216
+
217
+ /// Maps a unit to its large, plural symbol.
218
+ private static let largePluralSymbol : [ Unit : String ] = [ . millimeter: " millimeters " ,
219
+ . centimeter: " centimeters " ,
220
+ . meter: " meters " ,
221
+ . kilometer: " kilometers " ,
222
+ . inch: " inches " ,
223
+ . foot: " feet " ,
224
+ . yard: " yards " ,
225
+ . mile: " miles " ]
226
+ }
0 commit comments