Parse value with Currency symbol - c#

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);
}

Related

Determining the date logic based on a given string in c#

I have written a logic in c# that determines the nextCallDate based on the given cobDate. cobDate is current date -1.
So if there are more than one future date in the given string then it should return the nearest future date to the cobdate and ignore the rest
For eg if the cobdate is 2020/02/12 and the string is
;2;4;2;5;20180328;3;103.3750;5;20190328;3;102.250;5; 20200328;3;101.1250;5;20210328;3;100.00;
Then NextCallDate would be 2020/03/28.
I need to return blank for dates in the past.
So say in the example if the given string has all the dates in the past then it should return blank.
Given string ;2;1;2;5;20120918;3;100.000000;
Here is what I have written
private DateTime? GetNextCallDate(string nextCallDate)
{
DateTime cobDate = DateTime.Now.Date.AddDays(-1);
var parts = nextCallDate.Split(';');
foreach (var part in parts)
{
DateTime parsedNextCallDate = DateTime.Parse(part);
if (parsedNextCallDate.Date > cobDate.Date)
{
return parsedNextCallDate;
}
}
return null;
}
You should probably be using DateTime.TryParse instead of Parse, since some of the values are not dates. Also, it looks like you're returning the first date that's greater than cobDate, not the nearest one.
To resolve this, we first set parsedNextCallDate to null, and this will be our default return value. Then we can check each part if it's a DateTime using the return value from TryParse, and then compare the value to both cobDate and parsedNextCallDate. If the date is greater than cobDate and less than parasedNextCallDate (or if parasedNextCallDate isn't set yet), then we update parasedNextCallDate to the new value. At the end, we just return parasedNextCallDate:
public static DateTime? GetNextCallDate(string input)
{
DateTime? nextCallDate = null;
if (string.IsNullOrWhiteSpace(input)) return nextCallDate;
var yesterday = DateTime.Today.AddDays(-1);
var inputItems = input.Split(';');
foreach (var inputItem in inputItems)
{
DateTime itemDate;
// If inputItem is a DateTime and it's greater than yesterday
if (DateTime.TryParseExact(inputItem.Trim(), "yyyyMMdd", null,
DateTimeStyles.None, out itemDate) &&
itemDate.Date > yesterday)
{
// and if nextCallDate doesn't have a value or the parsed value
// is less than nextCallDate, assign nextCallDate to this value
if (!nextCallDate.HasValue || itemDate < nextCallDate)
{
nextCallDate = itemDate;
}
}
}
return nextCallDate;
}
Here's one way solve your problem. Breaking things down into steps often makes things easier to reason with and easier to test. I'm often working on server side apps so I like the new span/memory classes. So first thing is to split our input string into chunks:
static IEnumerable<ReadOnlyMemory<char>> ReduceToPossibleDates(string source)
{
const int ExpectedDateLen = 9; // includes separator
int last = 0;
var mem = source.AsMemory();
for (int i = 0; i < source.Length; ++i)
{
if (';' == mem.Span[i])
{
int length = i - last;
if (length == ExpectedDateLen)
{
yield return mem.Slice(last+1,length-1);
}
last = i;
}
}
}
This gives us a stream of ReadOnlyMemory that all contains what we think should be dates. Next we can do another method to consume those chunks and turn them into dates.
static IEnumerable<DateTime> ToDateTime(IEnumerable<ReadOnlyMemory<char>> rawDates)
{
foreach (var rawDate in rawDates)
{
if (DateTime.TryParseExact(rawDate.Span,"yyyyMMdd".AsSpan(),
CultureInfo.InvariantCulture,
DateTimeStyles.None, out var date))
yield return date;
}
}
Once we have that we can treat the stream of dates however we want. In this case we check to find the first that's after our COB.
static void Main(string[] _)
{
const string GoodData = ";2;4;2;5;20180328;3;103.3750;5;20190328;3;102.250;"+
"5;20200328;3;101.1250;5;20210328;3;100.00;";
const string NoDateData = ";2;1;2;5;20120918;3;100.000000;";
var cobDate = new DateTime(2020, 2,12); // some actual close of business date...
var nextCallDate = ToDateTime(ReduceToPossibleDates(GoodData))
.FirstOrDefault(x => x >= cobDate);
var noDateExpected = ToDateTime(ReduceToPossibleDates(NoDateData))
.FirstOrDefault(x => x >= cobDate);
if (nextCallDate != default(DateTime))
Console.WriteLine(nextCallDate);
else
Console.WriteLine("no call date.");
if (noDateExpected != default(DateTime))
Console.WriteLine(nextCallDate);
else
Console.WriteLine("no call date.");
}
It would be a little cleaner with extension methods but you get the idea.

c# dollar to euro conversion is always returning 0

I am trying to make a dollar to euro converter. but strangely enough, it always returns 0. The intention is that the user, for example, 10 dollars to euro and then the result comes out how much 10 dollars than currently is in euros this is my code what I have at the moment.
This is my current code
public void EUR_TO_usd(string clipboardText)
{
ActionResult actionResult = new ActionResult(clipboardText);
if (clipboardText.EndsWith(" dollar to euro"))
{
var parts = clipboardText.Split(' ');
if (parts.Length == 4)
{
if (double.TryParse(parts[0], out double amount))
{
if (parts[1] == "dollar" && parts[2] == "to" && parts[3] == "euro")
{
string url = "http://api.openrates.io/latest?base=USD";
string json = new WebClient().DownloadString(url);
var currency = JsonConvert.DeserializeObject<dynamic>(json);
double curAmount = amount * Convert.ToSingle(currency.rates.EUR);
actionResult.Title = clipboardText;
actionResult.Description = string.Format("{0:N2} {1} = {2:N2} {3}", amount, currency["base"], curAmount, "EUR");
}
}
}
}
the only thing that worked was putting this in a switch and creating a variable amount with a value like this
case "dollar to euro":
{
string url = "http://api.openrates.io/latest?base=USD";
string json = new WebClient().DownloadString(url);
var amount = 1;
var currency = JsonConvert.DeserializeObject<dynamic>(json);
double curAmount = amount * (double)currency.rates.EUR;
{
actionResult.Title = clipboardText;
actionResult.Description = $"{amount:N2} {currency.#base} = {curAmount:N2} EUR";
}
}
return actionResult;
but what I want is that the user can copy the number of euros as in my first example, but that always returns 0.
How do I solve this?
I might be wrong but it doesn't seem like you are changing the actionResult, but only the title and description. And if you would want to just return a int i would use public int EUR_TO_usd()
And i would look into the naming convention in C#, since a function name shoud not look like that imo.
And of course you need to call your function with that string.

Round string before converting to numeric type

I have a number (just an example:
"3616,946489653802082229919075063226"
stored in a string. I want to convert it into decimal but get an OverflowException because the value is too long for decimal.
I want to round the string before converting it to fix the error.
How do I do this?
Parsing is culture specific. So , can be either decimal or thousand separator. If , is a thousand separator, it will be ignored:
"3616,946489653802082229919075063226" -> 3616946489653802082229919075063226m
And this value is over decimal.MaxValue so you have the exception thrown.
If , should be treated as a decimal separator, let system know it:
decimal result = decimal.Parse(source,
new NumberFormatInfo() { NumberDecimalSeparator = ","});
And you'll get 3616.9464896538020822299190751m
Your problem is not actually a rounding issue, it is trying to parse a decimal with a comma on a system which expects a dot as a decimal separator by default.
You could try this to make sure it is parsed well on all environments:
using System;
using System.Globalization;
public class Program
{
public static void Main()
{
var input = "3616,946489653802082229919075063226";
var result = decimal.Parse(input, new NumberFormatInfo() { NumberDecimalSeparator = ","});
Console.WriteLine(result);
}
}
https://dotnetfiddle.net/8iaL9d
Given an arbitrary number of decimal places in a format with a comma separating the units from the fractional part, you could solve it by doing the following:
class Program
{
static void Main(string[] args)
{
var decimalPlacesCount = 10;
var decimalSeparator = ',';
var parts = "3616,946489653802082229919075063226".Split(decimalSeparator);
var value = decimal.Parse(parts[0]);
if (parts.Length == 2)
{
value += decimal.Parse($"0.{parts[1].Substring(0, decimalPlacesCount)}");
}
Console.WriteLine(value);
}
}
This effectively rounds it to decimalPlacesCount.
While this wasn't your problem if anyone happens to stumble across this well-named question looking for a method that will actually round a string hopefully, the following will be helpful.
public static string RoundString(string value, int decimalPlaces)
{
StringBuilder returnValue = new StringBuilder(value);
int startIndex = 0;
int charIndex = 0;
while (charIndex < value.Length && startIndex == 0)
{
if (value[charIndex].ToString() == ".")
startIndex = charIndex + 1;
charIndex++;
}
if (int.Parse(value[charIndex + decimalPlaces + 1].ToString()) >= 5)
{
bool rounded = false;
for (charIndex = startIndex + decimalPlaces; charIndex > -1; charIndex--)
{
if (!rounded && charIndex != startIndex-1)
{
int newVal = int.Parse(returnValue[charIndex].ToString()) + 1;
if (newVal > 9)
{
returnValue[charIndex] = '0';
}
else
{
returnValue[charIndex] = (int.Parse(returnValue[charIndex].ToString()) + 1).ToString()[0];
rounded = true;
}
}
}
if (!rounded)
{
startIndex++;
returnValue = new StringBuilder("1" + returnValue.ToString());
}
}
return returnValue.ToString().Substring(0, startIndex + decimalPlaces);
}
It's pretty poorly written and I'm sure someone could do better, but it does the job. The StringBuilder is pretty shoddy for example and it can likely be made to run quicker.
Also, I do 0 validation on the actual input string.

Parsing string including currency code?

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);

String.Format Currency for K, M and B

If a currency amount is very large, I'm trying to abbreviate it.
For example:
if (amt > 1000000)
{
decimal d = (decimal)Math.Round(amt / 1000, 0);
return String.Format("{0:C0}", d) + " K";
}
If a number is given over 1 million, it will take off the last 3 digits and replace with a K. Works just fine when the currency symbol (like $ is on the left hand side)
However, some currency symbols get put on the right hand side.
So instead of a nice looking $100 K for USD, I'd get 100 € K for French Euros.
How can I change the format to put the K immediately after the numbers, and before the currency symbol.
Seems like it might be a step too far. Any ideas?
I would create a class with the IFormatProvider like this
public class MoneyFormat: IFormatProvider, ICustomFormatter
{
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
return this;
else
return null;
}
public string Format(string fmt, object arg, IFormatProvider formatProvider)
{
if (arg.GetType() != typeof(decimal))
try
{
return HandleOtherFormats(fmt, arg);
}
catch (FormatException e)
{
throw new FormatException(string.Format("The format of '{0}' is invalid", fmt), e);
}
string ufmt = fmt.ToUpper(CultureInfo.InvariantCulture);
if (!(ufmt == "K"))
try
{
return HandleOtherFormats(fmt, arg);
}
catch (FormatException e)
{
throw new FormatException(string.Format("The format of '{0}' is invalid", fmt), e);
}
decimal result;
if (decimal.TryParse(arg.ToString(), out result))
{
if (result >= 1000000)
{
decimal d = (decimal)Math.Round(result / 10000, 0);
CultureInfo clone = (CultureInfo)CultureInfo.CurrentCulture.Clone();
string oldCurrSymbol = clone.NumberFormat.CurrencySymbol;
clone.NumberFormat.CurrencySymbol = "";
return String.Format(clone, "{0:C0}", d).Trim() + " K" + oldCurrSymbol;
}
}
else
return string.Format("{0:C0}", result) + " K";
}
private string HandleOtherFormats(string format, object arg)
{
if (arg is IFormattable)
return ((IFormattable)arg).ToString(format, CultureInfo.CurrentCulture);
else if (arg != null)
return arg.ToString();
else
return string.Empty;
}
}
Then you can call it in your format like so:
return string.Format( new MoneyFormat(), "{0:K}", amt);
You can then tweek the way you want to represent your "K" or other reference symbols that you want to add
CultureInfo("fr-fr") : 100 K€
CultureInfo("en-us") : 100 K$
CultureInfo("ru-RU") : 100 Kр.
You can use the CurrencyPositivePattern to determine if the currency symbol comes before or after the number. Then you can modify the CurrencySymbol to suit your needs.
decimal amt = 10000000;
Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR"); //set up France as current culture
NumberFormatInfo NFI = CultureInfo.CurrentCulture.NumberFormat;
string currencySymbol = NFI.CurrencySymbol;
int currencyPosition = NFI.CurrencyPositivePattern;
if (amt > 1000000)
{
if (currencyPosition == 3) // n $
{
NFI.CurrencySymbol = "K " + currencySymbol;
}
decimal d = (decimal)Math.Round(amt / 1000, 0);
string output = d.ToString("c");
}
I know this is not the best implementation of a custom number format, but this is just to get the idea across.
See:
NumberFormatInfo.CurrencyPositivePattern Property

Categories

Resources