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 public sealed class NumberFormatInfo : ICloneable, IFormatProvider {
56 /* Keep in sync with object-internals.h */
58 #pragma warning disable 649
59 private bool isReadOnly;
60 // used for temporary storage. Used in InitPatterns ()
61 string decimalFormats;
62 string currencyFormats;
63 string percentFormats;
64 string digitPattern = "#";
65 string zeroPattern = "0";
67 // Currency Related Format Info
68 private int currencyDecimalDigits;
69 private string currencyDecimalSeparator;
70 private string currencyGroupSeparator;
71 private int[] currencyGroupSizes;
72 private int currencyNegativePattern;
73 private int currencyPositivePattern;
74 private string currencySymbol;
76 private string nanSymbol;
77 private string negativeInfinitySymbol;
78 private string negativeSign;
80 // Number Related Format Info
81 private int numberDecimalDigits;
82 private string numberDecimalSeparator;
83 private string numberGroupSeparator;
84 private int[] numberGroupSizes;
85 private int numberNegativePattern;
87 // Percent Related Format Info
88 private int percentDecimalDigits;
89 private string percentDecimalSeparator;
90 private string percentGroupSeparator;
91 private int[] percentGroupSizes;
92 private int percentNegativePattern;
93 private int percentPositivePattern;
94 private string percentSymbol;
96 private string perMilleSymbol;
97 private string positiveInfinitySymbol;
98 private string positiveSign;
99 #pragma warning restore 649
101 #pragma warning disable 169
102 string ansiCurrencySymbol; // TODO, MS.NET serializes this.
103 int m_dataItem; // Unused, but MS.NET serializes this.
104 bool m_useUserOverride; // Unused, but MS.NET serializes this.
105 bool validForParseAsNumber; // Unused, but MS.NET serializes this.
106 bool validForParseAsCurrency; // Unused, but MS.NET serializes this.
107 #pragma warning restore 169
109 string[] nativeDigits = invariantNativeDigits;
110 int digitSubstitution = 1; // DigitShapes.None.
112 static readonly string [] invariantNativeDigits = new string [] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
114 internal NumberFormatInfo (int lcid, bool read_only)
116 isReadOnly = read_only;
118 //FIXME: should add more LCID
119 // CultureInfo uses this one also.
123 // The Invariant Culture Info ID.
124 if (lcid == 0x007f) {
125 // Currency Related Format Info
126 currencyDecimalDigits = 2;
127 currencyDecimalSeparator = ".";
128 currencyGroupSeparator = ",";
129 currencyGroupSizes = new int[1] { 3 };
130 currencyNegativePattern = 0;
131 currencyPositivePattern = 0;
132 currencySymbol = "\u00a4";
135 negativeInfinitySymbol = "-Infinity";
138 // Number Related Format Info
139 numberDecimalDigits = 2;
140 numberDecimalSeparator = ".";
141 numberGroupSeparator = ",";
142 numberGroupSizes = new int[1] { 3 };
143 numberNegativePattern = 1;
145 // Percent Related Format Info
146 percentDecimalDigits = 2;
147 percentDecimalSeparator = ".";
148 percentGroupSeparator = ",";
149 percentGroupSizes = new int[1] { 3 };
150 percentNegativePattern = 0;
151 percentPositivePattern = 0;
154 perMilleSymbol = "\u2030";
155 positiveInfinitySymbol = "Infinity";
160 internal NumberFormatInfo (bool read_only) : this (0x007f, read_only)
164 public NumberFormatInfo () : this (false)
168 // this is called by mono/mono/metadata/locales.c
169 #pragma warning disable 169
172 string [] partOne, partTwo;
173 string [] posNeg = decimalFormats.Split (new char [1] {';'}, 2);
175 if (posNeg.Length == 2) {
177 partOne = posNeg [0].Split (new char [1] {'.'}, 2);
179 if (partOne.Length == 2) {
180 // assumed same for both positive and negative
181 // decimal digit side
182 numberDecimalDigits = 0;
183 for (int i = 0; i < partOne [1].Length; i ++) {
184 if (partOne [1][i] == digitPattern [0]) {
185 numberDecimalDigits ++;
190 // decimal grouping side
191 partTwo = partOne [0].Split (',');
192 if (partTwo.Length > 1) {
193 numberGroupSizes = new int [partTwo.Length - 1];
194 for (int i = 0; i < numberGroupSizes.Length; i ++) {
195 string pat = partTwo [i + 1];
196 numberGroupSizes [i] = pat.Length;
199 numberGroupSizes = new int [1] { 0 };
202 if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith (")")) {
203 numberNegativePattern = 0;
204 } else if (posNeg [1].StartsWith ("- ")) {
205 numberNegativePattern = 2;
206 } else if (posNeg [1].StartsWith ("-")) {
207 numberNegativePattern = 1;
208 } else if (posNeg [1].EndsWith (" -")) {
209 numberNegativePattern = 4;
210 } else if (posNeg [1].EndsWith ("-")) {
211 numberNegativePattern = 3;
213 numberNegativePattern = 1;
218 posNeg = currencyFormats.Split (new char [1] {';'}, 2);
219 if (posNeg.Length == 2) {
220 partOne = posNeg [0].Split (new char [1] {'.'}, 2);
222 if (partOne.Length == 2) {
223 // assumed same for both positive and negative
224 // decimal digit side
225 currencyDecimalDigits = 0;
226 for (int i = 0; i < partOne [1].Length; i ++) {
227 if (partOne [1][i] == zeroPattern [0])
228 currencyDecimalDigits ++;
233 // decimal grouping side
234 partTwo = partOne [0].Split (',');
235 if (partTwo.Length > 1) {
236 currencyGroupSizes = new int [partTwo.Length - 1];
237 for (int i = 0; i < currencyGroupSizes.Length; i ++) {
238 string pat = partTwo [i + 1];
239 currencyGroupSizes [i] = pat.Length;
242 currencyGroupSizes = new int [1] { 0 };
245 if (posNeg [1].StartsWith ("(\u00a4 ") && posNeg [1].EndsWith (")")) {
246 currencyNegativePattern = 14;
247 } else if (posNeg [1].StartsWith ("(\u00a4") && posNeg [1].EndsWith (")")) {
248 currencyNegativePattern = 0;
249 } else if (posNeg [1].StartsWith ("\u00a4 ") && posNeg [1].EndsWith ("-")) {
250 currencyNegativePattern = 11;
251 } else if (posNeg [1].StartsWith ("\u00a4") && posNeg [1].EndsWith ("-")) {
252 currencyNegativePattern = 3;
253 } else if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith (" \u00a4")) {
254 currencyNegativePattern = 15;
255 } else if (posNeg [1].StartsWith ("(") && posNeg [1].EndsWith ("\u00a4")) {
256 currencyNegativePattern = 4;
257 } else if (posNeg [1].StartsWith ("-") && posNeg [1].EndsWith (" \u00a4")) {
258 currencyNegativePattern = 8;
259 } else if (posNeg [1].StartsWith ("-") && posNeg [1].EndsWith ("\u00a4")) {
260 currencyNegativePattern = 5;
261 } else if (posNeg [1].StartsWith ("-\u00a4 ")) {
262 currencyNegativePattern = 9;
263 } else if (posNeg [1].StartsWith ("-\u00a4")) {
264 currencyNegativePattern = 1;
265 } else if (posNeg [1].StartsWith ("\u00a4 -")) {
266 currencyNegativePattern = 12;
267 } else if (posNeg [1].StartsWith ("\u00a4-")) {
268 currencyNegativePattern = 2;
269 } else if (posNeg [1].EndsWith (" \u00a4-")) {
270 currencyNegativePattern = 10;
271 } else if (posNeg [1].EndsWith ("\u00a4-")) {
272 currencyNegativePattern = 7;
273 } else if (posNeg [1].EndsWith ("- \u00a4")) {
274 currencyNegativePattern = 13;
275 } else if (posNeg [1].EndsWith ("-\u00a4")) {
276 currencyNegativePattern = 6;
278 currencyNegativePattern = 0;
281 if (posNeg [0].StartsWith ("\u00a4 ")) {
282 currencyPositivePattern = 2;
283 } else if (posNeg [0].StartsWith ("\u00a4")) {
284 currencyPositivePattern = 0;
285 } else if (posNeg [0].EndsWith (" \u00a4")) {
286 currencyPositivePattern = 3;
287 } else if (posNeg [0].EndsWith ("\u00a4")) {
288 currencyPositivePattern = 1;
290 currencyPositivePattern = 0;
295 // we don't have percentNegativePattern in CLDR so
296 // the percentNegativePattern are just guesses
297 if (percentFormats.StartsWith ("%")) {
298 percentPositivePattern = 2;
299 percentNegativePattern = 2;
300 } else if (percentFormats.EndsWith (" %")) {
301 percentPositivePattern = 0;
302 percentNegativePattern = 0;
303 } else if (percentFormats.EndsWith ("%")) {
304 percentPositivePattern = 1;
305 percentNegativePattern = 1;
307 percentPositivePattern = 0;
308 percentNegativePattern = 0;
311 partOne = percentFormats.Split (new char [1] {'.'}, 2);
313 if (partOne.Length == 2) {
314 // assumed same for both positive and negative
315 // decimal digit side
316 percentDecimalDigits = 0;
317 for (int i = 0; i < partOne [1].Length; i ++) {
318 if (partOne [1][i] == digitPattern [0])
319 percentDecimalDigits ++;
324 // percent grouping side
325 partTwo = partOne [0].Split (',');
326 if (partTwo.Length > 1) {
327 percentGroupSizes = new int [partTwo.Length - 1];
328 for (int i = 0; i < percentGroupSizes.Length; i ++) {
329 string pat = partTwo [i + 1];
330 percentGroupSizes [i] = pat.Length;
333 percentGroupSizes = new int [1] { 0 };
338 #pragma warning restore 169
340 // =========== Currency Format Properties =========== //
342 public int CurrencyDecimalDigits {
344 return currencyDecimalDigits;
348 if (value < 0 || value > 99)
349 throw new ArgumentOutOfRangeException
350 ("The value specified for the property is less than 0 or greater than 99");
353 throw new InvalidOperationException
354 ("The current instance is read-only and a set operation was attempted");
356 currencyDecimalDigits = value;
360 public string CurrencyDecimalSeparator {
362 return currencyDecimalSeparator;
367 throw new ArgumentNullException
368 ("The value specified for the property is a null reference");
371 throw new InvalidOperationException
372 ("The current instance is read-only and a set operation was attempted");
374 currencyDecimalSeparator = value;
379 public string CurrencyGroupSeparator {
381 return currencyGroupSeparator;
386 throw new ArgumentNullException
387 ("The value specified for the property is a null reference");
390 throw new InvalidOperationException
391 ("The current instance is read-only and a set operation was attempted");
393 currencyGroupSeparator = value;
397 public int[] CurrencyGroupSizes {
399 return (int []) RawCurrencyGroupSizes.Clone ();
403 RawCurrencyGroupSizes = value;
407 internal int[] RawCurrencyGroupSizes {
409 return currencyGroupSizes;
414 throw new ArgumentNullException
415 ("The value specified for the property is a null reference");
418 throw new InvalidOperationException
419 ("The current instance is read-only and a set operation was attempted");
421 if (value.Length == 0) {
422 currencyGroupSizes = new int [0];
426 // All elements except last need to be in range 1 - 9, last can be 0.
427 int last = value.Length - 1;
429 for (int i = 0; i < last; i++)
430 if (value[i] < 1 || value[i] > 9)
431 throw new ArgumentOutOfRangeException
432 ("One of the elements in the array specified is not between 1 and 9");
434 if (value[last] < 0 || value[last] > 9)
435 throw new ArgumentOutOfRangeException
436 ("Last element in the array specified is not between 0 and 9");
438 currencyGroupSizes = (int[]) value.Clone();
442 public int CurrencyNegativePattern {
444 // See ECMA NumberFormatInfo page 8
445 return currencyNegativePattern;
449 if (value < 0 || value > 15)
450 throw new ArgumentOutOfRangeException
451 ("The value specified for the property is less than 0 or greater than 15");
454 throw new InvalidOperationException
455 ("The current instance is read-only and a set operation was attempted");
457 currencyNegativePattern = value;
461 public int CurrencyPositivePattern {
463 // See ECMA NumberFormatInfo page 11
464 return currencyPositivePattern;
468 if (value < 0 || value > 3)
469 throw new ArgumentOutOfRangeException
470 ("The value specified for the property is less than 0 or greater than 3");
473 throw new InvalidOperationException
474 ("The current instance is read-only and a set operation was attempted");
476 currencyPositivePattern = value;
480 public string CurrencySymbol {
482 return currencySymbol;
487 throw new ArgumentNullException
488 ("The value specified for the property is a null reference");
491 throw new InvalidOperationException
492 ("The current instance is read-only and a set operation was attempted");
494 currencySymbol = value;
498 // =========== Static Read-Only Properties =========== //
500 public static NumberFormatInfo CurrentInfo {
502 NumberFormatInfo nfi = (NumberFormatInfo) System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat;
503 nfi.isReadOnly = true;
508 public static NumberFormatInfo InvariantInfo {
510 // This uses invariant info, which is same as in the constructor
511 NumberFormatInfo nfi = new NumberFormatInfo (true);
516 public bool IsReadOnly {
524 public string NaNSymbol {
531 throw new ArgumentNullException
532 ("The value specified for the property is a null reference");
535 throw new InvalidOperationException
536 ("The current instance is read-only and a set operation was attempted");
543 [MonoNotSupported ("We don't have native digit info")]
545 public string [] NativeDigits {
546 get { return nativeDigits; }
549 throw new ArgumentNullException ("value");
550 if (value.Length != 10)
551 throw new ArgumentException ("Argument array length must be 10");
552 foreach (string s in value)
553 if (String.IsNullOrEmpty (s))
554 throw new ArgumentException ("Argument array contains one or more null strings");
555 nativeDigits = value;
559 [MonoNotSupported ("We don't have native digit info")]
561 public DigitShapes DigitSubstitution {
562 get { return (DigitShapes) digitSubstitution; }
563 set { digitSubstitution = (int) value; }
567 public string NegativeInfinitySymbol {
569 return negativeInfinitySymbol;
574 throw new ArgumentNullException
575 ("The value specified for the property is a null reference");
578 throw new InvalidOperationException
579 ("The current instance is read-only and a set operation was attempted");
581 negativeInfinitySymbol = value;
585 public string NegativeSign {
592 throw new ArgumentNullException
593 ("The value specified for the property is a null reference");
596 throw new InvalidOperationException
597 ("The current instance is read-only and a set operation was attempted");
599 negativeSign = value;
603 // =========== Number Format Properties =========== //
605 public int NumberDecimalDigits {
607 return numberDecimalDigits;
611 if (value < 0 || value > 99)
612 throw new ArgumentOutOfRangeException
613 ("The value specified for the property is less than 0 or greater than 99");
616 throw new InvalidOperationException
617 ("The current instance is read-only and a set operation was attempted");
619 numberDecimalDigits = value;
623 public string NumberDecimalSeparator {
625 return numberDecimalSeparator;
630 throw new ArgumentNullException
631 ("The value specified for the property is a null reference");
634 throw new InvalidOperationException
635 ("The current instance is read-only and a set operation was attempted");
637 numberDecimalSeparator = value;
642 public string NumberGroupSeparator {
644 return numberGroupSeparator;
649 throw new ArgumentNullException
650 ("The value specified for the property is a null reference");
653 throw new InvalidOperationException
654 ("The current instance is read-only and a set operation was attempted");
656 numberGroupSeparator = value;
660 public int[] NumberGroupSizes {
662 return (int []) RawNumberGroupSizes.Clone ();
666 RawNumberGroupSizes = value;
670 internal int[] RawNumberGroupSizes {
672 return numberGroupSizes;
677 throw new ArgumentNullException
678 ("The value specified for the property is a null reference");
681 throw new InvalidOperationException
682 ("The current instance is read-only and a set operation was attempted");
684 if (value.Length == 0) {
685 numberGroupSizes = new int [0];
688 // All elements except last need to be in range 1 - 9, last can be 0.
689 int last = value.Length - 1;
691 for (int i = 0; i < last; i++)
692 if (value[i] < 1 || value[i] > 9)
693 throw new ArgumentOutOfRangeException
694 ("One of the elements in the array specified is not between 1 and 9");
696 if (value[last] < 0 || value[last] > 9)
697 throw new ArgumentOutOfRangeException
698 ("Last element in the array specified is not between 0 and 9");
700 numberGroupSizes = (int[]) value.Clone();
704 public int NumberNegativePattern {
706 // See ECMA NumberFormatInfo page 27
707 return numberNegativePattern;
711 if (value < 0 || value > 4)
712 throw new ArgumentOutOfRangeException
713 ("The value specified for the property is less than 0 or greater than 15");
716 throw new InvalidOperationException
717 ("The current instance is read-only and a set operation was attempted");
719 numberNegativePattern = value;
723 // =========== Percent Format Properties =========== //
725 public int PercentDecimalDigits {
727 return percentDecimalDigits;
731 if (value < 0 || value > 99)
732 throw new ArgumentOutOfRangeException
733 ("The value specified for the property is less than 0 or greater than 99");
736 throw new InvalidOperationException
737 ("The current instance is read-only and a set operation was attempted");
739 percentDecimalDigits = value;
743 public string PercentDecimalSeparator {
745 return percentDecimalSeparator;
750 throw new ArgumentNullException
751 ("The value specified for the property is a null reference");
754 throw new InvalidOperationException
755 ("The current instance is read-only and a set operation was attempted");
757 percentDecimalSeparator = value;
762 public string PercentGroupSeparator {
764 return percentGroupSeparator;
769 throw new ArgumentNullException
770 ("The value specified for the property is a null reference");
773 throw new InvalidOperationException
774 ("The current instance is read-only and a set operation was attempted");
776 percentGroupSeparator = value;
780 public int[] PercentGroupSizes {
782 return (int []) RawPercentGroupSizes.Clone ();
786 RawPercentGroupSizes = value;
790 internal int[] RawPercentGroupSizes {
792 return percentGroupSizes;
797 throw new ArgumentNullException
798 ("The value specified for the property is a null reference");
801 throw new InvalidOperationException
802 ("The current instance is read-only and a set operation was attempted");
804 if (this == CultureInfo.CurrentCulture.NumberFormat)
805 throw new Exception ("HERE the value was modified");
807 if (value.Length == 0) {
808 percentGroupSizes = new int [0];
812 // All elements except last need to be in range 1 - 9, last can be 0.
813 int last = value.Length - 1;
815 for (int i = 0; i < last; i++)
816 if (value[i] < 1 || value[i] > 9)
817 throw new ArgumentOutOfRangeException
818 ("One of the elements in the array specified is not between 1 and 9");
820 if (value[last] < 0 || value[last] > 9)
821 throw new ArgumentOutOfRangeException
822 ("Last element in the array specified is not between 0 and 9");
824 percentGroupSizes = (int[]) value.Clone();
828 public int PercentNegativePattern {
830 // See ECMA NumberFormatInfo page 8
831 return percentNegativePattern;
835 if (value < 0 || value > 2)
836 throw new ArgumentOutOfRangeException
837 ("The value specified for the property is less than 0 or greater than 15");
840 throw new InvalidOperationException
841 ("The current instance is read-only and a set operation was attempted");
843 percentNegativePattern = value;
847 public int PercentPositivePattern {
849 // See ECMA NumberFormatInfo page 11
850 return percentPositivePattern;
854 if (value < 0 || value > 2)
855 throw new ArgumentOutOfRangeException
856 ("The value specified for the property is less than 0 or greater than 3");
859 throw new InvalidOperationException
860 ("The current instance is read-only and a set operation was attempted");
862 percentPositivePattern = value;
866 public string PercentSymbol {
868 return percentSymbol;
873 throw new ArgumentNullException
874 ("The value specified for the property is a null reference");
877 throw new InvalidOperationException
878 ("The current instance is read-only and a set operation was attempted");
880 percentSymbol = value;
884 public string PerMilleSymbol {
886 return perMilleSymbol;
891 throw new ArgumentNullException
892 ("The value specified for the property is a null reference");
895 throw new InvalidOperationException
896 ("The current instance is read-only and a set operation was attempted");
898 perMilleSymbol = value;
902 public string PositiveInfinitySymbol {
904 return positiveInfinitySymbol;
909 throw new ArgumentNullException
910 ("The value specified for the property is a null reference");
913 throw new InvalidOperationException
914 ("The current instance is read-only and a set operation was attempted");
916 positiveInfinitySymbol = value;
920 public string PositiveSign {
927 throw new ArgumentNullException
928 ("The value specified for the property is a null reference");
931 throw new InvalidOperationException
932 ("The current instance is read-only and a set operation was attempted");
934 positiveSign = value;
938 public object GetFormat (Type formatType)
940 return (formatType == typeof (NumberFormatInfo)) ? this : null;
943 public object Clone ()
945 NumberFormatInfo clone = (NumberFormatInfo) MemberwiseClone();
946 // clone is not read only
947 clone.isReadOnly = false;
951 public static NumberFormatInfo ReadOnly (NumberFormatInfo nfi)
953 NumberFormatInfo copy = (NumberFormatInfo)nfi.Clone();
954 copy.isReadOnly = true;
958 public static NumberFormatInfo GetInstance(IFormatProvider formatProvider)
960 if (formatProvider != null) {
961 NumberFormatInfo nfi;
962 nfi = (NumberFormatInfo)formatProvider.GetFormat(typeof(NumberFormatInfo));