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 string[] nativeDigits; // Unused, but MS.NET serializes this.
103 int digitSubstitution; // Unused, but MS.NET serializes this.
106 internal NumberFormatInfo (int lcid)
108 //FIXME: should add more LCID
109 // CultureInfo uses this one also.
115 // The Invariant Culture Info ID.
119 // Currency Related Format Info
120 currencyDecimalDigits = 2;
121 currencyDecimalSeparator = ".";
122 currencyGroupSeparator = ",";
123 currencyGroupSizes = new int[1] { 3 };
124 currencyNegativePattern = 0;
125 currencyPositivePattern = 0;
126 currencySymbol = "$";
129 negativeInfinitySymbol = "-Infinity";
132 // Number Related Format Info
133 numberDecimalDigits = 2;
134 numberDecimalSeparator = ".";
135 numberGroupSeparator = ",";
136 numberGroupSizes = new int[1] { 3 };
137 numberNegativePattern = 1;
139 // Percent Related Format Info
140 percentDecimalDigits = 2;
141 percentDecimalSeparator = ".";
142 percentGroupSeparator = ",";
143 percentGroupSizes = new int[1] { 3 };
144 percentNegativePattern = 0;
145 percentPositivePattern = 0;
148 perMilleSymbol = "\u2030";
149 positiveInfinitySymbol = "Infinity";
155 public NumberFormatInfo () : this (0x007f)
159 // this is called by mono/mono/metadata/locales.c
162 string [] partOne, partTwo;
163 string [] posNeg = decimalFormats.Split (new char [1] {';'}, 2);
165 if (posNeg.Length == 2) {
167 partOne = posNeg [0].Split (new char [1] {'.'}, 2);
169 if (partOne.Length == 2) {
170 // assumed same for both positive and negative
171 // decimal digit side
172 numberDecimalDigits = 0;
173 for (int i = 0; i < partOne [1].Length; i ++) {
174 if (partOne [1][i] == digitPattern [0]) {
175 numberDecimalDigits ++;
180 // decimal grouping side
181 partTwo = partOne [0].Split (',');
182 if (partTwo.Length > 1) {
183 numberGroupSizes = new int [partTwo.Length - 1];
184 for (int i = 0; i < numberGroupSizes.Length; i ++) {
185 string pat = partTwo [i + 1];
186 numberGroupSizes [i] = pat.Length;
189 numberGroupSizes = new int [1] { 0 };
192 if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith (")")) {
193 numberNegativePattern = 0;
194 } else if (posNeg [1].StartsWith ("- ")) {
195 numberNegativePattern = 2;
196 } else if (posNeg [1].StartsWith ("-")) {
197 numberNegativePattern = 1;
198 } else if (posNeg [1].EndsWith (" -")) {
199 numberNegativePattern = 4;
200 } else if (posNeg [1].EndsWith ("-")) {
201 numberNegativePattern = 3;
203 numberNegativePattern = 1;
208 posNeg = currencyFormats.Split (new char [1] {';'}, 2);
209 if (posNeg.Length == 2) {
210 partOne = posNeg [0].Split (new char [1] {'.'}, 2);
212 if (partOne.Length == 2) {
213 // assumed same for both positive and negative
214 // decimal digit side
215 currencyDecimalDigits = 0;
216 for (int i = 0; i < partOne [1].Length; i ++) {
217 if (partOne [1][i] == zeroPattern [0])
218 currencyDecimalDigits ++;
223 // decimal grouping side
224 partTwo = partOne [0].Split (',');
225 if (partTwo.Length > 1) {
226 currencyGroupSizes = new int [partTwo.Length - 1];
227 for (int i = 0; i < currencyGroupSizes.Length; i ++) {
228 string pat = partTwo [i + 1];
229 currencyGroupSizes [i] = pat.Length;
232 currencyGroupSizes = new int [1] { 0 };
235 if (posNeg [1].StartsWith ("(\u00a4 ") && posNeg [1].EndsWith (")")) {
236 currencyNegativePattern = 14;
237 } else if (posNeg [1].StartsWith ("(\u00a4") && posNeg [1].EndsWith (")")) {
238 currencyNegativePattern = 0;
239 } else if (posNeg [1].StartsWith ("\u00a4 ") && posNeg [1].EndsWith ("-")) {
240 currencyNegativePattern = 11;
241 } else if (posNeg [1].StartsWith ("\u00a4") && posNeg [1].EndsWith ("-")) {
242 currencyNegativePattern = 3;
243 } else if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith (" \u00a4")) {
244 currencyNegativePattern = 15;
245 } else if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith ("\u00a4")) {
246 currencyNegativePattern = 4;
247 } else if (posNeg [1].StartsWith ("-") && posNeg [1].EndsWith (" \u00a4")) {
248 currencyNegativePattern = 8;
249 } else if (posNeg [1].StartsWith ("-") && posNeg [1].EndsWith ("\u00a4")) {
250 currencyNegativePattern = 5;
251 } else if (posNeg [1].StartsWith ("-\u00a4 ")) {
252 currencyNegativePattern = 9;
253 } else if (posNeg [1].StartsWith ("-\u00a4")) {
254 currencyNegativePattern = 1;
255 } else if (posNeg [1].StartsWith ("\u00a4 -")) {
256 currencyNegativePattern = 12;
257 } else if (posNeg [1].StartsWith ("\u00a4-")) {
258 currencyNegativePattern = 2;
259 } else if (posNeg [1].EndsWith (" \u00a4-")) {
260 currencyNegativePattern = 10;
261 } else if (posNeg [1].EndsWith ("\u00a4-")) {
262 currencyNegativePattern = 7;
263 } else if (posNeg [1].EndsWith ("- \u00a4")) {
264 currencyNegativePattern = 13;
265 } else if (posNeg [1].EndsWith ("-\u00a4")) {
266 currencyNegativePattern = 6;
268 currencyNegativePattern = 0;
271 if (posNeg [0].StartsWith ("\u00a4 ")) {
272 currencyPositivePattern = 2;
273 } else if (posNeg [0].StartsWith ("\u00a4")) {
274 currencyPositivePattern = 0;
275 } else if (posNeg [0].EndsWith (" \u00a4")) {
276 currencyPositivePattern = 3;
277 } else if (posNeg [0].EndsWith ("\u00a4")) {
278 currencyPositivePattern = 1;
280 currencyPositivePattern = 0;
285 // we don't have percentNegativePattern in CLDR so
286 // the percentNegativePattern are just guesses
287 if (percentFormats.StartsWith ("%")) {
288 percentPositivePattern = 2;
289 percentNegativePattern = 2;
290 } else if (percentFormats.EndsWith (" %")) {
291 percentPositivePattern = 0;
292 percentNegativePattern = 0;
293 } else if (percentFormats.EndsWith ("%")) {
294 percentPositivePattern = 1;
295 percentNegativePattern = 1;
297 percentPositivePattern = 0;
298 percentNegativePattern = 0;
301 partOne = percentFormats.Split (new char [1] {'.'}, 2);
303 if (partOne.Length == 2) {
304 // assumed same for both positive and negative
305 // decimal digit side
306 percentDecimalDigits = 0;
307 for (int i = 0; i < partOne [1].Length; i ++) {
308 if (partOne [1][i] == digitPattern [0])
309 percentDecimalDigits ++;
314 // percent grouping side
315 partTwo = partOne [0].Split (',');
316 if (partTwo.Length > 1) {
317 percentGroupSizes = new int [partTwo.Length - 1];
318 for (int i = 0; i < percentGroupSizes.Length; i ++) {
319 string pat = partTwo [i + 1];
320 percentGroupSizes [i] = pat.Length;
323 percentGroupSizes = new int [1] { 0 };
329 // =========== Currency Format Properties =========== //
331 public int CurrencyDecimalDigits {
333 return currencyDecimalDigits;
337 if (value < 0 || value > 99)
338 throw new ArgumentOutOfRangeException
339 ("The value specified for the property is less than 0 or greater than 99");
342 throw new InvalidOperationException
343 ("The current instance is read-only and a set operation was attempted");
345 currencyDecimalDigits = value;
349 public string CurrencyDecimalSeparator {
351 return currencyDecimalSeparator;
356 throw new ArgumentNullException
357 ("The value specified for the property is a null reference");
360 throw new InvalidOperationException
361 ("The current instance is read-only and a set operation was attempted");
363 currencyDecimalSeparator = value;
368 public string CurrencyGroupSeparator {
370 return currencyGroupSeparator;
375 throw new ArgumentNullException
376 ("The value specified for the property is a null reference");
379 throw new InvalidOperationException
380 ("The current instance is read-only and a set operation was attempted");
382 currencyGroupSeparator = value;
386 public int[] CurrencyGroupSizes {
388 return (int []) currencyGroupSizes.Clone ();
393 throw new ArgumentNullException
394 ("The value specified for the property is a null reference");
397 throw new InvalidOperationException
398 ("The current instance is read-only and a set operation was attempted");
400 if (value.Length == 0) {
401 currencyGroupSizes = new int [0];
405 // All elements except last need to be in range 1 - 9, last can be 0.
406 int last = value.Length - 1;
408 for (int i = 0; i < last; i++)
409 if (value[i] < 1 || value[i] > 9)
410 throw new ArgumentOutOfRangeException
411 ("One of the elements in the array specified is not between 1 and 9");
413 if (value[last] < 0 || value[last] > 9)
414 throw new ArgumentOutOfRangeException
415 ("Last element in the array specified is not between 0 and 9");
417 currencyGroupSizes = (int[]) value.Clone();
421 public int CurrencyNegativePattern {
423 // See ECMA NumberFormatInfo page 8
424 return currencyNegativePattern;
428 if (value < 0 || value > 15)
429 throw new ArgumentOutOfRangeException
430 ("The value specified for the property is less than 0 or greater than 15");
433 throw new InvalidOperationException
434 ("The current instance is read-only and a set operation was attempted");
436 currencyNegativePattern = value;
440 public int CurrencyPositivePattern {
442 // See ECMA NumberFormatInfo page 11
443 return currencyPositivePattern;
447 if (value < 0 || value > 3)
448 throw new ArgumentOutOfRangeException
449 ("The value specified for the property is less than 0 or greater than 3");
452 throw new InvalidOperationException
453 ("The current instance is read-only and a set operation was attempted");
455 currencyPositivePattern = value;
459 public string CurrencySymbol {
461 return currencySymbol;
466 throw new ArgumentNullException
467 ("The value specified for the property is a null reference");
470 throw new InvalidOperationException
471 ("The current instance is read-only and a set operation was attempted");
473 currencySymbol = value;
477 // =========== Static Read-Only Properties =========== //
479 public static NumberFormatInfo CurrentInfo {
481 NumberFormatInfo nfi = (NumberFormatInfo) System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat;
482 nfi.isReadOnly = true;
487 public static NumberFormatInfo InvariantInfo {
489 // This uses invariant info, which is same as in the constructor
490 NumberFormatInfo nfi = new NumberFormatInfo ();
491 nfi.NumberNegativePattern = 1;
492 nfi.isReadOnly = true;
497 public bool IsReadOnly {
505 public string NaNSymbol {
512 throw new ArgumentNullException
513 ("The value specified for the property is a null reference");
516 throw new InvalidOperationException
517 ("The current instance is read-only and a set operation was attempted");
523 public string NegativeInfinitySymbol {
525 return negativeInfinitySymbol;
530 throw new ArgumentNullException
531 ("The value specified for the property is a null reference");
534 throw new InvalidOperationException
535 ("The current instance is read-only and a set operation was attempted");
537 negativeInfinitySymbol = value;
541 public string NegativeSign {
548 throw new ArgumentNullException
549 ("The value specified for the property is a null reference");
552 throw new InvalidOperationException
553 ("The current instance is read-only and a set operation was attempted");
555 negativeSign = value;
559 // =========== Number Format Properties =========== //
561 public int NumberDecimalDigits {
563 return numberDecimalDigits;
567 if (value < 0 || value > 99)
568 throw new ArgumentOutOfRangeException
569 ("The value specified for the property is less than 0 or greater than 99");
572 throw new InvalidOperationException
573 ("The current instance is read-only and a set operation was attempted");
575 numberDecimalDigits = value;
579 public string NumberDecimalSeparator {
581 return numberDecimalSeparator;
586 throw new ArgumentNullException
587 ("The value specified for the property is a null reference");
590 throw new InvalidOperationException
591 ("The current instance is read-only and a set operation was attempted");
593 numberDecimalSeparator = value;
598 public string NumberGroupSeparator {
600 return numberGroupSeparator;
605 throw new ArgumentNullException
606 ("The value specified for the property is a null reference");
609 throw new InvalidOperationException
610 ("The current instance is read-only and a set operation was attempted");
612 numberGroupSeparator = value;
616 public int[] NumberGroupSizes {
618 return (int []) numberGroupSizes.Clone ();
623 throw new ArgumentNullException
624 ("The value specified for the property is a null reference");
627 throw new InvalidOperationException
628 ("The current instance is read-only and a set operation was attempted");
630 if (value.Length == 0) {
631 numberGroupSizes = new int [0];
634 // All elements except last need to be in range 1 - 9, last can be 0.
635 int last = value.Length - 1;
637 for (int i = 0; i < last; i++)
638 if (value[i] < 1 || value[i] > 9)
639 throw new ArgumentOutOfRangeException
640 ("One of the elements in the array specified is not between 1 and 9");
642 if (value[last] < 0 || value[last] > 9)
643 throw new ArgumentOutOfRangeException
644 ("Last element in the array specified is not between 0 and 9");
646 numberGroupSizes = (int[]) value.Clone();
650 public int NumberNegativePattern {
652 // See ECMA NumberFormatInfo page 27
653 return numberNegativePattern;
657 if (value < 0 || value > 4)
658 throw new ArgumentOutOfRangeException
659 ("The value specified for the property is less than 0 or greater than 15");
662 throw new InvalidOperationException
663 ("The current instance is read-only and a set operation was attempted");
665 numberNegativePattern = value;
669 // =========== Percent Format Properties =========== //
671 public int PercentDecimalDigits {
673 return percentDecimalDigits;
677 if (value < 0 || value > 99)
678 throw new ArgumentOutOfRangeException
679 ("The value specified for the property is less than 0 or greater than 99");
682 throw new InvalidOperationException
683 ("The current instance is read-only and a set operation was attempted");
685 percentDecimalDigits = value;
689 public string PercentDecimalSeparator {
691 return percentDecimalSeparator;
696 throw new ArgumentNullException
697 ("The value specified for the property is a null reference");
700 throw new InvalidOperationException
701 ("The current instance is read-only and a set operation was attempted");
703 percentDecimalSeparator = value;
708 public string PercentGroupSeparator {
710 return percentGroupSeparator;
715 throw new ArgumentNullException
716 ("The value specified for the property is a null reference");
719 throw new InvalidOperationException
720 ("The current instance is read-only and a set operation was attempted");
722 percentGroupSeparator = value;
726 public int[] PercentGroupSizes {
728 return (int []) percentGroupSizes.Clone ();
733 throw new ArgumentNullException
734 ("The value specified for the property is a null reference");
737 throw new InvalidOperationException
738 ("The current instance is read-only and a set operation was attempted");
740 if (this == CultureInfo.CurrentCulture.NumberFormat)
741 throw new Exception ("HERE the value was modified");
743 if (value.Length == 0) {
744 percentGroupSizes = new int [0];
748 // All elements except last need to be in range 1 - 9, last can be 0.
749 int last = value.Length - 1;
751 for (int i = 0; i < last; i++)
752 if (value[i] < 1 || value[i] > 9)
753 throw new ArgumentOutOfRangeException
754 ("One of the elements in the array specified is not between 1 and 9");
756 if (value[last] < 0 || value[last] > 9)
757 throw new ArgumentOutOfRangeException
758 ("Last element in the array specified is not between 0 and 9");
760 percentGroupSizes = (int[]) value.Clone();
764 public int PercentNegativePattern {
766 // See ECMA NumberFormatInfo page 8
767 return percentNegativePattern;
771 if (value < 0 || value > 2)
772 throw new ArgumentOutOfRangeException
773 ("The value specified for the property is less than 0 or greater than 15");
776 throw new InvalidOperationException
777 ("The current instance is read-only and a set operation was attempted");
779 percentNegativePattern = value;
783 public int PercentPositivePattern {
785 // See ECMA NumberFormatInfo page 11
786 return percentPositivePattern;
790 if (value < 0 || value > 2)
791 throw new ArgumentOutOfRangeException
792 ("The value specified for the property is less than 0 or greater than 3");
795 throw new InvalidOperationException
796 ("The current instance is read-only and a set operation was attempted");
798 percentPositivePattern = value;
802 public string PercentSymbol {
804 return percentSymbol;
809 throw new ArgumentNullException
810 ("The value specified for the property is a null reference");
813 throw new InvalidOperationException
814 ("The current instance is read-only and a set operation was attempted");
816 percentSymbol = value;
820 public string PerMilleSymbol {
822 return perMilleSymbol;
827 throw new ArgumentNullException
828 ("The value specified for the property is a null reference");
831 throw new InvalidOperationException
832 ("The current instance is read-only and a set operation was attempted");
834 perMilleSymbol = value;
838 public string PositiveInfinitySymbol {
840 return positiveInfinitySymbol;
845 throw new ArgumentNullException
846 ("The value specified for the property is a null reference");
849 throw new InvalidOperationException
850 ("The current instance is read-only and a set operation was attempted");
852 positiveInfinitySymbol = value;
856 public string PositiveSign {
863 throw new ArgumentNullException
864 ("The value specified for the property is a null reference");
867 throw new InvalidOperationException
868 ("The current instance is read-only and a set operation was attempted");
870 positiveSign = value;
874 public object GetFormat (Type formatType)
876 return (formatType == typeof (NumberFormatInfo)) ? this : null;
879 public object Clone ()
881 NumberFormatInfo clone = (NumberFormatInfo) MemberwiseClone();
882 // clone is not read only
883 clone.isReadOnly = false;
887 public static NumberFormatInfo ReadOnly (NumberFormatInfo nfi)
889 NumberFormatInfo copy = (NumberFormatInfo)nfi.Clone();
890 copy.isReadOnly = true;
894 public static NumberFormatInfo GetInstance(IFormatProvider provider)
896 if (provider != null) {
897 NumberFormatInfo nfi;
898 nfi = (NumberFormatInfo)provider.GetFormat(typeof(NumberFormatInfo));