API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPNumberFormatter.j
Go to the documentation of this file.
1 /*
2  * CPNumberFormatter.j
3  * Foundation
4  *
5  * Created by Alexander Ljungberg.
6  * Copyright 2011, WireLoad Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 #import "Ref.h"
24 
25 
26 #define UPDATE_NUMBER_HANDLER_IF_NECESSARY() if (!_numberHandler) \
27  _numberHandler = [CPDecimalNumberHandler decimalNumberHandlerWithRoundingMode:_roundingMode scale:_maximumFractionDigits raiseOnExactness:NO raiseOnOverflow:NO raiseOnUnderflow:NO raiseOnDivideByZero:YES];
28 #define SET_NEEDS_NUMBER_HANDLER_UPDATE() _numberHandler = nil;
29 
36 
42 CPNumberFormatterRoundHalfDown = _CPRoundHalfDown;
44 
53 @implementation CPNumberFormatter : CPFormatter
54 {
55  CPNumberFormatterStyle _numberStyle;
56  CPString _perMillSymbol;
57  CPString _groupingSeparator;
58  CPNumberFormatterRoundingMode _roundingMode;
59  CPUInteger _minimumFractionDigits;
60  CPUInteger _maximumFractionDigits;
61  CPString _currencyCode;
62  CPString _currencySymbol;
63  BOOL _generatesDecimalNumbers;
64 
65  // Note that we do not implement the 10.0 style number formatter, but the 10.4+ formatter. Therefore
66  // we don't expose this through a `roundingBehavior` property.
67  CPDecimalNumberHandler _numberHandler;
68 }
69 
70 - (id)init
71 {
72  if (self = [super init])
73  {
74  _roundingMode = CPNumberFormatterRoundHalfEven;
75  _minimumFractionDigits = 0;
76  _maximumFractionDigits = 0;
77  _groupingSeparator = @",";
78  _generatesDecimalNumbers = YES;
79 
80  // FIXME Add locale support.
81  _currencyCode = @"USD";
82  _currencySymbol = @"$";
83  }
84 
85  return self;
86 }
87 
88 - (CPString)stringFromNumber:(CPNumber)number
89 {
90  if (_numberStyle == CPNumberFormatterPercentStyle)
91  {
92  number *= 100.0;
93  }
94 
95  var dcmn = [number isKindOfClass:CPDecimalNumber] ? number : [[CPDecimalNumber alloc] _initWithJSNumber:number];
96 
97  // TODO Add locale support.
98  switch (_numberStyle)
99  {
104 
105  dcmn = [dcmn decimalNumberByRoundingAccordingToBehavior:_numberHandler];
106 
107  var output = [dcmn descriptionWithLocale:nil],
108  parts = [output componentsSeparatedByString:"."], // FIXME Locale specific.
109  preFraction = parts[0],
110  fraction = parts.length > 1 ? parts[1] : "",
111  preFractionLength = [preFraction length],
112  commaPosition = 3;
113 
114  while (fraction.length < _minimumFractionDigits)
115  fraction += "0";
116 
117  // TODO This is just a temporary solution. Should be generalised.
118  // Add in thousands separators.
119  if (_groupingSeparator)
120  {
121  for (var commaPosition = 3, prefLength = [preFraction length]; commaPosition < prefLength; commaPosition += 4)
122  {
123  preFraction = [preFraction stringByReplacingCharactersInRange:CPMakeRange(prefLength - commaPosition, 0) withString:_groupingSeparator];
124  prefLength += 1;
125  }
126  }
127 
128  var string = preFraction;
129  if (fraction)
130  string += "." + fraction;
131 
132  if (_numberStyle === CPNumberFormatterCurrencyStyle)
133  {
134  if (_currencySymbol)
135  string = _currencySymbol + string;
136  else
137  string = _currencyCode + string;
138  }
139 
140  if (_numberStyle == CPNumberFormatterPercentStyle)
141  {
142  string += "%";
143  }
144 
145  return string;
146  default:
147  return [number description];
148  }
149 }
150 
151 - (CPNumber)numberFromString:(CPString)aString
152 {
153  if (_generatesDecimalNumbers)
154  return [CPDecimalNumber decimalNumberWithString:aString];
155  else
156  return parseFloat(aString);
157 }
158 
159 - (CPString)stringForObjectValue:(id)anObject
160 {
161  if ([anObject isKindOfClass:[CPNumber class]])
162  return [self stringFromNumber:anObject];
163  else
164  return [anObject description];
165 }
166 
167 - (CPString)editingStringForObjectValue:(id)anObject
168 {
169  return [self stringForObjectValue:anObject];
170 }
171 
172 - (BOOL)getObjectValue:(id)anObject forString:(CPString)aString errorDescription:(CPString)anError
173 {
174  // TODO Error handling.
175  var value = [self numberFromString:aString];
176  AT_DEREF(anObject, value);
177 
178  return YES;
179 }
180 
181 - (void)setNumberStyle:(CPNumberFormatterStyle)aStyle
182 {
183  _numberStyle = aStyle;
184 
185  switch (aStyle)
186  {
188  _minimumFractionDigits = 0;
189  _maximumFractionDigits = 3;
191  break;
193  _minimumFractionDigits = 2;
194  _maximumFractionDigits = 2;
196  break;
197  }
198 }
199 
200 - (void)setRoundingMode:(CPNumberFormatterRoundingMode)aRoundingMode
201 {
202  _roundingMode = aRoundingMode;
204 }
205 
206 - (void)setMinimumFractionDigits:(CPUInteger)aNumber
207 {
208  _minimumFractionDigits = aNumber;
210 }
211 
212 - (void)setMaximumFractionDigits:(CPUInteger)aNumber
213 {
214  _maximumFractionDigits = aNumber;
216 }
217 
218 @end
219 
220 var CPNumberFormatterStyleKey = "CPNumberFormatterStyleKey",
221  CPNumberFormatterMinimumFractionDigitsKey = @"CPNumberFormatterMinimumFractionDigitsKey",
222  CPNumberFormatterMaximumFractionDigitsKey = @"CPNumberFormatterMaximumFractionDigitsKey",
223  CPNumberFormatterRoundingModeKey = @"CPNumberFormatterRoundingModeKey",
224  CPNumberFormatterGroupingSeparatorKey = @"CPNumberFormatterGroupingSeparatorKey",
225  CPNumberFormatterCurrencyCodeKey = @"CPNumberFormatterCurrencyCodeKey",
226  CPNumberFormatterCurrencySymbolKey = @"CPNumberFormatterCurrencySymbolKey",
227  CPNumberFormatterGeneratesDecimalNumbers = @"CPNumberFormatterGeneratesDecimalNumbers";
228 
230 
231 - (id)initWithCoder:(CPCoder)aCoder
232 {
233  self = [super initWithCoder:aCoder];
234 
235  if (self)
236  {
237  _numberStyle = [aCoder decodeIntForKey:CPNumberFormatterStyleKey];
238  _minimumFractionDigits = [aCoder decodeIntForKey:CPNumberFormatterMinimumFractionDigitsKey];
239  _maximumFractionDigits = [aCoder decodeIntForKey:CPNumberFormatterMaximumFractionDigitsKey];
240  _roundingMode = [aCoder decodeIntForKey:CPNumberFormatterRoundingModeKey];
241  _groupingSeparator = [aCoder decodeObjectForKey:CPNumberFormatterGroupingSeparatorKey];
242  _currencyCode = [aCoder decodeObjectForKey:CPNumberFormatterCurrencyCodeKey];
243  _currencySymbol = [aCoder decodeObjectForKey:CPNumberFormatterCurrencySymbolKey];
244  _generatesDecimalNumbers = [aCoder decodeBoolForKey:CPNumberFormatterGeneratesDecimalNumbers];
245  }
246 
247  return self;
248 }
249 
250 - (void)encodeWithCoder:(CPCoder)aCoder
251 {
252  [super encodeWithCoder:aCoder];
253 
254  [aCoder encodeInt:_numberStyle forKey:CPNumberFormatterStyleKey];
255  [aCoder encodeInt:_minimumFractionDigits forKey:CPNumberFormatterMinimumFractionDigitsKey];
256  [aCoder encodeInt:_maximumFractionDigits forKey:CPNumberFormatterMaximumFractionDigitsKey];
257  [aCoder encodeInt:_roundingMode forKey:CPNumberFormatterRoundingModeKey];
258  [aCoder encodeObject:_groupingSeparator forKey:CPNumberFormatterGroupingSeparatorKey];
259  [aCoder encodeObject:_currencyCode forKey:CPNumberFormatterCurrencyCodeKey];
260  [aCoder encodeObject:_currencySymbol forKey:CPNumberFormatterCurrencySymbolKey];
261  [aCoder encodeBool:_generatesDecimalNumbers forKey:CPNumberFormatterGeneratesDecimalNumbers];
262 }
263 
264 @end
265 
267 
271 - (CPNumberFormatterStyle)numberStyle
272 {
273  return _numberStyle;
274 }
275 
279 - (void)setNumberStyle:(CPNumberFormatterStyle)aValue
280 {
281  _numberStyle = aValue;
282 }
283 
287 - (CPString)perMillSymbol
288 {
289  return _perMillSymbol;
290 }
291 
295 - (void)setPerMillSymbol:(CPString)aValue
296 {
297  _perMillSymbol = aValue;
298 }
299 
303 - (CPString)groupingSeparator
304 {
305  return _groupingSeparator;
306 }
307 
311 - (void)setGroupingSeparator:(CPString)aValue
312 {
313  _groupingSeparator = aValue;
314 }
315 
319 - (CPNumberFormatterRoundingMode)roundingMode
320 {
321  return _roundingMode;
322 }
323 
327 - (void)setRoundingMode:(CPNumberFormatterRoundingMode)aValue
328 {
329  _roundingMode = aValue;
330 }
331 
335 - (CPUInteger)minimumFractionDigits
336 {
337  return _minimumFractionDigits;
338 }
339 
343 - (void)setMinimumFractionDigits:(CPUInteger)aValue
344 {
345  _minimumFractionDigits = aValue;
346 }
347 
351 - (CPUInteger)maximumFractionDigits
352 {
353  return _maximumFractionDigits;
354 }
355 
359 - (void)setMaximumFractionDigits:(CPUInteger)aValue
360 {
361  _maximumFractionDigits = aValue;
362 }
363 
367 - (CPString)currencyCode
368 {
369  return _currencyCode;
370 }
371 
375 - (void)setCurrencyCode:(CPString)aValue
376 {
377  _currencyCode = aValue;
378 }
379 
383 - (CPString)currencySymbol
384 {
385  return _currencySymbol;
386 }
387 
391 - (void)setCurrencySymbol:(CPString)aValue
392 {
393  _currencySymbol = aValue;
394 }
395 
399 - (BOOL)generatesDecimalNumbers
400 {
401  return _generatesDecimalNumbers;
402 }
403 
407 - (void)setGeneratesDecimalNumbers:(BOOL)aValue
408 {
409  _generatesDecimalNumbers = aValue;
410 }
411 
412 @end