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 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
17 // Permission is hereby granted, free of charge, to any person obtaining
18 // a copy of this software and associated documentation files (the
19 // "Software"), to deal in the Software without restriction, including
20 // without limitation the rights to use, copy, modify, merge, publish,
21 // distribute, sublicense, and/or sell copies of the Software, and to
22 // permit persons to whom the Software is furnished to do so, subject to
23 // the following conditions:
25 // The above copyright notice and this permission notice shall be
26 // included in all copies or substantial portions of the Software.
28 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38 // NumberFormatInfo. One can only assume it is the class gotten
39 // back from a GetFormat() method from an IFormatProvider /
40 // IFormattable implementer. There are some discrepencies with the
41 // ECMA spec and the SDK docs, surprisingly. See my conversation
42 // with myself on it at:
43 // http://lists.ximian.com/archives/public/mono-list/2001-July/000794.html
45 // Other than that this is totally ECMA compliant.
48 namespace System.Globalization {
51 [System.Runtime.InteropServices.ComVisible(false)]
54 public sealed class NumberFormatInfo : ICloneable, IFormatProvider {
55 private bool isReadOnly;
56 // used for temporary storage. Used in InitPatterns ()
57 string decimalFormats;
58 string currencyFormats;
59 string percentFormats;
60 string digitPattern = "#";
61 string zeroPattern = "0";
63 // Currency Related Format Info
64 private int currencyDecimalDigits;
65 private string currencyDecimalSeparator;
66 private string currencyGroupSeparator;
67 private int[] currencyGroupSizes;
68 private int currencyNegativePattern;
69 private int currencyPositivePattern;
70 private string currencySymbol;
72 private string nanSymbol;
73 private string negativeInfinitySymbol;
74 private string negativeSign;
76 // Number Related Format Info
77 private int numberDecimalDigits;
78 private string numberDecimalSeparator;
79 private string numberGroupSeparator;
80 private int[] numberGroupSizes;
81 private int numberNegativePattern;
83 // Percent Related Format Info
84 private int percentDecimalDigits;
85 private string percentDecimalSeparator;
86 private string percentGroupSeparator;
87 private int[] percentGroupSizes;
88 private int percentNegativePattern;
89 private int percentPositivePattern;
90 private string percentSymbol;
92 private string perMilleSymbol;
93 private string positiveInfinitySymbol;
94 private string positiveSign;
96 string ansiCurrencySymbol; // TODO, MS.NET serializes this.
97 int m_dataItem; // Unused, but MS.NET serializes this.
98 bool m_useUserOverride; // Unused, but MS.NET serializes this.
99 bool validForParseAsNumber; // Unused, but MS.NET serializes this.
100 bool validForParseAsCurrency; // Unused, but MS.NET serializes this.
102 internal NumberFormatInfo (int lcid)
104 //FIXME: should add more LCID
105 // CultureInfo uses this one also.
111 // The Invariant Culture Info ID.
115 // Currency Related Format Info
116 currencyDecimalDigits = 2;
117 currencyDecimalSeparator = ".";
118 currencyGroupSeparator = ",";
119 currencyGroupSizes = new int[1] { 3 };
120 currencyNegativePattern = 0;
121 currencyPositivePattern = 0;
122 currencySymbol = "$";
125 negativeInfinitySymbol = "-Infinity";
128 // Number Related Format Info
129 numberDecimalDigits = 2;
130 numberDecimalSeparator = ".";
131 numberGroupSeparator = ",";
132 numberGroupSizes = new int[1] { 3 };
133 numberNegativePattern = 1;
135 // Percent Related Format Info
136 percentDecimalDigits = 2;
137 percentDecimalSeparator = ".";
138 percentGroupSeparator = ",";
139 percentGroupSizes = new int[1] { 3 };
140 percentNegativePattern = 0;
141 percentPositivePattern = 0;
144 perMilleSymbol = "\u2030";
145 positiveInfinitySymbol = "Infinity";
151 public NumberFormatInfo () : this (0x007f)
155 // this is called by mono/mono/metadata/locales.c
158 string [] partOne, partTwo;
159 string [] posNeg = decimalFormats.Split (new char [1] {';'}, 2);
161 if (posNeg.Length == 2) {
163 partOne = posNeg [0].Split (new char [1] {'.'}, 2);
165 if (partOne.Length == 2) {
166 // assumed same for both positive and negative
167 // decimal digit side
168 numberDecimalDigits = 0;
169 for (int i = 0; i < partOne [1].Length; i ++) {
170 if (partOne [1][i] == digitPattern [0]) {
171 numberDecimalDigits ++;
176 // decimal grouping side
177 partTwo = partOne [0].Split (',');
178 if (partTwo.Length > 1) {
179 numberGroupSizes = new int [partTwo.Length - 1];
180 for (int i = 0; i < numberGroupSizes.Length; i ++) {
181 string pat = partTwo [i + 1];
182 numberGroupSizes [i] = pat.Length;
185 numberGroupSizes = new int [1] { 0 };
188 if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith (")")) {
189 numberNegativePattern = 0;
190 } else if (posNeg [1].StartsWith ("- ")) {
191 numberNegativePattern = 2;
192 } else if (posNeg [1].StartsWith ("-")) {
193 numberNegativePattern = 1;
194 } else if (posNeg [1].EndsWith (" -")) {
195 numberNegativePattern = 4;
196 } else if (posNeg [1].EndsWith ("-")) {
197 numberNegativePattern = 3;
199 numberNegativePattern = 1;
204 posNeg = currencyFormats.Split (new char [1] {';'}, 2);
205 if (posNeg.Length == 2) {
206 partOne = posNeg [0].Split (new char [1] {'.'}, 2);
208 if (partOne.Length == 2) {
209 // assumed same for both positive and negative
210 // decimal digit side
211 currencyDecimalDigits = 0;
212 for (int i = 0; i < partOne [1].Length; i ++) {
213 if (partOne [1][i] == zeroPattern [0])
214 currencyDecimalDigits ++;
219 // decimal grouping side
220 partTwo = partOne [0].Split (',');
221 if (partTwo.Length > 1) {
222 currencyGroupSizes = new int [partTwo.Length - 1];
223 for (int i = 0; i < currencyGroupSizes.Length; i ++) {
224 string pat = partTwo [i + 1];
225 currencyGroupSizes [i] = pat.Length;
228 currencyGroupSizes = new int [1] { 0 };
231 if (posNeg [1].StartsWith ("(\u00a4 ") && posNeg [1].EndsWith (")")) {
232 currencyNegativePattern = 14;
233 } else if (posNeg [1].StartsWith ("(\u00a4") && posNeg [1].EndsWith (")")) {
234 currencyNegativePattern = 0;
235 } else if (posNeg [1].StartsWith ("\u00a4 ") && posNeg [1].EndsWith ("-")) {
236 currencyNegativePattern = 11;
237 } else if (posNeg [1].StartsWith ("\u00a4") && posNeg [1].EndsWith ("-")) {
238 currencyNegativePattern = 3;
239 } else if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith (" \u00a4")) {
240 currencyNegativePattern = 15;
241 } else if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith ("\u00a4")) {
242 currencyNegativePattern = 4;
243 } else if (posNeg [1].StartsWith ("-") && posNeg [1].EndsWith (" \u00a4")) {
244 currencyNegativePattern = 8;
245 } else if (posNeg [1].StartsWith ("-") && posNeg [1].EndsWith ("\u00a4")) {
246 currencyNegativePattern = 5;
247 } else if (posNeg [1].StartsWith ("-\u00a4 ")) {
248 currencyNegativePattern = 9;
249 } else if (posNeg [1].StartsWith ("-\u00a4")) {
250 currencyNegativePattern = 1;
251 } else if (posNeg [1].StartsWith ("\u00a4 -")) {
252 currencyNegativePattern = 12;
253 } else if (posNeg [1].StartsWith ("\u00a4-")) {
254 currencyNegativePattern = 2;
255 } else if (posNeg [1].EndsWith (" \u00a4-")) {
256 currencyNegativePattern = 10;
257 } else if (posNeg [1].EndsWith ("\u00a4-")) {
258 currencyNegativePattern = 7;
259 } else if (posNeg [1].EndsWith ("- \u00a4")) {
260 currencyNegativePattern = 13;
261 } else if (posNeg [1].EndsWith ("-\u00a4")) {
262 currencyNegativePattern = 6;
264 currencyNegativePattern = 0;
267 if (posNeg [0].StartsWith ("\u00a4 ")) {
268 currencyPositivePattern = 2;
269 } else if (posNeg [0].StartsWith ("\u00a4")) {
270 currencyPositivePattern = 0;
271 } else if (posNeg [0].EndsWith (" \u00a4")) {
272 currencyPositivePattern = 3;
273 } else if (posNeg [0].EndsWith ("\u00a4")) {
274 currencyPositivePattern = 1;
276 currencyPositivePattern = 0;
281 // we don't have percentNegativePattern in CLDR so
282 // the percentNegativePattern are just guesses
283 if (percentFormats.StartsWith ("%")) {
284 percentPositivePattern = 2;
285 percentNegativePattern = 2;
286 } else if (percentFormats.EndsWith (" %")) {
287 percentPositivePattern = 0;
288 percentNegativePattern = 0;
289 } else if (percentFormats.EndsWith ("%")) {
290 percentPositivePattern = 1;
291 percentNegativePattern = 1;
293 percentPositivePattern = 0;
294 percentNegativePattern = 0;
297 partOne = percentFormats.Split (new char [1] {'.'}, 2);
299 if (partOne.Length == 2) {
300 // assumed same for both positive and negative
301 // decimal digit side
302 percentDecimalDigits = 0;
303 for (int i = 0; i < partOne [1].Length; i ++) {
304 if (partOne [1][i] == digitPattern [0])
305 percentDecimalDigits ++;
310 // percent grouping side
311 partTwo = partOne [0].Split (',');
312 if (partTwo.Length > 1) {
313 percentGroupSizes = new int [partTwo.Length - 1];
314 for (int i = 0; i < percentGroupSizes.Length; i ++) {
315 string pat = partTwo [i + 1];
316 percentGroupSizes [i] = pat.Length;
319 percentGroupSizes = new int [1] { 0 };
325 // =========== Currency Format Properties =========== //
327 public int CurrencyDecimalDigits {
329 return currencyDecimalDigits;
333 if (value < 0 || value > 99)
334 throw new ArgumentOutOfRangeException
335 ("The value specified for the property is less than 0 or greater than 99");
338 throw new InvalidOperationException
339 ("The current instance is read-only and a set operation was attempted");
341 currencyDecimalDigits = value;
345 public string CurrencyDecimalSeparator {
347 return currencyDecimalSeparator;
352 throw new ArgumentNullException
353 ("The value specified for the property is a null reference");
356 throw new InvalidOperationException
357 ("The current instance is read-only and a set operation was attempted");
359 currencyDecimalSeparator = value;
364 public string CurrencyGroupSeparator {
366 return currencyGroupSeparator;
371 throw new ArgumentNullException
372 ("The value specified for the property is a null reference");
375 throw new InvalidOperationException
376 ("The current instance is read-only and a set operation was attempted");
378 currencyGroupSeparator = value;
382 public int[] CurrencyGroupSizes {
384 return (int []) currencyGroupSizes.Clone ();
389 throw new ArgumentNullException
390 ("The value specified for the property is a null reference");
393 throw new InvalidOperationException
394 ("The current instance is read-only and a set operation was attempted");
396 if (value.Length == 0) {
397 currencyGroupSizes = new int [0];
401 // All elements except last need to be in range 1 - 9, last can be 0.
402 int last = value.Length - 1;
404 for (int i = 0; i < last; i++)
405 if (value[i] < 1 || value[i] > 9)
406 throw new ArgumentOutOfRangeException
407 ("One of the elements in the array specified is not between 1 and 9");
409 if (value[last] < 0 || value[last] > 9)
410 throw new ArgumentOutOfRangeException
411 ("Last element in the array specified is not between 0 and 9");
413 currencyGroupSizes = (int[]) value.Clone();
417 public int CurrencyNegativePattern {
419 // See ECMA NumberFormatInfo page 8
420 return currencyNegativePattern;
424 if (value < 0 || value > 15)
425 throw new ArgumentOutOfRangeException
426 ("The value specified for the property is less than 0 or greater than 15");
429 throw new InvalidOperationException
430 ("The current instance is read-only and a set operation was attempted");
432 currencyNegativePattern = value;
436 public int CurrencyPositivePattern {
438 // See ECMA NumberFormatInfo page 11
439 return currencyPositivePattern;
443 if (value < 0 || value > 3)
444 throw new ArgumentOutOfRangeException
445 ("The value specified for the property is less than 0 or greater than 3");
448 throw new InvalidOperationException
449 ("The current instance is read-only and a set operation was attempted");
451 currencyPositivePattern = value;
455 public string CurrencySymbol {
457 return currencySymbol;
462 throw new ArgumentNullException
463 ("The value specified for the property is a null reference");
466 throw new InvalidOperationException
467 ("The current instance is read-only and a set operation was attempted");
469 currencySymbol = value;
473 // =========== Static Read-Only Properties =========== //
475 public static NumberFormatInfo CurrentInfo {
477 NumberFormatInfo nfi = (NumberFormatInfo) System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat;
478 nfi.isReadOnly = true;
483 public static NumberFormatInfo InvariantInfo {
485 // This uses invariant info, which is same as in the constructor
486 NumberFormatInfo nfi = new NumberFormatInfo ();
487 nfi.NumberNegativePattern = 1;
488 nfi.isReadOnly = true;
493 public bool IsReadOnly {
501 public string NaNSymbol {
508 throw new ArgumentNullException
509 ("The value specified for the property is a null reference");
512 throw new InvalidOperationException
513 ("The current instance is read-only and a set operation was attempted");
519 public string NegativeInfinitySymbol {
521 return negativeInfinitySymbol;
526 throw new ArgumentNullException
527 ("The value specified for the property is a null reference");
530 throw new InvalidOperationException
531 ("The current instance is read-only and a set operation was attempted");
533 negativeInfinitySymbol = value;
537 public string NegativeSign {
544 throw new ArgumentNullException
545 ("The value specified for the property is a null reference");
548 throw new InvalidOperationException
549 ("The current instance is read-only and a set operation was attempted");
551 negativeSign = value;
555 // =========== Number Format Properties =========== //
557 public int NumberDecimalDigits {
559 return numberDecimalDigits;
563 if (value < 0 || value > 99)
564 throw new ArgumentOutOfRangeException
565 ("The value specified for the property is less than 0 or greater than 99");
568 throw new InvalidOperationException
569 ("The current instance is read-only and a set operation was attempted");
571 numberDecimalDigits = value;
575 public string NumberDecimalSeparator {
577 return numberDecimalSeparator;
582 throw new ArgumentNullException
583 ("The value specified for the property is a null reference");
586 throw new InvalidOperationException
587 ("The current instance is read-only and a set operation was attempted");
589 numberDecimalSeparator = value;
594 public string NumberGroupSeparator {
596 return numberGroupSeparator;
601 throw new ArgumentNullException
602 ("The value specified for the property is a null reference");
605 throw new InvalidOperationException
606 ("The current instance is read-only and a set operation was attempted");
608 numberGroupSeparator = value;
612 public int[] NumberGroupSizes {
614 return (int []) numberGroupSizes.Clone ();
619 throw new ArgumentNullException
620 ("The value specified for the property is a null reference");
623 throw new InvalidOperationException
624 ("The current instance is read-only and a set operation was attempted");
626 if (value.Length == 0) {
627 numberGroupSizes = new int [0];
630 // All elements except last need to be in range 1 - 9, last can be 0.
631 int last = value.Length - 1;
633 for (int i = 0; i < last; i++)
634 if (value[i] < 1 || value[i] > 9)
635 throw new ArgumentOutOfRangeException
636 ("One of the elements in the array specified is not between 1 and 9");
638 if (value[last] < 0 || value[last] > 9)
639 throw new ArgumentOutOfRangeException
640 ("Last element in the array specified is not between 0 and 9");
642 numberGroupSizes = (int[]) value.Clone();
646 public int NumberNegativePattern {
648 // See ECMA NumberFormatInfo page 27
649 return numberNegativePattern;
653 if (value < 0 || value > 4)
654 throw new ArgumentOutOfRangeException
655 ("The value specified for the property is less than 0 or greater than 15");
658 throw new InvalidOperationException
659 ("The current instance is read-only and a set operation was attempted");
661 numberNegativePattern = value;
665 // =========== Percent Format Properties =========== //
667 public int PercentDecimalDigits {
669 return percentDecimalDigits;
673 if (value < 0 || value > 99)
674 throw new ArgumentOutOfRangeException
675 ("The value specified for the property is less than 0 or greater than 99");
678 throw new InvalidOperationException
679 ("The current instance is read-only and a set operation was attempted");
681 percentDecimalDigits = value;
685 public string PercentDecimalSeparator {
687 return percentDecimalSeparator;
692 throw new ArgumentNullException
693 ("The value specified for the property is a null reference");
696 throw new InvalidOperationException
697 ("The current instance is read-only and a set operation was attempted");
699 percentDecimalSeparator = value;
704 public string PercentGroupSeparator {
706 return percentGroupSeparator;
711 throw new ArgumentNullException
712 ("The value specified for the property is a null reference");
715 throw new InvalidOperationException
716 ("The current instance is read-only and a set operation was attempted");
718 percentGroupSeparator = value;
722 public int[] PercentGroupSizes {
724 return (int []) percentGroupSizes.Clone ();
729 throw new ArgumentNullException
730 ("The value specified for the property is a null reference");
733 throw new InvalidOperationException
734 ("The current instance is read-only and a set operation was attempted");
736 if (this == CultureInfo.CurrentCulture.NumberFormat)
737 throw new Exception ("HERE the value was modified");
739 if (value.Length == 0) {
740 percentGroupSizes = new int [0];
744 // All elements except last need to be in range 1 - 9, last can be 0.
745 int last = value.Length - 1;
747 for (int i = 0; i < last; i++)
748 if (value[i] < 1 || value[i] > 9)
749 throw new ArgumentOutOfRangeException
750 ("One of the elements in the array specified is not between 1 and 9");
752 if (value[last] < 0 || value[last] > 9)
753 throw new ArgumentOutOfRangeException
754 ("Last element in the array specified is not between 0 and 9");
756 percentGroupSizes = (int[]) value.Clone();
760 public int PercentNegativePattern {
762 // See ECMA NumberFormatInfo page 8
763 return percentNegativePattern;
767 if (value < 0 || value > 2)
768 throw new ArgumentOutOfRangeException
769 ("The value specified for the property is less than 0 or greater than 15");
772 throw new InvalidOperationException
773 ("The current instance is read-only and a set operation was attempted");
775 percentNegativePattern = value;
779 public int PercentPositivePattern {
781 // See ECMA NumberFormatInfo page 11
782 return percentPositivePattern;
786 if (value < 0 || value > 2)
787 throw new ArgumentOutOfRangeException
788 ("The value specified for the property is less than 0 or greater than 3");
791 throw new InvalidOperationException
792 ("The current instance is read-only and a set operation was attempted");
794 percentPositivePattern = value;
798 public string PercentSymbol {
800 return percentSymbol;
805 throw new ArgumentNullException
806 ("The value specified for the property is a null reference");
809 throw new InvalidOperationException
810 ("The current instance is read-only and a set operation was attempted");
812 percentSymbol = value;
816 public string PerMilleSymbol {
818 return perMilleSymbol;
823 throw new ArgumentNullException
824 ("The value specified for the property is a null reference");
827 throw new InvalidOperationException
828 ("The current instance is read-only and a set operation was attempted");
830 perMilleSymbol = value;
834 public string PositiveInfinitySymbol {
836 return positiveInfinitySymbol;
841 throw new ArgumentNullException
842 ("The value specified for the property is a null reference");
845 throw new InvalidOperationException
846 ("The current instance is read-only and a set operation was attempted");
848 positiveInfinitySymbol = value;
852 public string PositiveSign {
859 throw new ArgumentNullException
860 ("The value specified for the property is a null reference");
863 throw new InvalidOperationException
864 ("The current instance is read-only and a set operation was attempted");
866 positiveSign = value;
870 public object GetFormat (Type formatType)
872 return (formatType == typeof (NumberFormatInfo)) ? this : null;
875 public object Clone ()
877 NumberFormatInfo clone = (NumberFormatInfo) MemberwiseClone();
878 // clone is not read only
879 clone.isReadOnly = false;
883 public static NumberFormatInfo ReadOnly (NumberFormatInfo nfi)
885 NumberFormatInfo copy = (NumberFormatInfo)nfi.Clone();
886 copy.isReadOnly = true;
890 public static NumberFormatInfo GetInstance(IFormatProvider provider)
892 if (provider != null) {
893 NumberFormatInfo nfi;
894 nfi = (NumberFormatInfo)provider.GetFormat(typeof(NumberFormatInfo));