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 {
54 [StructLayout (LayoutKind.Sequential)]
55 public sealed class NumberFormatInfo : ICloneable, IFormatProvider {
57 /* Keep in sync with object-internals.h */
59 #pragma warning disable 649
60 private bool isReadOnly;
61 // used for temporary storage. Used in InitPatterns ()
62 string decimalFormats;
63 string currencyFormats;
64 string percentFormats;
65 string digitPattern = "#";
66 string zeroPattern = "0";
68 // Currency Related Format Info
69 private int currencyDecimalDigits;
70 private string currencyDecimalSeparator;
71 private string currencyGroupSeparator;
72 private int[] currencyGroupSizes;
73 private int currencyNegativePattern;
74 private int currencyPositivePattern;
75 private string currencySymbol;
77 private string nanSymbol;
78 private string negativeInfinitySymbol;
79 private string negativeSign;
81 // Number Related Format Info
82 private int numberDecimalDigits;
83 private string numberDecimalSeparator;
84 private string numberGroupSeparator;
85 private int[] numberGroupSizes;
86 private int numberNegativePattern;
88 // Percent Related Format Info
89 private int percentDecimalDigits;
90 private string percentDecimalSeparator;
91 private string percentGroupSeparator;
92 private int[] percentGroupSizes;
93 private int percentNegativePattern;
94 private int percentPositivePattern;
95 private string percentSymbol;
97 private string perMilleSymbol;
98 private string positiveInfinitySymbol;
99 private string positiveSign;
100 #pragma warning restore 649
102 #pragma warning disable 169
103 string ansiCurrencySymbol; // TODO, MS.NET serializes this.
104 int m_dataItem; // Unused, but MS.NET serializes this.
105 bool m_useUserOverride; // Unused, but MS.NET serializes this.
106 bool validForParseAsNumber; // Unused, but MS.NET serializes this.
107 bool validForParseAsCurrency; // Unused, but MS.NET serializes this.
108 #pragma warning restore 169
110 string[] nativeDigits = invariantNativeDigits;
111 int digitSubstitution = 1; // DigitShapes.None.
113 static readonly string [] invariantNativeDigits = new string [] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
115 internal NumberFormatInfo (int lcid, bool read_only)
117 isReadOnly = read_only;
119 //FIXME: should add more LCID
120 // CultureInfo uses this one also.
124 // The Invariant Culture Info ID.
125 if (lcid == 0x007f) {
126 // Currency Related Format Info
127 currencyDecimalDigits = 2;
128 currencyDecimalSeparator = ".";
129 currencyGroupSeparator = ",";
130 currencyGroupSizes = new int[1] { 3 };
131 currencyNegativePattern = 0;
132 currencyPositivePattern = 0;
133 currencySymbol = "\u00a4";
136 negativeInfinitySymbol = "-Infinity";
139 // Number Related Format Info
140 numberDecimalDigits = 2;
141 numberDecimalSeparator = ".";
142 numberGroupSeparator = ",";
143 numberGroupSizes = new int[1] { 3 };
144 numberNegativePattern = 1;
146 // Percent Related Format Info
147 percentDecimalDigits = 2;
148 percentDecimalSeparator = ".";
149 percentGroupSeparator = ",";
150 percentGroupSizes = new int[1] { 3 };
151 percentNegativePattern = 0;
152 percentPositivePattern = 0;
155 perMilleSymbol = "\u2030";
156 positiveInfinitySymbol = "Infinity";
161 internal NumberFormatInfo (bool read_only) : this (0x007f, read_only)
165 public NumberFormatInfo () : this (false)
169 // this is called by mono/mono/metadata/locales.c
170 #pragma warning disable 169
173 string [] partOne, partTwo;
174 string [] posNeg = decimalFormats.Split (new char [1] {';'}, 2);
176 if (posNeg.Length == 2) {
178 partOne = posNeg [0].Split (new char [1] {'.'}, 2);
180 if (partOne.Length == 2) {
181 // assumed same for both positive and negative
182 // decimal digit side
183 numberDecimalDigits = 0;
184 for (int i = 0; i < partOne [1].Length; i ++) {
185 if (partOne [1][i] == digitPattern [0]) {
186 numberDecimalDigits ++;
191 // decimal grouping side
192 partTwo = partOne [0].Split (',');
193 if (partTwo.Length > 1) {
194 numberGroupSizes = new int [partTwo.Length - 1];
195 for (int i = 0; i < numberGroupSizes.Length; i ++) {
196 string pat = partTwo [i + 1];
197 numberGroupSizes [i] = pat.Length;
200 numberGroupSizes = new int [1] { 0 };
203 if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith (")")) {
204 numberNegativePattern = 0;
205 } else if (posNeg [1].StartsWith ("- ")) {
206 numberNegativePattern = 2;
207 } else if (posNeg [1].StartsWith ("-")) {
208 numberNegativePattern = 1;
209 } else if (posNeg [1].EndsWith (" -")) {
210 numberNegativePattern = 4;
211 } else if (posNeg [1].EndsWith ("-")) {
212 numberNegativePattern = 3;
214 numberNegativePattern = 1;
219 posNeg = currencyFormats.Split (new char [1] {';'}, 2);
220 if (posNeg.Length == 2) {
221 partOne = posNeg [0].Split (new char [1] {'.'}, 2);
223 if (partOne.Length == 2) {
224 // assumed same for both positive and negative
225 // decimal digit side
226 currencyDecimalDigits = 0;
227 for (int i = 0; i < partOne [1].Length; i ++) {
228 if (partOne [1][i] == zeroPattern [0])
229 currencyDecimalDigits ++;
234 // decimal grouping side
235 partTwo = partOne [0].Split (',');
236 if (partTwo.Length > 1) {
237 currencyGroupSizes = new int [partTwo.Length - 1];
238 for (int i = 0; i < currencyGroupSizes.Length; i ++) {
239 string pat = partTwo [i + 1];
240 currencyGroupSizes [i] = pat.Length;
243 currencyGroupSizes = new int [1] { 0 };
246 if (posNeg [1].StartsWith ("(\u00a4 ") && posNeg [1].EndsWith (")")) {
247 currencyNegativePattern = 14;
248 } else if (posNeg [1].StartsWith ("(\u00a4") && posNeg [1].EndsWith (")")) {
249 currencyNegativePattern = 0;
250 } else if (posNeg [1].StartsWith ("\u00a4 ") && posNeg [1].EndsWith ("-")) {
251 currencyNegativePattern = 11;
252 } else if (posNeg [1].StartsWith ("\u00a4") && posNeg [1].EndsWith ("-")) {
253 currencyNegativePattern = 3;
254 } else if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith (" \u00a4")) {
255 currencyNegativePattern = 15;
256 } else if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith ("\u00a4")) {
257 currencyNegativePattern = 4;
258 } else if (posNeg [1].StartsWith ("-") && posNeg [1].EndsWith (" \u00a4")) {
259 currencyNegativePattern = 8;
260 } else if (posNeg [1].StartsWith ("-") && posNeg [1].EndsWith ("\u00a4")) {
261 currencyNegativePattern = 5;
262 } else if (posNeg [1].StartsWith ("-\u00a4 ")) {
263 currencyNegativePattern = 9;
264 } else if (posNeg [1].StartsWith ("-\u00a4")) {
265 currencyNegativePattern = 1;
266 } else if (posNeg [1].StartsWith ("\u00a4 -")) {
267 currencyNegativePattern = 12;
268 } else if (posNeg [1].StartsWith ("\u00a4-")) {
269 currencyNegativePattern = 2;
270 } else if (posNeg [1].EndsWith (" \u00a4-")) {
271 currencyNegativePattern = 10;
272 } else if (posNeg [1].EndsWith ("\u00a4-")) {
273 currencyNegativePattern = 7;
274 } else if (posNeg [1].EndsWith ("- \u00a4")) {
275 currencyNegativePattern = 13;
276 } else if (posNeg [1].EndsWith ("-\u00a4")) {
277 currencyNegativePattern = 6;
279 currencyNegativePattern = 0;
282 if (posNeg [0].StartsWith ("\u00a4 ")) {
283 currencyPositivePattern = 2;
284 } else if (posNeg [0].StartsWith ("\u00a4")) {
285 currencyPositivePattern = 0;
286 } else if (posNeg [0].EndsWith (" \u00a4")) {
287 currencyPositivePattern = 3;
288 } else if (posNeg [0].EndsWith ("\u00a4")) {
289 currencyPositivePattern = 1;
291 currencyPositivePattern = 0;
296 // we don't have percentNegativePattern in CLDR so
297 // the percentNegativePattern are just guesses
298 if (percentFormats.StartsWith ("%")) {
299 percentPositivePattern = 2;
300 percentNegativePattern = 2;
301 } else if (percentFormats.EndsWith (" %")) {
302 percentPositivePattern = 0;
303 percentNegativePattern = 0;
304 } else if (percentFormats.EndsWith ("%")) {
305 percentPositivePattern = 1;
306 percentNegativePattern = 1;
308 percentPositivePattern = 0;
309 percentNegativePattern = 0;
312 partOne = percentFormats.Split (new char [1] {'.'}, 2);
314 if (partOne.Length == 2) {
315 // assumed same for both positive and negative
316 // decimal digit side
317 percentDecimalDigits = 0;
318 for (int i = 0; i < partOne [1].Length; i ++) {
319 if (partOne [1][i] == digitPattern [0])
320 percentDecimalDigits ++;
325 // percent grouping side
326 partTwo = partOne [0].Split (',');
327 if (partTwo.Length > 1) {
328 percentGroupSizes = new int [partTwo.Length - 1];
329 for (int i = 0; i < percentGroupSizes.Length; i ++) {
330 string pat = partTwo [i + 1];
331 percentGroupSizes [i] = pat.Length;
334 percentGroupSizes = new int [1] { 0 };
339 #pragma warning restore 169
341 // =========== Currency Format Properties =========== //
343 public int CurrencyDecimalDigits {
345 return currencyDecimalDigits;
349 if (value < 0 || value > 99)
350 throw new ArgumentOutOfRangeException
351 ("The value specified for the property is less than 0 or greater than 99");
354 throw new InvalidOperationException
355 ("The current instance is read-only and a set operation was attempted");
357 currencyDecimalDigits = value;
361 public string CurrencyDecimalSeparator {
363 return currencyDecimalSeparator;
368 throw new ArgumentNullException
369 ("The value specified for the property is a null reference");
372 throw new InvalidOperationException
373 ("The current instance is read-only and a set operation was attempted");
375 currencyDecimalSeparator = value;
380 public string CurrencyGroupSeparator {
382 return currencyGroupSeparator;
387 throw new ArgumentNullException
388 ("The value specified for the property is a null reference");
391 throw new InvalidOperationException
392 ("The current instance is read-only and a set operation was attempted");
394 currencyGroupSeparator = value;
398 public int[] CurrencyGroupSizes {
400 return (int []) RawCurrencyGroupSizes.Clone ();
404 RawCurrencyGroupSizes = value;
408 internal int[] RawCurrencyGroupSizes {
410 return currencyGroupSizes;
415 throw new ArgumentNullException
416 ("The value specified for the property is a null reference");
419 throw new InvalidOperationException
420 ("The current instance is read-only and a set operation was attempted");
422 if (value.Length == 0) {
423 currencyGroupSizes = new int [0];
427 // All elements except last need to be in range 1 - 9, last can be 0.
428 int last = value.Length - 1;
430 for (int i = 0; i < last; i++)
431 if (value[i] < 1 || value[i] > 9)
432 throw new ArgumentOutOfRangeException
433 ("One of the elements in the array specified is not between 1 and 9");
435 if (value[last] < 0 || value[last] > 9)
436 throw new ArgumentOutOfRangeException
437 ("Last element in the array specified is not between 0 and 9");
439 currencyGroupSizes = (int[]) value.Clone();
443 public int CurrencyNegativePattern {
445 // See ECMA NumberFormatInfo page 8
446 return currencyNegativePattern;
450 if (value < 0 || value > 15)
451 throw new ArgumentOutOfRangeException
452 ("The value specified for the property is less than 0 or greater than 15");
455 throw new InvalidOperationException
456 ("The current instance is read-only and a set operation was attempted");
458 currencyNegativePattern = value;
462 public int CurrencyPositivePattern {
464 // See ECMA NumberFormatInfo page 11
465 return currencyPositivePattern;
469 if (value < 0 || value > 3)
470 throw new ArgumentOutOfRangeException
471 ("The value specified for the property is less than 0 or greater than 3");
474 throw new InvalidOperationException
475 ("The current instance is read-only and a set operation was attempted");
477 currencyPositivePattern = value;
481 public string CurrencySymbol {
483 return currencySymbol;
488 throw new ArgumentNullException
489 ("The value specified for the property is a null reference");
492 throw new InvalidOperationException
493 ("The current instance is read-only and a set operation was attempted");
495 currencySymbol = value;
499 // =========== Static Read-Only Properties =========== //
501 public static NumberFormatInfo CurrentInfo {
503 NumberFormatInfo nfi = (NumberFormatInfo) System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat;
504 nfi.isReadOnly = true;
509 public static NumberFormatInfo InvariantInfo {
511 // This uses invariant info, which is same as in the constructor
512 NumberFormatInfo nfi = new NumberFormatInfo (true);
517 public bool IsReadOnly {
525 public string NaNSymbol {
532 throw new ArgumentNullException
533 ("The value specified for the property is a null reference");
536 throw new InvalidOperationException
537 ("The current instance is read-only and a set operation was attempted");
544 [MonoNotSupported ("We don't have native digit info")]
546 public string [] NativeDigits {
547 get { return nativeDigits; }
550 throw new ArgumentNullException ("value");
551 if (value.Length != 10)
552 throw new ArgumentException ("Argument array length must be 10");
553 foreach (string s in value)
554 if (String.IsNullOrEmpty (s))
555 throw new ArgumentException ("Argument array contains one or more null strings");
556 nativeDigits = value;
560 [MonoNotSupported ("We don't have native digit info")]
562 public DigitShapes DigitSubstitution {
563 get { return (DigitShapes) digitSubstitution; }
564 set { digitSubstitution = (int) value; }
568 public string NegativeInfinitySymbol {
570 return negativeInfinitySymbol;
575 throw new ArgumentNullException
576 ("The value specified for the property is a null reference");
579 throw new InvalidOperationException
580 ("The current instance is read-only and a set operation was attempted");
582 negativeInfinitySymbol = value;
586 public string NegativeSign {
593 throw new ArgumentNullException
594 ("The value specified for the property is a null reference");
597 throw new InvalidOperationException
598 ("The current instance is read-only and a set operation was attempted");
600 negativeSign = value;
604 // =========== Number Format Properties =========== //
606 public int NumberDecimalDigits {
608 return numberDecimalDigits;
612 if (value < 0 || value > 99)
613 throw new ArgumentOutOfRangeException
614 ("The value specified for the property is less than 0 or greater than 99");
617 throw new InvalidOperationException
618 ("The current instance is read-only and a set operation was attempted");
620 numberDecimalDigits = value;
624 public string NumberDecimalSeparator {
626 return numberDecimalSeparator;
631 throw new ArgumentNullException
632 ("The value specified for the property is a null reference");
635 throw new InvalidOperationException
636 ("The current instance is read-only and a set operation was attempted");
638 numberDecimalSeparator = value;
643 public string NumberGroupSeparator {
645 return numberGroupSeparator;
650 throw new ArgumentNullException
651 ("The value specified for the property is a null reference");
654 throw new InvalidOperationException
655 ("The current instance is read-only and a set operation was attempted");
657 numberGroupSeparator = value;
661 public int[] NumberGroupSizes {
663 return (int []) RawNumberGroupSizes.Clone ();
667 RawNumberGroupSizes = value;
671 internal int[] RawNumberGroupSizes {
673 return numberGroupSizes;
678 throw new ArgumentNullException
679 ("The value specified for the property is a null reference");
682 throw new InvalidOperationException
683 ("The current instance is read-only and a set operation was attempted");
685 if (value.Length == 0) {
686 numberGroupSizes = new int [0];
689 // All elements except last need to be in range 1 - 9, last can be 0.
690 int last = value.Length - 1;
692 for (int i = 0; i < last; i++)
693 if (value[i] < 1 || value[i] > 9)
694 throw new ArgumentOutOfRangeException
695 ("One of the elements in the array specified is not between 1 and 9");
697 if (value[last] < 0 || value[last] > 9)
698 throw new ArgumentOutOfRangeException
699 ("Last element in the array specified is not between 0 and 9");
701 numberGroupSizes = (int[]) value.Clone();
705 public int NumberNegativePattern {
707 // See ECMA NumberFormatInfo page 27
708 return numberNegativePattern;
712 if (value < 0 || value > 4)
713 throw new ArgumentOutOfRangeException
714 ("The value specified for the property is less than 0 or greater than 15");
717 throw new InvalidOperationException
718 ("The current instance is read-only and a set operation was attempted");
720 numberNegativePattern = value;
724 // =========== Percent Format Properties =========== //
726 public int PercentDecimalDigits {
728 return percentDecimalDigits;
732 if (value < 0 || value > 99)
733 throw new ArgumentOutOfRangeException
734 ("The value specified for the property is less than 0 or greater than 99");
737 throw new InvalidOperationException
738 ("The current instance is read-only and a set operation was attempted");
740 percentDecimalDigits = value;
744 public string PercentDecimalSeparator {
746 return percentDecimalSeparator;
751 throw new ArgumentNullException
752 ("The value specified for the property is a null reference");
755 throw new InvalidOperationException
756 ("The current instance is read-only and a set operation was attempted");
758 percentDecimalSeparator = value;
763 public string PercentGroupSeparator {
765 return percentGroupSeparator;
770 throw new ArgumentNullException
771 ("The value specified for the property is a null reference");
774 throw new InvalidOperationException
775 ("The current instance is read-only and a set operation was attempted");
777 percentGroupSeparator = value;
781 public int[] PercentGroupSizes {
783 return (int []) RawPercentGroupSizes.Clone ();
787 RawPercentGroupSizes = value;
791 internal int[] RawPercentGroupSizes {
793 return percentGroupSizes;
798 throw new ArgumentNullException
799 ("The value specified for the property is a null reference");
802 throw new InvalidOperationException
803 ("The current instance is read-only and a set operation was attempted");
805 if (this == CultureInfo.CurrentCulture.NumberFormat)
806 throw new Exception ("HERE the value was modified");
808 if (value.Length == 0) {
809 percentGroupSizes = new int [0];
813 // All elements except last need to be in range 1 - 9, last can be 0.
814 int last = value.Length - 1;
816 for (int i = 0; i < last; i++)
817 if (value[i] < 1 || value[i] > 9)
818 throw new ArgumentOutOfRangeException
819 ("One of the elements in the array specified is not between 1 and 9");
821 if (value[last] < 0 || value[last] > 9)
822 throw new ArgumentOutOfRangeException
823 ("Last element in the array specified is not between 0 and 9");
825 percentGroupSizes = (int[]) value.Clone();
829 public int PercentNegativePattern {
831 // See ECMA NumberFormatInfo page 8
832 return percentNegativePattern;
836 if (value < 0 || value > 2)
837 throw new ArgumentOutOfRangeException
838 ("The value specified for the property is less than 0 or greater than 15");
841 throw new InvalidOperationException
842 ("The current instance is read-only and a set operation was attempted");
844 percentNegativePattern = value;
848 public int PercentPositivePattern {
850 // See ECMA NumberFormatInfo page 11
851 return percentPositivePattern;
855 if (value < 0 || value > 2)
856 throw new ArgumentOutOfRangeException
857 ("The value specified for the property is less than 0 or greater than 3");
860 throw new InvalidOperationException
861 ("The current instance is read-only and a set operation was attempted");
863 percentPositivePattern = value;
867 public string PercentSymbol {
869 return percentSymbol;
874 throw new ArgumentNullException
875 ("The value specified for the property is a null reference");
878 throw new InvalidOperationException
879 ("The current instance is read-only and a set operation was attempted");
881 percentSymbol = value;
885 public string PerMilleSymbol {
887 return perMilleSymbol;
892 throw new ArgumentNullException
893 ("The value specified for the property is a null reference");
896 throw new InvalidOperationException
897 ("The current instance is read-only and a set operation was attempted");
899 perMilleSymbol = value;
903 public string PositiveInfinitySymbol {
905 return positiveInfinitySymbol;
910 throw new ArgumentNullException
911 ("The value specified for the property is a null reference");
914 throw new InvalidOperationException
915 ("The current instance is read-only and a set operation was attempted");
917 positiveInfinitySymbol = value;
921 public string PositiveSign {
928 throw new ArgumentNullException
929 ("The value specified for the property is a null reference");
932 throw new InvalidOperationException
933 ("The current instance is read-only and a set operation was attempted");
935 positiveSign = value;
939 public object GetFormat (Type formatType)
941 return (formatType == typeof (NumberFormatInfo)) ? this : null;
944 public object Clone ()
946 NumberFormatInfo clone = (NumberFormatInfo) MemberwiseClone();
947 // clone is not read only
948 clone.isReadOnly = false;
952 public static NumberFormatInfo ReadOnly (NumberFormatInfo nfi)
954 NumberFormatInfo copy = (NumberFormatInfo)nfi.Clone();
955 copy.isReadOnly = true;
959 public static NumberFormatInfo GetInstance(IFormatProvider formatProvider)
961 if (formatProvider != null) {
962 NumberFormatInfo nfi;
963 nfi = (NumberFormatInfo)formatProvider.GetFormat(typeof(NumberFormatInfo));