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 using System.Runtime.InteropServices;
50 namespace System.Globalization {
56 public sealed class NumberFormatInfo : ICloneable, IFormatProvider {
57 private bool isReadOnly;
58 // used for temporary storage. Used in InitPatterns ()
59 string decimalFormats;
60 string currencyFormats;
61 string percentFormats;
62 string digitPattern = "#";
63 string zeroPattern = "0";
65 // Currency Related Format Info
66 private int currencyDecimalDigits;
67 private string currencyDecimalSeparator;
68 private string currencyGroupSeparator;
69 private int[] currencyGroupSizes;
70 private int currencyNegativePattern;
71 private int currencyPositivePattern;
72 private string currencySymbol;
74 private string nanSymbol;
75 private string negativeInfinitySymbol;
76 private string negativeSign;
78 // Number Related Format Info
79 private int numberDecimalDigits;
80 private string numberDecimalSeparator;
81 private string numberGroupSeparator;
82 private int[] numberGroupSizes;
83 private int numberNegativePattern;
85 // Percent Related Format Info
86 private int percentDecimalDigits;
87 private string percentDecimalSeparator;
88 private string percentGroupSeparator;
89 private int[] percentGroupSizes;
90 private int percentNegativePattern;
91 private int percentPositivePattern;
92 private string percentSymbol;
94 private string perMilleSymbol;
95 private string positiveInfinitySymbol;
96 private string positiveSign;
98 #pragma warning disable 169
99 string ansiCurrencySymbol; // TODO, MS.NET serializes this.
100 int m_dataItem; // Unused, but MS.NET serializes this.
101 bool m_useUserOverride; // Unused, but MS.NET serializes this.
102 bool validForParseAsNumber; // Unused, but MS.NET serializes this.
103 bool validForParseAsCurrency; // Unused, but MS.NET serializes this.
104 #pragma warning restore 169
107 string[] nativeDigits = invariantNativeDigits;
108 int digitSubstitution = 1; // DigitShapes.None.
110 static readonly string [] invariantNativeDigits = new string [] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
113 internal NumberFormatInfo (int lcid, bool read_only)
115 isReadOnly = read_only;
117 //FIXME: should add more LCID
118 // CultureInfo uses this one also.
124 // The Invariant Culture Info ID.
128 // Currency Related Format Info
129 currencyDecimalDigits = 2;
130 currencyDecimalSeparator = ".";
131 currencyGroupSeparator = ",";
132 currencyGroupSizes = new int[1] { 3 };
133 currencyNegativePattern = 0;
134 currencyPositivePattern = 0;
135 currencySymbol = "$";
138 negativeInfinitySymbol = "-Infinity";
141 // Number Related Format Info
142 numberDecimalDigits = 2;
143 numberDecimalSeparator = ".";
144 numberGroupSeparator = ",";
145 numberGroupSizes = new int[1] { 3 };
146 numberNegativePattern = 1;
148 // Percent Related Format Info
149 percentDecimalDigits = 2;
150 percentDecimalSeparator = ".";
151 percentGroupSeparator = ",";
152 percentGroupSizes = new int[1] { 3 };
153 percentNegativePattern = 0;
154 percentPositivePattern = 0;
157 perMilleSymbol = "\u2030";
158 positiveInfinitySymbol = "Infinity";
164 internal NumberFormatInfo (bool read_only) : this (0x007f, read_only)
168 public NumberFormatInfo () : this (false)
172 // this is called by mono/mono/metadata/locales.c
173 #pragma warning disable 169
176 string [] partOne, partTwo;
177 string [] posNeg = decimalFormats.Split (new char [1] {';'}, 2);
179 if (posNeg.Length == 2) {
181 partOne = posNeg [0].Split (new char [1] {'.'}, 2);
183 if (partOne.Length == 2) {
184 // assumed same for both positive and negative
185 // decimal digit side
186 numberDecimalDigits = 0;
187 for (int i = 0; i < partOne [1].Length; i ++) {
188 if (partOne [1][i] == digitPattern [0]) {
189 numberDecimalDigits ++;
194 // decimal grouping side
195 partTwo = partOne [0].Split (',');
196 if (partTwo.Length > 1) {
197 numberGroupSizes = new int [partTwo.Length - 1];
198 for (int i = 0; i < numberGroupSizes.Length; i ++) {
199 string pat = partTwo [i + 1];
200 numberGroupSizes [i] = pat.Length;
203 numberGroupSizes = new int [1] { 0 };
206 if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith (")")) {
207 numberNegativePattern = 0;
208 } else if (posNeg [1].StartsWith ("- ")) {
209 numberNegativePattern = 2;
210 } else if (posNeg [1].StartsWith ("-")) {
211 numberNegativePattern = 1;
212 } else if (posNeg [1].EndsWith (" -")) {
213 numberNegativePattern = 4;
214 } else if (posNeg [1].EndsWith ("-")) {
215 numberNegativePattern = 3;
217 numberNegativePattern = 1;
222 posNeg = currencyFormats.Split (new char [1] {';'}, 2);
223 if (posNeg.Length == 2) {
224 partOne = posNeg [0].Split (new char [1] {'.'}, 2);
226 if (partOne.Length == 2) {
227 // assumed same for both positive and negative
228 // decimal digit side
229 currencyDecimalDigits = 0;
230 for (int i = 0; i < partOne [1].Length; i ++) {
231 if (partOne [1][i] == zeroPattern [0])
232 currencyDecimalDigits ++;
237 // decimal grouping side
238 partTwo = partOne [0].Split (',');
239 if (partTwo.Length > 1) {
240 currencyGroupSizes = new int [partTwo.Length - 1];
241 for (int i = 0; i < currencyGroupSizes.Length; i ++) {
242 string pat = partTwo [i + 1];
243 currencyGroupSizes [i] = pat.Length;
246 currencyGroupSizes = new int [1] { 0 };
249 if (posNeg [1].StartsWith ("(\u00a4 ") && posNeg [1].EndsWith (")")) {
250 currencyNegativePattern = 14;
251 } else if (posNeg [1].StartsWith ("(\u00a4") && posNeg [1].EndsWith (")")) {
252 currencyNegativePattern = 0;
253 } else if (posNeg [1].StartsWith ("\u00a4 ") && posNeg [1].EndsWith ("-")) {
254 currencyNegativePattern = 11;
255 } else if (posNeg [1].StartsWith ("\u00a4") && posNeg [1].EndsWith ("-")) {
256 currencyNegativePattern = 3;
257 } else if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith (" \u00a4")) {
258 currencyNegativePattern = 15;
259 } else if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith ("\u00a4")) {
260 currencyNegativePattern = 4;
261 } else if (posNeg [1].StartsWith ("-") && posNeg [1].EndsWith (" \u00a4")) {
262 currencyNegativePattern = 8;
263 } else if (posNeg [1].StartsWith ("-") && posNeg [1].EndsWith ("\u00a4")) {
264 currencyNegativePattern = 5;
265 } else if (posNeg [1].StartsWith ("-\u00a4 ")) {
266 currencyNegativePattern = 9;
267 } else if (posNeg [1].StartsWith ("-\u00a4")) {
268 currencyNegativePattern = 1;
269 } else if (posNeg [1].StartsWith ("\u00a4 -")) {
270 currencyNegativePattern = 12;
271 } else if (posNeg [1].StartsWith ("\u00a4-")) {
272 currencyNegativePattern = 2;
273 } else if (posNeg [1].EndsWith (" \u00a4-")) {
274 currencyNegativePattern = 10;
275 } else if (posNeg [1].EndsWith ("\u00a4-")) {
276 currencyNegativePattern = 7;
277 } else if (posNeg [1].EndsWith ("- \u00a4")) {
278 currencyNegativePattern = 13;
279 } else if (posNeg [1].EndsWith ("-\u00a4")) {
280 currencyNegativePattern = 6;
282 currencyNegativePattern = 0;
285 if (posNeg [0].StartsWith ("\u00a4 ")) {
286 currencyPositivePattern = 2;
287 } else if (posNeg [0].StartsWith ("\u00a4")) {
288 currencyPositivePattern = 0;
289 } else if (posNeg [0].EndsWith (" \u00a4")) {
290 currencyPositivePattern = 3;
291 } else if (posNeg [0].EndsWith ("\u00a4")) {
292 currencyPositivePattern = 1;
294 currencyPositivePattern = 0;
299 // we don't have percentNegativePattern in CLDR so
300 // the percentNegativePattern are just guesses
301 if (percentFormats.StartsWith ("%")) {
302 percentPositivePattern = 2;
303 percentNegativePattern = 2;
304 } else if (percentFormats.EndsWith (" %")) {
305 percentPositivePattern = 0;
306 percentNegativePattern = 0;
307 } else if (percentFormats.EndsWith ("%")) {
308 percentPositivePattern = 1;
309 percentNegativePattern = 1;
311 percentPositivePattern = 0;
312 percentNegativePattern = 0;
315 partOne = percentFormats.Split (new char [1] {'.'}, 2);
317 if (partOne.Length == 2) {
318 // assumed same for both positive and negative
319 // decimal digit side
320 percentDecimalDigits = 0;
321 for (int i = 0; i < partOne [1].Length; i ++) {
322 if (partOne [1][i] == digitPattern [0])
323 percentDecimalDigits ++;
328 // percent grouping side
329 partTwo = partOne [0].Split (',');
330 if (partTwo.Length > 1) {
331 percentGroupSizes = new int [partTwo.Length - 1];
332 for (int i = 0; i < percentGroupSizes.Length; i ++) {
333 string pat = partTwo [i + 1];
334 percentGroupSizes [i] = pat.Length;
337 percentGroupSizes = new int [1] { 0 };
342 #pragma warning restore 169
344 // =========== Currency Format Properties =========== //
346 public int CurrencyDecimalDigits {
348 return currencyDecimalDigits;
352 if (value < 0 || value > 99)
353 throw new ArgumentOutOfRangeException
354 ("The value specified for the property is less than 0 or greater than 99");
357 throw new InvalidOperationException
358 ("The current instance is read-only and a set operation was attempted");
360 currencyDecimalDigits = value;
364 public string CurrencyDecimalSeparator {
366 return currencyDecimalSeparator;
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 currencyDecimalSeparator = value;
383 public string CurrencyGroupSeparator {
385 return currencyGroupSeparator;
390 throw new ArgumentNullException
391 ("The value specified for the property is a null reference");
394 throw new InvalidOperationException
395 ("The current instance is read-only and a set operation was attempted");
397 currencyGroupSeparator = value;
401 public int[] CurrencyGroupSizes {
403 return (int []) RawCurrencyGroupSizes.Clone ();
407 RawCurrencyGroupSizes = value;
411 internal int[] RawCurrencyGroupSizes {
413 return currencyGroupSizes;
418 throw new ArgumentNullException
419 ("The value specified for the property is a null reference");
422 throw new InvalidOperationException
423 ("The current instance is read-only and a set operation was attempted");
425 if (value.Length == 0) {
426 currencyGroupSizes = new int [0];
430 // All elements except last need to be in range 1 - 9, last can be 0.
431 int last = value.Length - 1;
433 for (int i = 0; i < last; i++)
434 if (value[i] < 1 || value[i] > 9)
435 throw new ArgumentOutOfRangeException
436 ("One of the elements in the array specified is not between 1 and 9");
438 if (value[last] < 0 || value[last] > 9)
439 throw new ArgumentOutOfRangeException
440 ("Last element in the array specified is not between 0 and 9");
442 currencyGroupSizes = (int[]) value.Clone();
446 public int CurrencyNegativePattern {
448 // See ECMA NumberFormatInfo page 8
449 return currencyNegativePattern;
453 if (value < 0 || value > 15)
454 throw new ArgumentOutOfRangeException
455 ("The value specified for the property is less than 0 or greater than 15");
458 throw new InvalidOperationException
459 ("The current instance is read-only and a set operation was attempted");
461 currencyNegativePattern = value;
465 public int CurrencyPositivePattern {
467 // See ECMA NumberFormatInfo page 11
468 return currencyPositivePattern;
472 if (value < 0 || value > 3)
473 throw new ArgumentOutOfRangeException
474 ("The value specified for the property is less than 0 or greater than 3");
477 throw new InvalidOperationException
478 ("The current instance is read-only and a set operation was attempted");
480 currencyPositivePattern = value;
484 public string CurrencySymbol {
486 return currencySymbol;
491 throw new ArgumentNullException
492 ("The value specified for the property is a null reference");
495 throw new InvalidOperationException
496 ("The current instance is read-only and a set operation was attempted");
498 currencySymbol = value;
502 // =========== Static Read-Only Properties =========== //
504 public static NumberFormatInfo CurrentInfo {
506 NumberFormatInfo nfi = (NumberFormatInfo) System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat;
507 nfi.isReadOnly = true;
512 public static NumberFormatInfo InvariantInfo {
514 // This uses invariant info, which is same as in the constructor
515 NumberFormatInfo nfi = new NumberFormatInfo ();
516 nfi.NumberNegativePattern = 1;
517 nfi.isReadOnly = true;
522 public bool IsReadOnly {
530 public string NaNSymbol {
537 throw new ArgumentNullException
538 ("The value specified for the property is a null reference");
541 throw new InvalidOperationException
542 ("The current instance is read-only and a set operation was attempted");
549 [MonoNotSupported ("We don't have native digit info")]
551 public string [] NativeDigits {
552 get { return nativeDigits; }
555 throw new ArgumentNullException ("value");
556 if (value.Length != 10)
557 throw new ArgumentException ("Argument array length must be 10");
558 foreach (string s in value)
559 if (String.IsNullOrEmpty (s))
560 throw new ArgumentException ("Argument array contains one or more null strings");
561 nativeDigits = value;
565 [MonoNotSupported ("We don't have native digit info")]
567 public DigitShapes DigitSubstitution {
568 get { return (DigitShapes) digitSubstitution; }
569 set { digitSubstitution = (int) value; }
573 public string NegativeInfinitySymbol {
575 return negativeInfinitySymbol;
580 throw new ArgumentNullException
581 ("The value specified for the property is a null reference");
584 throw new InvalidOperationException
585 ("The current instance is read-only and a set operation was attempted");
587 negativeInfinitySymbol = value;
591 public string NegativeSign {
598 throw new ArgumentNullException
599 ("The value specified for the property is a null reference");
602 throw new InvalidOperationException
603 ("The current instance is read-only and a set operation was attempted");
605 negativeSign = value;
609 // =========== Number Format Properties =========== //
611 public int NumberDecimalDigits {
613 return numberDecimalDigits;
617 if (value < 0 || value > 99)
618 throw new ArgumentOutOfRangeException
619 ("The value specified for the property is less than 0 or greater than 99");
622 throw new InvalidOperationException
623 ("The current instance is read-only and a set operation was attempted");
625 numberDecimalDigits = value;
629 public string NumberDecimalSeparator {
631 return numberDecimalSeparator;
636 throw new ArgumentNullException
637 ("The value specified for the property is a null reference");
640 throw new InvalidOperationException
641 ("The current instance is read-only and a set operation was attempted");
643 numberDecimalSeparator = value;
648 public string NumberGroupSeparator {
650 return numberGroupSeparator;
655 throw new ArgumentNullException
656 ("The value specified for the property is a null reference");
659 throw new InvalidOperationException
660 ("The current instance is read-only and a set operation was attempted");
662 numberGroupSeparator = value;
666 public int[] NumberGroupSizes {
668 return (int []) RawNumberGroupSizes.Clone ();
672 RawNumberGroupSizes = value;
676 internal int[] RawNumberGroupSizes {
678 return numberGroupSizes;
683 throw new ArgumentNullException
684 ("The value specified for the property is a null reference");
687 throw new InvalidOperationException
688 ("The current instance is read-only and a set operation was attempted");
690 if (value.Length == 0) {
691 numberGroupSizes = new int [0];
694 // All elements except last need to be in range 1 - 9, last can be 0.
695 int last = value.Length - 1;
697 for (int i = 0; i < last; i++)
698 if (value[i] < 1 || value[i] > 9)
699 throw new ArgumentOutOfRangeException
700 ("One of the elements in the array specified is not between 1 and 9");
702 if (value[last] < 0 || value[last] > 9)
703 throw new ArgumentOutOfRangeException
704 ("Last element in the array specified is not between 0 and 9");
706 numberGroupSizes = (int[]) value.Clone();
710 public int NumberNegativePattern {
712 // See ECMA NumberFormatInfo page 27
713 return numberNegativePattern;
717 if (value < 0 || value > 4)
718 throw new ArgumentOutOfRangeException
719 ("The value specified for the property is less than 0 or greater than 15");
722 throw new InvalidOperationException
723 ("The current instance is read-only and a set operation was attempted");
725 numberNegativePattern = value;
729 // =========== Percent Format Properties =========== //
731 public int PercentDecimalDigits {
733 return percentDecimalDigits;
737 if (value < 0 || value > 99)
738 throw new ArgumentOutOfRangeException
739 ("The value specified for the property is less than 0 or greater than 99");
742 throw new InvalidOperationException
743 ("The current instance is read-only and a set operation was attempted");
745 percentDecimalDigits = value;
749 public string PercentDecimalSeparator {
751 return percentDecimalSeparator;
756 throw new ArgumentNullException
757 ("The value specified for the property is a null reference");
760 throw new InvalidOperationException
761 ("The current instance is read-only and a set operation was attempted");
763 percentDecimalSeparator = value;
768 public string PercentGroupSeparator {
770 return percentGroupSeparator;
775 throw new ArgumentNullException
776 ("The value specified for the property is a null reference");
779 throw new InvalidOperationException
780 ("The current instance is read-only and a set operation was attempted");
782 percentGroupSeparator = value;
786 public int[] PercentGroupSizes {
788 return (int []) RawPercentGroupSizes.Clone ();
792 RawPercentGroupSizes = value;
796 internal int[] RawPercentGroupSizes {
798 return percentGroupSizes;
803 throw new ArgumentNullException
804 ("The value specified for the property is a null reference");
807 throw new InvalidOperationException
808 ("The current instance is read-only and a set operation was attempted");
810 if (this == CultureInfo.CurrentCulture.NumberFormat)
811 throw new Exception ("HERE the value was modified");
813 if (value.Length == 0) {
814 percentGroupSizes = new int [0];
818 // All elements except last need to be in range 1 - 9, last can be 0.
819 int last = value.Length - 1;
821 for (int i = 0; i < last; i++)
822 if (value[i] < 1 || value[i] > 9)
823 throw new ArgumentOutOfRangeException
824 ("One of the elements in the array specified is not between 1 and 9");
826 if (value[last] < 0 || value[last] > 9)
827 throw new ArgumentOutOfRangeException
828 ("Last element in the array specified is not between 0 and 9");
830 percentGroupSizes = (int[]) value.Clone();
834 public int PercentNegativePattern {
836 // See ECMA NumberFormatInfo page 8
837 return percentNegativePattern;
841 if (value < 0 || value > 2)
842 throw new ArgumentOutOfRangeException
843 ("The value specified for the property is less than 0 or greater than 15");
846 throw new InvalidOperationException
847 ("The current instance is read-only and a set operation was attempted");
849 percentNegativePattern = value;
853 public int PercentPositivePattern {
855 // See ECMA NumberFormatInfo page 11
856 return percentPositivePattern;
860 if (value < 0 || value > 2)
861 throw new ArgumentOutOfRangeException
862 ("The value specified for the property is less than 0 or greater than 3");
865 throw new InvalidOperationException
866 ("The current instance is read-only and a set operation was attempted");
868 percentPositivePattern = value;
872 public string PercentSymbol {
874 return percentSymbol;
879 throw new ArgumentNullException
880 ("The value specified for the property is a null reference");
883 throw new InvalidOperationException
884 ("The current instance is read-only and a set operation was attempted");
886 percentSymbol = value;
890 public string PerMilleSymbol {
892 return perMilleSymbol;
897 throw new ArgumentNullException
898 ("The value specified for the property is a null reference");
901 throw new InvalidOperationException
902 ("The current instance is read-only and a set operation was attempted");
904 perMilleSymbol = value;
908 public string PositiveInfinitySymbol {
910 return positiveInfinitySymbol;
915 throw new ArgumentNullException
916 ("The value specified for the property is a null reference");
919 throw new InvalidOperationException
920 ("The current instance is read-only and a set operation was attempted");
922 positiveInfinitySymbol = value;
926 public string PositiveSign {
933 throw new ArgumentNullException
934 ("The value specified for the property is a null reference");
937 throw new InvalidOperationException
938 ("The current instance is read-only and a set operation was attempted");
940 positiveSign = value;
944 public object GetFormat (Type formatType)
946 return (formatType == typeof (NumberFormatInfo)) ? this : null;
949 public object Clone ()
951 NumberFormatInfo clone = (NumberFormatInfo) MemberwiseClone();
952 // clone is not read only
953 clone.isReadOnly = false;
957 public static NumberFormatInfo ReadOnly (NumberFormatInfo nfi)
959 NumberFormatInfo copy = (NumberFormatInfo)nfi.Clone();
960 copy.isReadOnly = true;
964 public static NumberFormatInfo GetInstance(IFormatProvider formatProvider)
966 if (formatProvider != null) {
967 NumberFormatInfo nfi;
968 nfi = (NumberFormatInfo)formatProvider.GetFormat(typeof(NumberFormatInfo));