2 // System.Globalization.NumberFormatInfo.cs
5 // Derek Holden (dholden@draper.com)
6 // Bob Smith (bob@thestuff.net)
7 // Mohammad DAMT (mdamt@cdl2000.com)
10 // (C) Bob Smith http://www.thestuff.net
11 // (c) 2003, PT Cakram Datalingga Duaribu http://www.cdl2000.com
15 // NumberFormatInfo. One can only assume it is the class gotten
16 // back from a GetFormat() method from an IFormatProvider /
17 // IFormattable implementer. There are some discrepencies with the
18 // ECMA spec and the SDK docs, surprisingly. See my conversation
19 // with myself on it at:
20 // http://lists.ximian.com/archives/public/mono-list/2001-July/000794.html
22 // Other than that this is totally ECMA compliant.
25 namespace System.Globalization {
28 public sealed class NumberFormatInfo : ICloneable, IFormatProvider {
29 private bool isReadOnly;
30 // used for temporary storage. Used in InitPatterns ()
31 string decimalFormats;
32 string currencyFormats;
33 string percentFormats;
34 string digitPattern = "#";
35 string zeroPattern = "0";
37 // Currency Related Format Info
38 private int currencyDecimalDigits;
39 private string currencyDecimalSeparator;
40 private string currencyGroupSeparator;
41 private int[] currencyGroupSizes;
42 private int currencyNegativePattern;
43 private int currencyPositivePattern;
44 private string currencySymbol;
46 private string nanSymbol;
47 private string negativeInfinitySymbol;
48 private string negativeSign;
50 // Number Related Format Info
51 private int numberDecimalDigits;
52 private string numberDecimalSeparator;
53 private string numberGroupSeparator;
54 private int[] numberGroupSizes;
55 private int numberNegativePattern;
57 // Percent Related Format Info
58 private int percentDecimalDigits;
59 private string percentDecimalSeparator;
60 private string percentGroupSeparator;
61 private int[] percentGroupSizes;
62 private int percentNegativePattern;
63 private int percentPositivePattern;
64 private string percentSymbol;
66 private string perMilleSymbol;
67 private string positiveInfinitySymbol;
68 private string positiveSign;
70 string ansiCurrencySymbol; // TODO, MS.NET serializes this.
71 int m_dataItem; // Unused, but MS.NET serializes this.
72 bool m_useUserOverride; // Unused, but MS.NET serializes this.
73 bool validForParseAsNumber; // Unused, but MS.NET serializes this.
74 bool validForParseAsCurrency; // Unused, but MS.NET serializes this.
76 internal NumberFormatInfo (int lcid)
78 //FIXME: should add more LCID
79 // CultureInfo uses this one also.
85 // The Invariant Culture Info ID.
89 // Currency Related Format Info
90 currencyDecimalDigits = 2;
91 currencyDecimalSeparator = ".";
92 currencyGroupSeparator = ",";
93 currencyGroupSizes = new int[1] { 3 };
94 currencyNegativePattern = 0;
95 currencyPositivePattern = 0;
99 negativeInfinitySymbol = "-Infinity";
102 // Number Related Format Info
103 numberDecimalDigits = 2;
104 numberDecimalSeparator = ".";
105 numberGroupSeparator = ",";
106 numberGroupSizes = new int[1] { 3 };
107 numberNegativePattern = 1;
109 // Percent Related Format Info
110 percentDecimalDigits = 2;
111 percentDecimalSeparator = ".";
112 percentGroupSeparator = ",";
113 percentGroupSizes = new int[1] { 3 };
114 percentNegativePattern = 0;
115 percentPositivePattern = 0;
118 perMilleSymbol = "\u2030";
119 positiveInfinitySymbol = "Infinity";
125 public NumberFormatInfo () : this (0x007f)
129 // this is called by mono/mono/metadata/locales.c
132 string [] partOne, partTwo;
133 string [] posNeg = decimalFormats.Split (new char [1] {';'}, 2);
135 if (posNeg.Length == 2) {
137 partOne = posNeg [0].Split (new char [1] {'.'}, 2);
139 if (partOne.Length == 2) {
140 // assumed same for both positive and negative
141 // decimal digit side
142 numberDecimalDigits = 0;
143 for (int i = 0; i < partOne [1].Length; i ++) {
144 if (partOne [1][i] == digitPattern [0]) {
145 numberDecimalDigits ++;
150 // decimal grouping side
151 partTwo = partOne [0].Split (',');
152 if (partTwo.Length > 1) {
153 numberGroupSizes = new int [partTwo.Length - 1];
154 for (int i = 0; i < numberGroupSizes.Length; i ++) {
155 string pat = partTwo [i + 1];
156 numberGroupSizes [i] = pat.Length;
159 numberGroupSizes = new int [1] { 0 };
162 if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith (")")) {
163 numberNegativePattern = 0;
164 } else if (posNeg [1].StartsWith ("- ")) {
165 numberNegativePattern = 2;
166 } else if (posNeg [1].StartsWith ("-")) {
167 numberNegativePattern = 1;
168 } else if (posNeg [1].EndsWith (" -")) {
169 numberNegativePattern = 4;
170 } else if (posNeg [1].EndsWith ("-")) {
171 numberNegativePattern = 3;
173 numberNegativePattern = 1;
178 posNeg = currencyFormats.Split (new char [1] {';'}, 2);
179 if (posNeg.Length == 2) {
180 partOne = posNeg [0].Split (new char [1] {'.'}, 2);
182 if (partOne.Length == 2) {
183 // assumed same for both positive and negative
184 // decimal digit side
185 currencyDecimalDigits = 0;
186 for (int i = 0; i < partOne [1].Length; i ++) {
187 if (partOne [1][i] == zeroPattern [0])
188 currencyDecimalDigits ++;
193 // decimal grouping side
194 partTwo = partOne [0].Split (',');
195 if (partTwo.Length > 1) {
196 currencyGroupSizes = new int [partTwo.Length - 1];
197 for (int i = 0; i < currencyGroupSizes.Length; i ++) {
198 string pat = partTwo [i + 1];
199 currencyGroupSizes [i] = pat.Length;
202 currencyGroupSizes = new int [1] { 0 };
205 if (posNeg [1].StartsWith ("(\u00a4 ") && posNeg [1].EndsWith (")")) {
206 currencyNegativePattern = 14;
207 } else if (posNeg [1].StartsWith ("(\u00a4") && posNeg [1].EndsWith (")")) {
208 currencyNegativePattern = 0;
209 } else if (posNeg [1].StartsWith ("\u00a4 ") && posNeg [1].EndsWith ("-")) {
210 currencyNegativePattern = 11;
211 } else if (posNeg [1].StartsWith ("\u00a4") && posNeg [1].EndsWith ("-")) {
212 currencyNegativePattern = 3;
213 } else if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith (" \u00a4")) {
214 currencyNegativePattern = 15;
215 } else if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith ("\u00a4")) {
216 currencyNegativePattern = 4;
217 } else if (posNeg [1].StartsWith ("-") && posNeg [1].EndsWith (" \u00a4")) {
218 currencyNegativePattern = 8;
219 } else if (posNeg [1].StartsWith ("-") && posNeg [1].EndsWith ("\u00a4")) {
220 currencyNegativePattern = 5;
221 } else if (posNeg [1].StartsWith ("-\u00a4 ")) {
222 currencyNegativePattern = 9;
223 } else if (posNeg [1].StartsWith ("-\u00a4")) {
224 currencyNegativePattern = 1;
225 } else if (posNeg [1].StartsWith ("\u00a4 -")) {
226 currencyNegativePattern = 12;
227 } else if (posNeg [1].StartsWith ("\u00a4-")) {
228 currencyNegativePattern = 2;
229 } else if (posNeg [1].EndsWith (" \u00a4-")) {
230 currencyNegativePattern = 10;
231 } else if (posNeg [1].EndsWith ("\u00a4-")) {
232 currencyNegativePattern = 7;
233 } else if (posNeg [1].EndsWith ("- \u00a4")) {
234 currencyNegativePattern = 13;
235 } else if (posNeg [1].EndsWith ("-\u00a4")) {
236 currencyNegativePattern = 6;
238 currencyNegativePattern = 0;
241 if (posNeg [0].StartsWith ("\u00a4 ")) {
242 currencyPositivePattern = 2;
243 } else if (posNeg [0].StartsWith ("\u00a4")) {
244 currencyPositivePattern = 0;
245 } else if (posNeg [0].EndsWith (" \u00a4")) {
246 currencyPositivePattern = 3;
247 } else if (posNeg [0].EndsWith ("\u00a4")) {
248 currencyPositivePattern = 1;
250 currencyPositivePattern = 0;
255 // we don't have percentNegativePattern in CLDR so
256 // the percentNegativePattern are just guesses
257 if (percentFormats.StartsWith ("%")) {
258 percentPositivePattern = 2;
259 percentNegativePattern = 2;
260 } else if (percentFormats.EndsWith (" %")) {
261 percentPositivePattern = 0;
262 percentNegativePattern = 0;
263 } else if (percentFormats.EndsWith ("%")) {
264 percentPositivePattern = 1;
265 percentNegativePattern = 1;
267 percentPositivePattern = 0;
268 percentNegativePattern = 0;
271 partOne = percentFormats.Split (new char [1] {'.'}, 2);
273 if (partOne.Length == 2) {
274 // assumed same for both positive and negative
275 // decimal digit side
276 percentDecimalDigits = 0;
277 for (int i = 0; i < partOne [1].Length; i ++) {
278 if (partOne [1][i] == digitPattern [0])
279 percentDecimalDigits ++;
284 // percent grouping side
285 partTwo = partOne [0].Split (',');
286 if (partTwo.Length > 1) {
287 percentGroupSizes = new int [partTwo.Length - 1];
288 for (int i = 0; i < percentGroupSizes.Length; i ++) {
289 string pat = partTwo [i + 1];
290 percentGroupSizes [i] = pat.Length;
293 percentGroupSizes = new int [1] { 0 };
299 // =========== Currency Format Properties =========== //
301 public int CurrencyDecimalDigits {
303 return currencyDecimalDigits;
307 if (value < 0 || value > 99)
308 throw new ArgumentOutOfRangeException
309 ("The value specified for the property is less than 0 or greater than 99");
312 throw new InvalidOperationException
313 ("The current instance is read-only and a set operation was attempted");
315 currencyDecimalDigits = value;
319 public string CurrencyDecimalSeparator {
321 return currencyDecimalSeparator;
326 throw new ArgumentNullException
327 ("The value specified for the property is a null reference");
330 throw new InvalidOperationException
331 ("The current instance is read-only and a set operation was attempted");
333 currencyDecimalSeparator = value;
338 public string CurrencyGroupSeparator {
340 return currencyGroupSeparator;
345 throw new ArgumentNullException
346 ("The value specified for the property is a null reference");
349 throw new InvalidOperationException
350 ("The current instance is read-only and a set operation was attempted");
352 currencyGroupSeparator = value;
356 public int[] CurrencyGroupSizes {
358 return (int []) currencyGroupSizes.Clone ();
362 if (value == null || value.Length == 0)
363 throw new ArgumentNullException
364 ("The value specified for the property is a null reference");
367 throw new InvalidOperationException
368 ("The current instance is read-only and a set operation was attempted");
370 // All elements except last need to be in range 1 - 9, last can be 0.
371 int last = value.Length - 1;
373 for (int i = 0; i < last; i++)
374 if (value[i] < 1 || value[i] > 9)
375 throw new ArgumentOutOfRangeException
376 ("One of the elements in the array specified is not between 1 and 9");
378 if (value[last] < 0 || value[last] > 9)
379 throw new ArgumentOutOfRangeException
380 ("Last element in the array specified is not between 0 and 9");
382 currencyGroupSizes = (int[]) value.Clone();
386 public int CurrencyNegativePattern {
388 // See ECMA NumberFormatInfo page 8
389 return currencyNegativePattern;
393 if (value < 0 || value > 15)
394 throw new ArgumentOutOfRangeException
395 ("The value specified for the property is less than 0 or greater than 15");
398 throw new InvalidOperationException
399 ("The current instance is read-only and a set operation was attempted");
401 currencyNegativePattern = value;
405 public int CurrencyPositivePattern {
407 // See ECMA NumberFormatInfo page 11
408 return currencyPositivePattern;
412 if (value < 0 || value > 3)
413 throw new ArgumentOutOfRangeException
414 ("The value specified for the property is less than 0 or greater than 3");
417 throw new InvalidOperationException
418 ("The current instance is read-only and a set operation was attempted");
420 currencyPositivePattern = value;
424 public string CurrencySymbol {
426 return currencySymbol;
431 throw new ArgumentNullException
432 ("The value specified for the property is a null reference");
435 throw new InvalidOperationException
436 ("The current instance is read-only and a set operation was attempted");
438 currencySymbol = value;
442 // =========== Static Read-Only Properties =========== //
444 public static NumberFormatInfo CurrentInfo {
446 NumberFormatInfo nfi = (NumberFormatInfo) System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat;
447 nfi.isReadOnly = true;
452 public static NumberFormatInfo InvariantInfo {
454 // This uses invariant info, which is same as in the constructor
455 NumberFormatInfo nfi = new NumberFormatInfo ();
456 nfi.NumberNegativePattern = 1;
457 nfi.isReadOnly = true;
462 public bool IsReadOnly {
470 public string NaNSymbol {
477 throw new ArgumentNullException
478 ("The value specified for the property is a null reference");
481 throw new InvalidOperationException
482 ("The current instance is read-only and a set operation was attempted");
488 public string NegativeInfinitySymbol {
490 return negativeInfinitySymbol;
495 throw new ArgumentNullException
496 ("The value specified for the property is a null reference");
499 throw new InvalidOperationException
500 ("The current instance is read-only and a set operation was attempted");
502 negativeInfinitySymbol = value;
506 public string NegativeSign {
513 throw new ArgumentNullException
514 ("The value specified for the property is a null reference");
517 throw new InvalidOperationException
518 ("The current instance is read-only and a set operation was attempted");
520 negativeSign = value;
524 // =========== Number Format Properties =========== //
526 public int NumberDecimalDigits {
528 return numberDecimalDigits;
532 if (value < 0 || value > 99)
533 throw new ArgumentOutOfRangeException
534 ("The value specified for the property is less than 0 or greater than 99");
537 throw new InvalidOperationException
538 ("The current instance is read-only and a set operation was attempted");
540 numberDecimalDigits = value;
544 public string NumberDecimalSeparator {
546 return numberDecimalSeparator;
551 throw new ArgumentNullException
552 ("The value specified for the property is a null reference");
555 throw new InvalidOperationException
556 ("The current instance is read-only and a set operation was attempted");
558 numberDecimalSeparator = value;
563 public string NumberGroupSeparator {
565 return numberGroupSeparator;
570 throw new ArgumentNullException
571 ("The value specified for the property is a null reference");
574 throw new InvalidOperationException
575 ("The current instance is read-only and a set operation was attempted");
577 numberGroupSeparator = value;
581 public int[] NumberGroupSizes {
583 return (int []) numberGroupSizes.Clone ();
587 if (value == null || value.Length == 0)
588 throw new ArgumentNullException
589 ("The value specified for the property is a null reference");
592 throw new InvalidOperationException
593 ("The current instance is read-only and a set operation was attempted");
595 // All elements except last need to be in range 1 - 9, last can be 0.
596 int last = value.Length - 1;
598 for (int i = 0; i < last; i++)
599 if (value[i] < 1 || value[i] > 9)
600 throw new ArgumentOutOfRangeException
601 ("One of the elements in the array specified is not between 1 and 9");
603 if (value[last] < 0 || value[last] > 9)
604 throw new ArgumentOutOfRangeException
605 ("Last element in the array specified is not between 0 and 9");
607 numberGroupSizes = (int[]) value.Clone();
611 public int NumberNegativePattern {
613 // See ECMA NumberFormatInfo page 27
614 return numberNegativePattern;
618 if (value < 0 || value > 4)
619 throw new ArgumentOutOfRangeException
620 ("The value specified for the property is less than 0 or greater than 15");
623 throw new InvalidOperationException
624 ("The current instance is read-only and a set operation was attempted");
626 numberNegativePattern = value;
630 // =========== Percent Format Properties =========== //
632 public int PercentDecimalDigits {
634 return percentDecimalDigits;
638 if (value < 0 || value > 99)
639 throw new ArgumentOutOfRangeException
640 ("The value specified for the property is less than 0 or greater than 99");
643 throw new InvalidOperationException
644 ("The current instance is read-only and a set operation was attempted");
646 percentDecimalDigits = value;
650 public string PercentDecimalSeparator {
652 return percentDecimalSeparator;
657 throw new ArgumentNullException
658 ("The value specified for the property is a null reference");
661 throw new InvalidOperationException
662 ("The current instance is read-only and a set operation was attempted");
664 percentDecimalSeparator = value;
669 public string PercentGroupSeparator {
671 return percentGroupSeparator;
676 throw new ArgumentNullException
677 ("The value specified for the property is a null reference");
680 throw new InvalidOperationException
681 ("The current instance is read-only and a set operation was attempted");
683 percentGroupSeparator = value;
687 public int[] PercentGroupSizes {
689 return (int []) percentGroupSizes.Clone ();
693 if (value == null || value.Length == 0)
694 throw new ArgumentNullException
695 ("The value specified for the property is a null reference");
698 throw new InvalidOperationException
699 ("The current instance is read-only and a set operation was attempted");
701 if (this == CultureInfo.CurrentCulture.NumberFormat)
702 throw new Exception ("HERE the value was modified");
703 // All elements except last need to be in range 1 - 9, last can be 0.
704 int last = value.Length - 1;
706 for (int i = 0; i < last; i++)
707 if (value[i] < 1 || value[i] > 9)
708 throw new ArgumentOutOfRangeException
709 ("One of the elements in the array specified is not between 1 and 9");
711 if (value[last] < 0 || value[last] > 9)
712 throw new ArgumentOutOfRangeException
713 ("Last element in the array specified is not between 0 and 9");
715 percentGroupSizes = (int[]) value.Clone();
719 public int PercentNegativePattern {
721 // See ECMA NumberFormatInfo page 8
722 return percentNegativePattern;
726 if (value < 0 || value > 2)
727 throw new ArgumentOutOfRangeException
728 ("The value specified for the property is less than 0 or greater than 15");
731 throw new InvalidOperationException
732 ("The current instance is read-only and a set operation was attempted");
734 percentNegativePattern = value;
738 public int PercentPositivePattern {
740 // See ECMA NumberFormatInfo page 11
741 return percentPositivePattern;
745 if (value < 0 || value > 2)
746 throw new ArgumentOutOfRangeException
747 ("The value specified for the property is less than 0 or greater than 3");
750 throw new InvalidOperationException
751 ("The current instance is read-only and a set operation was attempted");
753 percentPositivePattern = value;
757 public string PercentSymbol {
759 return percentSymbol;
764 throw new ArgumentNullException
765 ("The value specified for the property is a null reference");
768 throw new InvalidOperationException
769 ("The current instance is read-only and a set operation was attempted");
771 percentSymbol = value;
775 public string PerMilleSymbol {
777 return perMilleSymbol;
782 throw new ArgumentNullException
783 ("The value specified for the property is a null reference");
786 throw new InvalidOperationException
787 ("The current instance is read-only and a set operation was attempted");
789 perMilleSymbol = value;
793 public string PositiveInfinitySymbol {
795 return positiveInfinitySymbol;
800 throw new ArgumentNullException
801 ("The value specified for the property is a null reference");
804 throw new InvalidOperationException
805 ("The current instance is read-only and a set operation was attempted");
807 positiveInfinitySymbol = value;
811 public string PositiveSign {
818 throw new ArgumentNullException
819 ("The value specified for the property is a null reference");
822 throw new InvalidOperationException
823 ("The current instance is read-only and a set operation was attempted");
825 positiveSign = value;
829 public object GetFormat (Type formatType)
831 return (formatType == typeof (NumberFormatInfo)) ? this : null;
834 public object Clone ()
836 NumberFormatInfo clone = (NumberFormatInfo) MemberwiseClone();
837 // clone is not read only
838 clone.isReadOnly = false;
842 public static NumberFormatInfo ReadOnly (NumberFormatInfo nfi)
844 NumberFormatInfo copy = (NumberFormatInfo)nfi.Clone();
845 copy.isReadOnly = true;
849 public static NumberFormatInfo GetInstance(IFormatProvider provider)
851 if (provider != null) {
852 NumberFormatInfo nfi;
853 nfi = (NumberFormatInfo)provider.GetFormat(typeof(NumberFormatInfo));