Parsing string including currency code? - c#

I know there are many question similar to this one explaining how to parse a string with a currency symbol. Here I would like to do the same but with currency ISO-4217 code (https://en.wikipedia.org/wiki/ISO_4217).
decimal.Parse("45,000.00 USD", System.Globalization.NumberStyles.AllowDecimalPoint)
decimal.Parse("45.00 USD")
decimal.Parse("USD 45.00")
decimal.Parse("USD45.00")
decimal.Parse("45.00USD")
decimal.Parse("45.00 RUP")
decimal.Parse("IND 45.00")
decimal.Parse("45.00 EUR")
decimal.Parse("INR 45.00")
I was thinking about a solution with regular expression but maybe there are more direct solution or something already exist in .NET lib. I don't know.

You can use NumberFormatInfo.
See the example i have written below.
var nmfi = new NumberFormatInfo();
nmfi.NegativeSign = "-";
nmfi.CurrencySymbol = "USD";
nmfi.CurrencyDecimalSeparator = ".";
nmfi.CurrencyGroupSeparator = ",";
var result1 = decimal.Parse("USD45.00", NumberStyles.Currency, nmfi);
var result2 = decimal.Parse("45.00USD", NumberStyles.Currency, nmfi);
var result3 = decimal.Parse("45.00 USD", NumberStyles.Currency, nmfi);

At this moment I have no better solution than mine
public static decimal Parse(string s)
{
if (string.IsNullOrEmpty(s))
throw new ArgumentNullException("s is null");
var match = Regex.Match(s, "[A-Z]{3}");
if (!match.Success)
throw new FormatException("s is not in the correct format. Currency code is not found.");
s = s.Replace(match.Value, string.Empty); // I don't like this line
decimal value = decimal.Parse(s, NumberStyles.Currency);
return value;
}
In my code I use a Money Object. Money is composed by a decimal value and a 3 character currency code.
public static Money Parse(string s)
{
if (string.IsNullOrEmpty(s))
throw new ArgumentNullException("s is null");
var match = Regex.Match(s, "[A-Z]{3}");
if (!match.Success)
throw new FormatException("s is not in the correct format. Currency code is not found.");
s = s.Replace(match.Value, string.Empty); // I don't like this line
decimal value = decimal.Parse(s, NumberStyles.Currency);
return new Money(value, (CurrencyCode)Enum.Parse(typeof(CurrencyCode), match.Value));
}

Your best bet here is to bundle the decimal number with it's format so that it is easy for you to know what currency the value is for. I have created IsoDecimal helper struct to help with such thing.
void Main()
{
var arr = new string[]
{
"45,000.00 USD" ,
"45.00 USD" ,
"USD 45.00" ,
"USD45.00" ,
"45.00USD" ,
"45.00 RUP" ,
"IND 45.00" ,
"45.00 EUR" ,
"INR 45.00"
};
foreach (var num in arr)
{
Console.WriteLine(new IsoDecimal(num).ToString());
}
}
The struct is here.
public struct IsoDecimal
{
private NumberFormatInfo numberFormat { get; set; }
private decimal value { get; set; }
public IsoDecimal(string strValue)
{
string strNumber = Regex.Match(strValue, #"[\d.\-,]+").Value;
string code = Regex.Match(strValue, #"[A-Z]+").Value;
numberFormat = new NumberFormatInfo();
numberFormat.NegativeSign = "-";
numberFormat.CurrencyDecimalSeparator = ".";
numberFormat.CurrencyGroupSeparator = ",";
numberFormat.CurrencySymbol = code;
value = Decimal.Parse(strNumber);
}
public static implicit operator decimal(IsoDecimal isoDecimal)
{
return isoDecimal.value;
}
public override string ToString()
{
return ToString("C");
}
public string ToString(string format)
{
return value.ToString(format, numberFormat);
}
}
The struct allows you to assign its value to a decimal variable at will in case you need it, and it overrides Object.ToString to that it is seamless to integrate and work with.

You could use regex
Decimal.Parse(Regex.Match("USD 45.00", #"(\d+(\.\d+)?)").Value);

Related

string to decimal using extension methods

My string is 1799.00
I want to like this: 1.799,00
But i cannot convert this method.
I'm using this function.
public static decimal ToDecimal(this object str)
{
if (str != null)
{
try
{
return Convert.ToDecimal(str, new CultureInfo("tr-TR"));
}
catch (Exception)
{
return 0;
}
}
else
{
return 0;
}
}
You are, probably, looking for changing formats. Given a decimal as a string in invariant culture representation ("1799.00") you want a string, but in Turkish cutrure representation: ("1.799,00")
// you want to return string in Turkish culture, right?
public static string ToTuskishDecimal(this object value) {
if (null == value)
return null; // or throw exception, or return "0,00" or return "?"
try {
return Convert
.ToDecimal(value, CultureInfo.InvariantCulture)
.ToString("N", CultureInfo.GetCultureInfo("tr-TR"));
}
catch (FormatException) {
return "0,00"; // or "?"
}
}
Test:
decimal d = 1799.00m;
string s = d.ToString(CultureInfo.InvariantCulture);
// 1.799,00
Console.Write(d.ToTuskishDecimal());
// 1.799,00
Console.Write(s.ToTuskishDecimal());
In case you want to return decimal you have to format it manually when printing out:
public static decimal ToDecimal(this object value) {
if (null == value)
return 0.00m;
try {
return Convert.ToDecimal(value, CultureInfo.InvariantCulture);
}
catch (FormatException) {
return 0.00m;
}
}
...
// return me a decimal, please
decimal d = "1799.00".ToDecimal();
// when printing decimal, use Turkish culture
Console.Write(d.ToString("N", CultureInfo.GetCultureInfo("tr-TR")));
you can specify Turkish culture as a default one for the entire thread:
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("tr-TR");
...
// now you don't have to mention Turkish culture
// ...but still have to specify the format
Console.Write(d.ToString("N"));
I use this to obtain a decimal value:
public static decimal toDecimal( string s ) {
decimal res = 0;
decimal.TryParse(s.Replace('.', ','), out res);
return res;
}
In case you want to show the decimal value on the format you ask, try this:
toDecimal("1700.99").ToString("N2")
you will obtain the "1.700,99" string.
I wrote another simple example to obtain a decimal value in Turkish format:
decimal value = 1700.00m;
Console.WriteLine(value.ToString("N", CultureInfo.GetCultureInfo("tr-TR")));

Parse value with Currency symbol

I have looked to multiple SO questions on parsing currency, the best (recommended) way seems to be the one I'm trying below:
var payout = decimal.Parse("$2.10", NumberStyles.Currency | NumberStyles.AllowDecimalPoint);
However, it throws and exception: Input string is not in the correct format.
I don't know what I'm doing wrong?
EDIT
Thanks for the answers. Additional info: the hard-coded currency value I gave was just an example. I have a list of currencies:
€2,66
$2.10
$5.55
etc.
I cannot determine the culture info in advance. Any ideas?
Similar approach #un-lucky mentioned as one of the answer, I tried making it generic and work for every Symbol/Format
public static decimal ParseCurrencyWithSymbol(string input)
{
var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures)
.GroupBy(c=> c.NumberFormat.CurrencySymbol)
.ToDictionary(c=> c.Key, c=>c.First());
var culture = cultures.FirstOrDefault(c=>input.Contains(c.Key));
decimal result = 0;
if(!culture.Equals(default(KeyValuePair<string,CultureInfo>)))
{
result = decimal.Parse(input, NumberStyles.Currency | NumberStyles.AllowDecimalPoint, culture.Value);
}
else
{
if( !decimal.TryParse(input, out result))
{
throw new Exception("Invalid number format");
}
}
return result;
}
Usage
decimal output = ParseCurrencyWithSymbol("$2.10");
Working Code
You can try like this:
decimal currencyValue;
string inputCurrency = "$12.6";
if (decimal.TryParse(inputCurrency, NumberStyles.Currency, CultureInfo.CreateSpecificCulture("en-US"), out currencyValue))
{
// proceed with currencyValue
}
else
{
//Show error ; Conversion failed
}
For dealing with all currencies you can use the following:
Dictionary<char, string> currencyCulture = new Dictionary<char, string>();
currencyCulture.Add('$', "en-US");
currencyCulture.Add('€', "en-IE");
// populate all posible values here
decimal currencyValue;
string inputCurrency = "€2,66";
char currencySymbol= inputCurrency.ToCharArray()[0];
CultureInfo currentCulture= CultureInfo.CreateSpecificCulture(currencyCulture[currencySymbol]);
if (decimal.TryParse(inputCurrency, NumberStyles.Currency, currentCulture, out currencyValue))
{
// proceed with currencyValue
}
else
{
//Show error ; Conversion failed
}
You can choose culture Names from here
Pertinent to your particular case, you may use the following code snippet:
var payout = decimal.Parse("$2.10".Replace("$",""));
If you don't know what the currency symbol would be, then try the following solution:
string _money = "$2.10";
var payout = decimal.Parse(_money.Substring(1));
Dealing with commas and decimal points is much more difficult: if this is the issue, refer to the solution given by member #un-lucky.
Hope this may help.
How about a CleanCurrency method?
/// Loops each char in the string and returns only numbers, . or ,
static double? CleanCurrency(string currencyStringIn) {
string temp = "";
int n;
for (int i = 0; i < currencyStringIn.Length; i++) {
string c = currencyStringIn.Substring(i, 1);
if (int.TryParse(c, out n) || c == "." || c == ",") {
temp += c;
}
}
if (temp == "") {
return null;
else {
return double.Parse("0" + temp);
}
}
The idea here being to just get an actual number regardless of what the string content is.
double? payout = CleanCurrency("$3.50");
I've expounded on Hari Prasad's answer. With this you can minimize the culture result. Update "SupportedCultures" with the ones you might use in your app.
private static readonly List<string> SupportedCultures = new List<string> {"en-US", "en-GB", "fr-FR"};
public static void Main()
{
var (amount, culture) = ParseCurrencyWithSymbol("$256.12");
Console.WriteLine($"{culture?.Name} | {amount}");
var (amount2, culture2) = ParseCurrencyWithSymbol("£389.17");
Console.WriteLine($"{culture2?.Name} | {amount2}");
var (amount3, culture3) = ParseCurrencyWithSymbol("€421,10");
Console.WriteLine(culture3 != null ? $"{culture3.Name} | {amount3}" : "Failed!");
}
public static Tuple<decimal?, CultureInfo> ParseCurrencyWithSymbol(string input)
{
var culture = CultureInfo.GetCultures(CultureTypes.AllCultures)
.Where(x => SupportedCultures.Contains(x.Name))
.FirstOrDefault(c => input.Contains(c.NumberFormat.CurrencySymbol));
if (culture == null) return new Tuple<decimal?, CultureInfo>(null, null);
return new Tuple<decimal?, CultureInfo>(decimal.Parse(input,
NumberStyles.Currency | NumberStyles.AllowCurrencySymbol |
NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands, culture), culture);
}

How can i write this java code in C#

i have an array of integer called digits
public String toDecimalString() {
StringBuilder b = new StringBuilder(9 * digits.length);
Formatter f = new Formatter(b);
f.format("%d", digits[0]);
for(int i = 1 ; i < digits.length; i++) {
f.format("%09d", digits[i]);
}
return b.toString();
}
I tried
String.Format("%09d", digits[i]);
but I think I'm doing something wrong
I'm not really familiar with java formatters, but I think this is what you want
var str = string.Format("{0:D9}", digits[i]);
Or even better
var str = digits[i].ToString("D9");
To join all these strings I suggest this:
var str = string.Join(string.Empty, digits.Select(d => d.ToString("D9")));
Further Reading
Standard Numeric Format Strings
Custom Numeric Format Strings
I think you want something like
StringBuilder sb = new StringBuilder();
sb.append(String.Format("DL", digits[i]));
for (int i = 1; i < digits.Length; i++) {
sb.append(String.Format("D9", digits[i]));
}
Copy from java code and paste it directly into c# code, then change (which are in your toDecimalString() method):
f.format to f.Format
digits.length to digits.Length
b.toString() to b.ToString()
and then paste this class to your code:
public partial class Formatter: IFormatProvider, ICustomFormatter {
public String Format(String format, object arg, IFormatProvider formatProvider=null) {
if(!format.StartsWith("%")||!format.EndsWith("d"))
throw new NotImplementedException();
m_Builder.Append(String.Format("{0:D"+format.Substring(1, format.Length-2)+"}", arg));
return m_Builder.ToString();
}
object IFormatProvider.GetFormat(Type formatType) {
return typeof(ICustomFormatter)!=formatType?null:this;
}
public Formatter(StringBuilder b) {
this.m_Builder=b;
}
StringBuilder m_Builder;
}
Note that the class only implemented the minimum requirement as your question stated, you would need to add the code if your further extend the requirement.
public string toDecimalString()
{
StringBuilder b = new StringBuilder(9 * digits.Length);
var str = digits[0].ToString("D");
b.Append(str);
for (int i = 1; i < digits.Length; i++)
{
var str2 = digits[i].ToString("D9");
b.Append(str2);
}
return b.ToString();
}
Thanks for all the answers, I finally reached a solution as above

Find and Replace RegEx with wildcard search and addition of value

The below code is from my other questions that I have asked here on SO. Everyone has been so helpful and I almost have a grasp with regards to RegEx but I ran into another hurdle.
This is what I basically need to do in a nutshell. I need to take this line that is in a text file that I load into my content variable:
X17.8Y-1.Z0.1G0H1E1
I need to do a wildcard search for the X value, Y value, Z value, and H value. When I am done, I need this written back to my text file (I know how to create the text file so that is not the problem).
X17.8Y-1.G54G0T2
G43Z0.1H1M08
I have code that the kind users here have given me, except I need to create the T value at the end of the first line, and use the value from the H and increment it by 1 for the T value. For example:
X17.8Y-1.Z0.1G0H5E1
would translate as:
X17.8Y-1.G54G0T6
G43Z0.1H5M08
The T value is 6 because the H value is 5.
I have code that does everything (does two RegEx functions and separates the line of code into two new lines and adds some new G values). But I don't know how to add the T value back into the first line and increment it by 1 of the H value. Here is my code:
StreamReader reader = new StreamReader(fDialog.FileName.ToString());
string content = reader.ReadToEnd();
reader.Close();
content = Regex.Replace(content, #"X[-\d.]+Y[-\d.]+", "$0G54G0");
content = Regex.Replace(content, #"(Z(?:\d*\.)?\d+)[^H]*G0(H(?:\d*\.)?\d+)\w*", "\nG43$1$2M08"); //This must be created on a new line
This code works great at taking:
X17.8Y-1.Z0.1G0H5E1
and turning it into:
X17.8Y-1.G54G0
G43Z0.1H5M08
but I need it turned into this:
X17.8Y-1.G54G0T6
G43Z0.1H5M08
(notice the T value is added to the first line, which is the H value +1 (T = H + 1).
Can someone please modify my RegEx statement so I can do this automatically? I tried to combine my two RegEx statements into one line but I failed miserably.
Update1: Stephen in the comments below suggests, "there's no arithmetic operators in regex, you'll need to use a group to pull out the H value, turn it into an int, add one and build a new string.". But I have no idea on how to do this in C# code.
The easiest way to do this is with a simple program that uses a few Regex patterns that capture (named) groups, I had a little spare time so here you go:
Program.cs
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
const string InputFileName = #"input.txt";
const string OutputFileName = #"output.txt";
List<Line> parsedLineList = new List<Line>();
using (StreamReader sr = new StreamReader(InputFileName))
{
string inputLine;
int lineNum = 0;
while ((inputLine = sr.ReadLine()) != null)
{
lineNum++;
Line parsedLine = new Line(inputLine);
if (parsedLine.IsMatch)
{
parsedLineList.Add(parsedLine);
}
else
{
Debug.WriteLine("Line {0} did not match pattern {1}", lineNum, inputLine);
}
}
}
using (StreamWriter sw = new StreamWriter(OutputFileName))
{
foreach (Line line in parsedLineList)
{
sw.WriteLine(line.ToString());
}
}
}
}
}
With input.txt containing:
X17.8Y-1.Z0.1G0H1E1
this program creates output.txt containing:
X17.8Y-1.G54G0T2G43Z0.1H1M08
The above code in Program.cs requires the following simple Line and Fragment class definitions:
Line.cs
namespace Fragments
{
class Line
{
private readonly static Regex Pattern =
new Regex(#"^(?<X>X[^Y]+?)(?<Y>Y[^Z]+?)(?<Z>Z[^G]+?)(?<G>G[^H]+?)(?<H>H[^E]+?)(?<E>E[^$])$");
public readonly string OriginalText;
public string Text
{
get
{
return this.X.ToString() + this.Y.ToString() + this.G54.ToString() + this.G.ToString() + this.T.ToString() + Environment.NewLine +
this.G43.ToString() + this.Z.ToString() + this.H.ToString() + this.M08.ToString();
}
}
public readonly bool IsMatch;
public Fragment X { get; set; }
public Fragment Y { get; set; }
public readonly Fragment G54 = new Fragment("G54");
public Fragment G { get; set; }
public Fragment T { get; set; }
public readonly Fragment G43 = new Fragment("G43");
public Fragment Z { get; set; }
public Fragment H { get; set; }
public readonly Fragment M08 = new Fragment("M08");
public Fragment E { get; set; }
public Line(string text)
{
this.OriginalText = text;
Match match = Line.Pattern.Match(text);
this.IsMatch = match.Success;
if (match.Success)
{
this.X = new Fragment(match.Groups["X"].Value);
this.Y = new Fragment(match.Groups["Y"].Value);
this.G = new Fragment(match.Groups["G"].Value);
this.Z = new Fragment(match.Groups["Z"].Value);
this.H = new Fragment(match.Groups["H"].Value);
this.E = new Fragment(match.Groups["E"].Value);
this.T = new Fragment('T', this.H.Number + 1.0);
}
}
public override string ToString()
{
return this.Text;
}
}
}
Fragment.cs
namespace Fragments
{
class Fragment
{
private readonly static Regex Pattern =
new Regex(#"^(?<Letter>[A-Z]{1})(?<Number>.+)$");
public readonly string Text;
public readonly bool IsMatch;
public readonly char Letter;
public readonly double Number;
public Fragment(string text)
{
this.Text = text;
Match match = Fragment.Pattern.Match(text);
this.IsMatch = match.Success;
if (match.Success)
{
this.Letter = match.Groups["Letter"].Value[0];
string possibleNumber = match.Groups["Number"].Value;
double parsedNumber;
if (double.TryParse(possibleNumber, out parsedNumber))
{
this.Number = parsedNumber;
}
else
{
Debug.WriteLine("Couldn't parse double from input {0}", possibleNumber);
}
}
else
{
Debug.WriteLine("Fragment {0} did not match fragment pattern", text);
}
}
public Fragment(char letter, double number)
{
this.Letter = letter;
this.Number = number;
this.Text = letter + number.ToString();
this.IsMatch = true;
}
public override string ToString()
{
return this.Text;
}
}
}
Create a new C# Console Application project, add these three files, update your using statements and you're ready to go. You can very easily alter the code in Program.cs to read the input and output filenames from Main's command line arguments to make the program reusable.
I'm not sure you can do this just with Regular Expressions, and even in case you can, thinking on maintainability of the code, I wouldn't implement it that way. What you can easily do with RegEx, is to capture the pieces you need into groups, and from those create the output expression.
Here's the code for that:
System.Text.StringBuilder content = new System.Text.StringBuilder();
using (var reader = new StreamReader(fDialog.FileName.ToString()))
{
string line = reader.ReadLine();
while (line != null)
{
var matchingExpression = Regex.Match(line, #"(X[-\d.]+)(Y[-\d.]+)(Z(?:\d*\.)?\d+)[^H]*G0H((?:\d*\.)?\d+)\w*");
content.AppendFormat(
System.Globalization.CultureInfo.InvariantCulture,
"{0}{1}G54G0T{2}\n",
matchingExpression.Groups[0].Value,
matchingExpression.Groups[1].Value,
Int32.Parse(matchingExpression.Groups[3].Value) + 1);
content.AppendFormat(
System.Globalization.CultureInfo.InvariantCulture,
"G43{0}H{1}M08\n",
matchingExpression.Groups[2].Value,
matchingExpression.Groups[3].Value);
line = reader.ReadLine();
}
}
And to get the output string you should do:
content.ToString();

How can I use Convert.ChangeType to convert string into numerics with group separator?

I want to make a generic string to numeric converter, and provide it as a string extension, so I wrote the following code:
public static bool TryParse<T>( this string text, out T result, IFormatProvider formatProvider ) where T : struct
try
{
result = (T)Convert.ChangeType( text, typeof( T ), formatProvider );
return true;
}
catch(...
I call it like this:
int value;
var ok = "123".TryParse(out value, NumberFormatInfo.CurrentInfo)
It works fine until I want to use a group separator:
As I live in France, where the thousand separator is a space and the decimal separator is a comma, the string "1 234 567,89" should be equals to 1234567.89 (in Invariant culture). But, the function crashes!
When a try to perform a non generic conversion, like double.Parse(...), I can use an overload which accepts a NumberStyles parameter. I specify NumberStyles.Number and this time it works!
So, the questions are :
Why the parsing does not respect my NumberFormatInfo (where the NumberGroupSeparator is well specified to a space, as I specified in my OS)
How could I make work the generic version with Convert.ChangeTime, as it has no overload wich accepts a NumberStyles parameter ?
Try specifying explicitly the culture "fr-FR":
CultureInfo c = new CultureInfo("fr-FR");
double d = 0;
if ("1 234 567,89".TryParse<double>(out d, c)) {
Console.WriteLine(d);
}
EDIT: this example works properly:
static class Extension {
public static bool TryParse<T>(this string text, out T result, IFormatProvider formatProvider) where T : struct {
result = default(T);
try {
result = (T)Convert.ChangeType(text, typeof(T), formatProvider);
return true;
} catch {
return false;
}
}
}
class Program {
static void Main(string[] args) {
CultureInfo c = new CultureInfo("fr-FR");
double d = 0;
// NumberGroupSeparator in fr-FR culture is space
bool res = "123 456,78".TryParse<double>(out d, c);
// Set separator as '.' and parse string with dots
c.NumberFormat.NumberGroupSeparator = ".";
res = "123.456,78".TryParse<double>(out d, c);
}
}
Est-ce que ca marche comme ca? :)

Categories

Resources