culturally neutral TryParse - c#

I have the following extension method
public static bool IsValidCurrency(this string value)
{
CultureInfo culture = new CultureInfo ("en-gb");
decimal result;
return decimal.TryParse(value, NumberStyles.Currency, culture, out result);
}
I wish to have this to be culturally neutral allowing to pass $ , €, ¥ etc. into the method
Thanks

Different cultures may use the same currency symbol to mean different things. Hence there is no invariant way to read a currency.
It could however be done in the following way:
public static bool IsValidCurrency(this string value)
{
decimal result;
var cultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
return cultures.Any(info => Decimal.TryParse(value, NumberStyles.Currency, info, out result));
}

My initial thought is that passing in a currency symbol to such a method without context means it might be a valid symbol, but not necessarily the correct one - many regions use the '$' symbol, for example.
If I were doing this anyway, I'd probably loop round the specific .NET cultures (avoiding the Invariant Culture, if you're on Windows 7), checking each RegionInfo's CurrencySymbol property against the string passed in. This is quick, dirty (and untested) while it's still in my head; I'm sure this could be more efficiently coded:
public static Boolean IsValidCurrency(this string value)
{
// Assume the worst.
Boolean isValid = false;
foreach (CultureInfo c in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
{
// Account for InvariantCulture weirdness in Windows 7.
if (!c.Equals(CultureInfo.InvariantCulture))
{
RegionInfo r = new RegionInfo(c.LCID);
if (r.CurrencySymbol.Equals(value, StringComparison.OrdinalIgnoreCase))
{
// We've got a match, so flag it and break.
isValid = true;
break;
}
}
}
return isValid;
}
This assumes you're only passing in the symbol, and doesn't solve the variant uses of different symbols. Hope this gets you thinking, anyway.

Related

Translate "True" to "Verdadero" in .NET

As documented in the Boolean.ToString(IFormatProvider) method doc, the IFormatProvider provider does not impact the constant "True/False" output.
Now, is there a way to however translate the "True" to "Verdadero"?
public static void Main(string[] args)
{
//Your code goes here
Console.WriteLine("Hello, world!");
System.Globalization.CultureInfo ci = new CultureInfo("es-ES");
Console.WriteLine(true.ToString(ci));
}
// Hello, world!
// True
There isn't a library-based solution to your question, nor should there be. The reason is that a string representation of System.Boolean is unlikely to be useful for anything but the most trivial of localization. Note that is not the case for floating-point numbers where a culture-specific . or , can be applied when formatting. Dates (System.DateTime) have some localization support from the operating system itself, so .NET is able to build on that; this is not the case for System.Boolean.
Usually, there will be other words in addition to just "True" (or "False"); those words will have to be translated too. And, depending on the language and those other words, you might not be able to do simple string concatenation: string message = baseMessage + b.ToString();
Instead, you should store your strings in resource files and retrieve the right one.
bool b = ...;
string message = b ? Properties.Resources.TrueMessage : Properties.Resources.FalseMessage;
See How to use localization in C# for more details.
As per the docs, Boolean.ToString(IFormatProvider) will not reflect culture specific strings.
However, one workaround could be to create an extension method on the Boolean object:
public static class BoolExtensions
{
public static string ToSpanishString(this bool val)
{
return val ? "Verdadero" : "Falso";
}
}
You can achieve this in the following way:
bool test = true;
Console.WriteLine(test ? "Verdadero" : "Equivocado");
The first value is always the truthy one, the second is the falsy.

why my parsed Double value is not right?

Im trying to parse a string to a Double.
Here is My code:
string a = "10.23";
double b = Double.Parse(a);
but b is 1023.0 and I dont know why. I would like to get 10.23 as a Double
It's because of your culture settings, you may specify culture for Parse method to get desired output:
string a = "10.23";
double b = double.Parse(a, System.Globalization.CultureInfo.InvariantCulture);
// b == 10.23
In Germany the comma (,) is used as the decimal point, whereas most English cultures and your example use the full stop (.) as the decimal point. Since Double.Parse uses the thread default culture to parse numbers, and the thread default culture is set to German, you're getting the wrong result.
You should instead specify the culture explicitly:
using System.Globalization;
string a = "10.23";
double b = Double.Parse(a, CultureInfo.InvariantCulture);
The invariant culture uses the full stop as the decimal point, so I suggest you use that instead. Or if you get the string from a source known to be written using a particular cultural convention, use that culture instead.
Or your location for number formatted, try this my source:
Ext:
public static class Ext
{
public static double? AsLocaleDouble(this string str)
{
var result = double.NaN;
var format = Thread.CurrentThread.CurrentUICulture.NumberFormat;
double.TryParse(str, NumberStyles.AllowDecimalPoint, format, out result);
return result;
}
}
Test:
class Program
{
static void Main(string[] args)
{
var str = "10,23";
Thread.CurrentThread.CurrentUICulture = new CultureInfo("uz-Cyrl-UZ");
Thread.CurrentThread.CurrentCulture = new CultureInfo("uz-Cyrl-UZ");
Console.WriteLine(str.AsLocaleDouble());
Console.ReadKey();
}
}

Method to parse decimals with various decimal/radix separators

I do a lot of file parsing, which involves parsing data types like decimal. To help make the code more readable, I've been using the following method:
public static decimal? ToDecimal(this string data)
{
decimal result;
if (decimal.TryParse(data, NumberStyles.Integer | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result))
return result;
return null;
}
This works fine for decimals which are represented with a full-stop/period as the decimal separator. However, I'd like this function to work with other standard decimal separators, particularly the comma. (I read that there is also an Arabic decimal separator: http://en.wikipedia.org/wiki/Decimal_mark#Other_numeral_systems, but that presumably relies on being able to parse the eastern arabic numerals too).
The Culture.CurrentCulture wouldn't be appropriate because the data is not necessarily created on the machine that is doing the processing. So I've now got this:
private static CultureInfo CreateCultureWithNumericDecimalSeparator(string separator)
{
var cultureInfo = (CultureInfo)CultureInfo.InvariantCulture.Clone();
cultureInfo.NumberFormat.NumberDecimalSeparator = separator;
return cultureInfo;
}
private static CultureInfo[] cultureInfos = new CultureInfo[]
{
CultureInfo.InvariantCulture,
CreateCultureWithNumericDecimalSeparator(",") // Normal comma
};
public static decimal? ToDecimal(this string data)
{
foreach (CultureInfo cultureInfo in cultureInfos)
{
decimal result;
if (decimal.TryParse(data, NumberStyles.Integer | NumberStyles.AllowDecimalPoint, cultureInfo, out result))
return result;
}
return null;
}
This works, but parsing twice, especially given that TryParse is checking all sorts of settings (thousand separators, hex specifiers, currency symbols, exponents, etc) seems a little heavy. It's probably not a performance issue, but I'm curious to know if there's a more efficient method to do this, or possibly even an existing method within the framework? And maybe even a method that could cope with other numeral systems in modern use? Thanks.
It seems that that the framework doesn't provide this directly. Interestingly, answers to another question suggest that the framework doesn't provide any mechanism for parsing Eastern Arabic numerals, even with the culture set. As the Eastern Arabic numerals requirement was just theoretical for my app, I'm sticking with the code I've already got above. If I implement anything more specific, I'll post it!

Decimal to string with thousand's separators?

Consider a Decimal value:
Decimal value = -1234567890.1234789012M;
i want to convert this Decimal value to a string, and include "thousands separators".
Note: i don't want to include thousand's separators, i want to include digit grouping. The difference is important for cultures that don't group numbers into thousands, or don't use commas to separate groups
Some example output with different standard formatting strings, on my computer, with my current locale:
value.ToString() = -1234567890..1234789012 (Implicit General)
value.ToString("g") = -1234567890..1234789012 (General)
value.ToString("d") = FormatException (Decimal whole number)
value.ToString("e") = -1..234568e++009 (Scientific)
value.ToString("f") = -1234567890..123 (Fixed Point)
value.ToString("n") = -12,,3456,,7890..123 (Number with commas for thousands)
value.ToString("r") = FormatException (Round trippable)
value.ToString("c") = -$$12,,3456,,7890..123 (Currency)
value.ToString("#,0.#") = -12,,3456,,7890..1
What i want (depending on culture) is:
en-US -1,234,567,890.1234789012
ca-ES -1.234.567.890,1234789012
gsw-FR -1 234 567 890,1234789012 (12/1/2012: fixed gws-FR to gsw-FR)
fr-CH -1'234'567'890.1234789012
ar-DZ 1,234,567,890.1234789012-
prs-AF 1.234.567.890,1234789012-
ps-AF 1،234،567،890,1234789012-
as-IN -1,23,45,67,890.1234789012
lo-LA (1234567,890.1234789012) (some debate if numbers should be "1,234,567,890")
qps-PLOC 12,,3456,,7890..1234789012
How can i convert a Decimal to a string, with digit groupings?
Update: Some more desired output, using my current culture of :
-1234567890M --> -12,,3456,,7890
-1234567890.1M --> -12,,3456,,7890..1
-1234567890.12M --> -12,,3456,,7890..12
-1234567890.123M --> -12,,3456,,7890..123
-1234567890.1234M --> -12,,3456,,7890..1234
-1234567890.12347M --> -12,,3456,,7890..12347
-1234567890.123478M --> -12,,3456,,7890..123478
-1234567890.1234789M --> -12,,3456,,7890..1234789
-1234567890.12347890M --> -12,,3456,,7890..1234789
-1234567890.123478901M --> -12,,3456,,7890..123478901
-1234567890.1234789012M --> -12,,3456,,7890..1234789012
Update: i tried peeking at how Decimal.ToString() manages to use the General format to show all the digits that it needs to show:
public override string ToString()
{
return Number.FormatDecimal(this, null, NumberFormatInfo.CurrentInfo);
}
except that Number.FormatDecimal is hidden somewhere:
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern string FormatDecimal(decimal value, string format, NumberFormatInfo info);
So that's a dead end.
The ToString method on decimals by default uses the CultureInfo.CurrentCulture for the user's session, and thus varies based on whom is running the code.
The ToString method also accepts an IFormatProvider in various overloads. This is where you need to supply your culture-specific Formatters.
For instance, if you pass the NumberFormat for fr-CH, you can format things as that culture expects:
var culture = CultureInfo.CreateSpecificCulture("fr-CH");
Decimal value = -1234567890.1234789012M;
Console.WriteLine(value.ToString("##,#.###############", culture.NumberFormat));
Will output
-1'234'567'890.1234789012
Edit #3 - rewrote using custom formatters. This should do what you want based on the new updated question.
Edit #4 - Took all of your input, and ran this:
public void TestOutput()
{
PrintValue(-1234567890M);
PrintValue(-1234567890.1M);
PrintValue(-1234567890.12M);
PrintValue(-1234567890.123M);
PrintValue(-1234567890.1234M);
PrintValue(-1234567890.12347M);
PrintValue(-1234567890.123478M);
PrintValue(-1234567890.1234789M);
PrintValue(-1234567890.12347890M);
PrintValue(-1234567890.123478901M);
PrintValue(-1234567890.1234789012M);
}
private static void PrintValue(decimal value)
{
var culture = CultureInfo.CreateSpecificCulture("qps-PLOC");
Console.WriteLine(value.ToString("##,#.###############", culture.NumberFormat));
}
Gives output matching what you supplied:
--12,,3456,,7890
--12,,3456,,7890..1
--12,,3456,,7890..12
--12,,3456,,7890..123
--12,,3456,,7890..1234
--12,,3456,,7890..12347
--12,,3456,,7890..123478
--12,,3456,,7890..1234789
--12,,3456,,7890..1234789
--12,,3456,,7890..123478901
--12,,3456,,7890..1234789012
As pointed out by Joshua, this only works for some locales.
From the looks of it then, you need to pick the lesser of two evils: Knowing the precision of your numbers, or specifying formats for each culture. I'd wager knowing the precision of your numbers may be easier.
In which case, a previous version of my answer may be of use:
To explicitly control the number of decimal places to output, you can clone the number format provided by the culture and modify the NumberDecimalDigits property.
var culture = CultureInfo.CreateSpecificCulture("fr-CH");
Decimal value = -1234567890.1234789012M;
NumberFormatInfo format = (NumberFormatInfo)culture.NumberFormat.Clone();
format.NumberDecimalDigits = 30;
Console.WriteLine(value.ToString("n", format));
This outputs:
-1'234'567'890.123478901200000000000000000000
You can specify a custom pattern (the pattern will appropriately resolve to the culture specific method of grouping and the appropriate grouping and decimal separator characters). A pattern can have positive, negative and zero sections. The positive pattern is always the same but the negative pattern depends on the culture and can be retrieved from the NumberFormatInfo's NumberNegativePattern property. Since you want as much precision as possible, you need to fill out 28 digit placeholders after the decimal; the comma forces grouping.
public static class DecimalFormatters
{
public static string ToStringNoTruncation(this Decimal n, IFormatProvider format)
{
NumberFormatInfo nfi = NumberFormatInfo.GetInstance(format);
string[] numberNegativePatterns = {
"(#,0.############################)", //0: (n)
"-#,0.############################", //1: -n
"- #,0.############################", //2: - n
"#,0.############################-", //3: n-
"#,0.############################ -"};//4: n -
var pattern = "#,0.############################;" + numberNegativePatterns[nfi.NumberNegativePattern];
return n.ToString(pattern, format);
}
public static string ToStringNoTruncation(this Decimal n)
{
return n.ToStringNoTruncation(CultureInfo.CurrentCulture);
}
}
Sample output
Locale Output
======== ============================
en-US -1,234,567,890.1234789012
ca-ES -1.234.567.890,1234789012
hr-HR - 1.234.567.890,1234789012
gsw-FR -1 234 567 890,1234789012
fr-CH -1'234'567'890.1234789012
ar-DZ 1,234,567,890.1234789012-
prs-AF 1.234.567.890,1234789012-
ps-AF 1،234،567،890,1234789012-
as-IN -1,23,45,67,890.1234789012
lo-LA (1234567,890.1234789012)
qps-PLOC -12,,3456,,7890..1234789012
There is currently no locale that uses NegativeNumberFormat 4 (n -), so that case cannot be tested. But there's no reason to think it would fail.
You need to include the culture when formatting for your strings. You can either use String.Format and include the culture as the first parameter or use the object's ToString method and use the overload that takes a culture.
The following code produces the expected output (except for gws-FR, it couldn't find a culture with that string).
namespace CultureFormatting {
using System;
using System.Globalization;
class Program {
public static void Main() {
Decimal value = -1234567890.1234789012M;
Print("en-US", value);
Print("ca-ES", value);
//print("gws-FR", value);
Print("fr-CH", value);
Print("ar-DZ", value);
Print("prs-AF", value);
Print("ps-AF", value);
Print("as-IN", value);
Print("lo-LA", value);
Print("qps-PLOC", value);
}
static void Print(string cultureName, Decimal value) {
CultureInfo cultureInfo = new CultureInfo(cultureName);
cultureInfo.NumberFormat.NumberDecimalDigits = 10;
// Or, you could replace the {1:N} with {1:N10} to do the same
// for just this string format call.
string result =
String.Format(cultureInfo, "{0,-8} {1:N}", cultureName, value);
Console.WriteLine(result);
}
}
}
The above code produces the following output:
en-US -1,234,567,890.1234789012
ca-ES -1.234.567.890,1234789012
fr-CH -1'234'567'890.1234789012
ar-DZ 1,234,567,890.1234789012-
prs-AF 1.234.567.890,1234789012-
ps-AF 1،234،567،890,1234789012-
as-IN -1,23,45,67,890.1234789012
lo-LA (1234567,890.1234789012)
qps-PLOC --12,,3456,,7890..1234789012
If you're working with a multithreaded system, such as ASP.Net, you can change the thread's CurrentCulture property. Changing the thread's culture will allow all of the associated ToString and String.Format calls to use that culture.
Update
Since you're wanting to display all of the precision you're going to have to do a bit of work. Using NumberFormat.NumberDecimalDigits will work, except that if the value has less precision, the number will output with trailing zeros. If you need to make sure you display every digit without any extras, you will need to calculate the precision beforehand and set that before you convert it to a string. The StackOverflow question Calculate System.Decimal Precision and Scale may be able to help you determine the precision of the decimal.

How to convert "12,4" to decimal en-Us culture

I have a decimal value ("133,3") stored in string column in the database, in norway culture.
after that user changed the regional setting to english-Us. when I convert "133,3" to decimal using CultureInfo.InvariantCulture, getting invalid value or error.
is there any best way to handle this scenario in C# application?
regards,
Anand
Regardless of the system culture, if you specify CultureInfo.InvariantCulture you won't be able to parse "133,3" as a decimal to 133.3. The same is true for US English.
You could just specify a Norwegian culture when parsing the value (using the overload of decimal.TryParse which takes an IFormatProvider), or (preferrably) change the field in the database to reflect the real data type (a decimal number) instead.
Do you referred to Convert.ToDecimal(), it says like
using System;
using System.Globalization;
public class Example
{
public static void Main()
{
string[] values = { "123456789", "12345.6789", "12 345,6789",
"123,456.789", "123 456,789", "123,456,789.0123",
"123 456 789,0123" };
CultureInfo[] cultures = { new CultureInfo("en-US"),
new CultureInfo("fr-FR") };
foreach (CultureInfo culture in cultures)
{
Console.WriteLine("String -> Decimal Conversion Using the {0} Culture",
culture.Name);
foreach (string value in values)
{
Console.Write("{0,20} -> ", value);
try {
Console.WriteLine(Convert.ToDecimal(value, culture));
}
catch (FormatException) {
Console.WriteLine("FormatException");
}
}
Console.WriteLine();
}
}
}
If you know the culture that was in use when persisting the value, you can use it when parsing it, i.e.:
Convert.ToDecimal("133,3", System.Globalization.CultureInfo.GetCultureInfo("no"));
Of course, you are probably better off changing how the data is stored in the database, to use a floating point number of some form.
Convert.ToDouble(textBox2.Text, new CultureInfo("uk-UA")).ToString(new CultureInfo("en-US"));
This solves your problem: .ToString(New CultureInfo("en-US"))
Hope it's helpful
double _number = 12536,8;
CultureInfo usCulture = new CultureInfo("en-US");
return _number.ToString("N", us);
used below code to fix my issue. I just hard coded the previous currency decimal part. may not be generic. but solved my problem.
public static decimal? ToDecimal1(this string source)
{
CultureInfo usCulture = new CultureInfo("en-US");
if (string.IsNullOrEmpty(source.Trim1()))
return null;
else
return Convert.ToDecimal(source.Replace(",", ".").Trim(), usCulture);
}

Categories