Change display format number decimal separator - c#

I have the following float number : -95.83334
this is in my view model:
[DisplayFormat(DataFormatString = "{0:#.##}")]
public float? mx { get; set; }
this is in my view
#Html.HiddenFor(model => model.mx)
this is the generated html
<input data-val="true" id="mx" name="mx" type="hidden" value="-95,83334">
and this is the desired html
<input data-val="true" id="mx" name="mx" type="hidden" value="-95.83334">
so the question is, which is the best way to change the decimal separator for this hidden input? without alter the the rest of my project

DisplayFormat is used when you use Html.DisplayFor. And, it's only for formatting what's supposed to be displayed on the view. If you want to change the format of decimal numbers in general, you'll need to use a different culture.

Finally, this was the solution for me
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
#Html.HiddenFor(model => model.mx)
adding the line
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
before my hidden solved the problem. thanks for the ideas and guides!

#Html.HiddenFor(model => model.mx.ToString(new CultureInfo("en-us")));
Edit:
Sorry for the brief answer. I think it would have been useful if it had worked, unfortunately I missed something important in your code.
Your issue is a localization problem. I'm guessing your machine is running with a European culture. (Just noticed your comment about being it set as Spanish)
ToString() has been overloaded for some of the basic types, float included. One of these overloads accepts an IFormatProvider which CultureInfo implements. By passing a CultureInfo using the culture code for United States English, you can ensure the dot will be used instead of the comma as the decimal separator.
What I missed is that mx is a float?, which is short for Nullable. Nullable does not overload ToString in the same way, so you have a couple of alternatives.
If you are using C#6, you can use the null check operator to convert your float? back to a float. You could then use the ToString overload:
#Html.HiddenFor(model => model.mx?.ToString(CultureInfo.GetCultureInfo("en-us")));
Alternatively, you can use the String.Format overload, which accepts both a format string and an IFormatProvider:
#Html.HiddenFor(model => String.Format(CultureInfo.GetCultureInfo("en-us"), "{0:#.##}", model.mx));
I did not want to suggest changing the culture of your thread because this stood out in your question "without alter the the rest of my project" which I, perhaps mistakenly, assumed to mean you did not want to change the formatting of other components in your application. Changing the default thread culture could have a larger impact than you anticipate.
Edit 2:
Here is my attempt at an extension overload accepting an IFormatProvider. This was just an experiment and shouldn't be used in production... I'm including it purely for interest sake.
public static class HtmlExtensions
{
public static IHtmlString HiddenFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IFormatProvider formatter)
{
var value = expression.Compile().Invoke(helper.ViewData.Model);
var modelMetadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
var property = typeof(TModel).GetProperty(modelMetadata.PropertyName);
var attribute = property.GetCustomAttributes(typeof(DisplayFormatAttribute), false).SingleOrDefault() as DisplayFormatAttribute;
var displayValue = String.Format(formatter, attribute?.DataFormatString ?? "{0}", value);
TagBuilder tagBuilder = new TagBuilder("input");
tagBuilder.MergeAttribute("type", "hidden");
tagBuilder.MergeAttribute("value", displayValue);
return MvcHtmlString.Create(tagBuilder.ToString());
}
}
Use it like this
#Html.HiddenFor(mode => mode.myNumber, System.Globalization.CultureInfo.GetCultureInfo("en-us"))

Related

Textbox for DateTime wrong format

I know this is one of the most frequently asked questions.
I had the solution to this but my current project is driving me nuts.
I use the exact same code and still the result is different.
This is the code I normally use to format my dates:
#Html.TextBoxFor(m => m.DateOfAgenda, "{0:dd/MM/yyyy}")
Normally this should result in 07/10/2014 but for some reason it results in 07-10-2014
When I test it in another project the code above works as expected.
As a test I compared the results of the follow lines:
#Html.TextBoxFor(m => m.DateOfAgenda, "{0:dd/MM/yyyy}")
/* result: 07-10-2014*/
#Html.TextBoxFor(m => m.DateOfAgenda)
/* result: 7-10-2014 00:00:00*/
So my code seems to work partially only the '-' won't be replaced by '/'
Does somebody know how to handle this once and for all?
EDIT
I have custom code setting the Culture.
It uses the route to set it to a specific culture (default is 'nl')
When I put the code below in comments everything seems to work.
But I need this part to load my resources (label text, errors, ...).
var language = handler.RequestContext.RouteData.Values["language"];
if (language != null)
{
Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(language.ToString());
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(language.ToString());
}
To be more specific this line breaks the format
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(language.ToString());
Does anybody know why? I would expect that {0:dd/MM/yyyy} has priority because it is explicitly set.
Funny part is that the / is just translated to -.
When I use {0:dd//MM//yyyy} it results in 19--10--2014.
EDIT
Sorry for my late response but I couldn't find the time to try this out.
So basically the problem was the culture and my format settings.
#Html.TextBoxFor(m => m.DateOfAgenda, "{0:dd/MM/yyyy}")
The '/' still gets replaced by the divider specified by your culture.
My culture was set to 'nl' but by default this results in 'nl-nl' where they use '-' as date separator. I live in Belgium 'nl-be' and here we use '/'.
The could have set my culture or change the format.
The code below did the trick.
#Html.TextBoxFor(m => m.DateOfAgenda, "{0:dd'/'MM'/'yyyy}")
I'm not that familiar with ASP.NET MVC, but if you don't specify a format, your current culture's DateTimeFormat is used. Also, / is a special format specifier in .NET which means "replace me with the current DateTimeFormats date-separator which seems to be -.
So you should provide CultureInfo.InvariantCulture or mask the format specifier by using \\/instead:
#Html.TextBoxFor(m => m.DateOfAgenda, "{0:dd\\/MM\\/yyyy}")
or by using the character literal '/':
#Html.TextBoxFor(m => m.DateOfAgenda, "{0:dd'/'MM'/'yyyy}")
Read: The "/" Custom Format Specifier
This appears to be by design. TextBoxFor sets the value as per the following code snippet
string attemptedValue = (string)htmlHelper.GetModelStateValue(fullName, typeof(string));
tagBuilder.MergeAttribute("value", attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(fullName, format) : valueParameter), isExplicitValue);
which calls the EvalString method of HtmlHelper
internal string EvalString(string key, string format)
{
return Convert.ToString(ViewData.Eval(key, format), CultureInfo.CurrentCulture);
}
which calls the Eval method of ViewDataDictionary
public string Eval(string expression, string format)
{
object value = Eval(expression);
return FormatValueInternal(value, format);
}
internal static string FormatValueInternal(object value, string format)
{
....
return String.Format(CultureInfo.CurrentCulture, format, value);
}
So the value uses the format of the current culture. I presume this is because the DefaultModelBinder use the current culture info to parse the string representation of the date back to a DateTime (if you were able to override it, it would not bind unless you created a custom model binder.
View Page
#Html.TextBoxFor(m => m.DateOfAgenda, new { Value = Model.DateOfAgenda.ToString("dd/MM/yyyy")});
Model page
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime DateOfAgenda{ get; set;}
View Page
#Html.EditorFor(model => model.DateOfAgenda)

Converting string to currency and globalization issue

I have this:
var pl = new CultureInfo("pl-PL");
decimal valsue = decimal.Parse("2,25 PLN", pl);
It works ok if i don't put "PLN" into my string. But PLN is currency in my country and i think that it should parse, so maybe i am doing something wrong? Is there any option to parse this into decimal with "PLN" attached to the string?
If you take a look at your CultureInfo's NumberFormat object, this will yield some clues about how it intends to parse values. In particular, NumberFormat.CurrencySymbol for the "pl-PL" culture is zł.
In other words, this expression would parse successfully: decimal.Parse("2,25 zł", pl);
If you prefer to use PLN as the currency symbol (technically, it's the currency code), it is possible to configure a custom NumberFormat, like so:
var pln = (NumberFormatInfo) pl.NumberFormat.Clone(); // Clone is needed to create a copy whose properties can be set.
pln.CurrencySymbol = "PLN";
decimal valsue = decimal.Parse("2,25 PLN", NumberStyles.Currency, pln);
But note the usage of NumberStyles.Currency in the Parse call: by default, decimal.Parse accepts only strings containing numeric values, without currency formatting.

Convert string to decimal with format

I need convert a String to a decimal in C#, but this string have different formats.
For example:
"50085"
"500,85"
"500.85"
This should be convert for 500,85 in decimal. Is there is a simplified form to do this convertion using format?
Some cultures use a comma to indicate the floating point. You can test this with the following code on an aspx page:
var x = decimal.Parse("500,85");
Response.Write(x + (decimal)0.15);
This gives the answer 501 when the thread culture has been set to a culture that uses the comma as floating point. You can force this like so:
var x = decimal.Parse("500,85", new NumberFormatInfo() { NumberDecimalSeparator = "," });
While decimal.Parse() is the method you are looking for, you will have to provide a bit more information to it. It will not automatically pick between the 3 formats you give, you will have to tell it which format you are expecting (in the form of an IFormatProvider). Note that even with an IFormatProvider, I don't think "50085" will be properly pulled in.
The only consistent thing I see is that it appears from your examples that you always expect two decimal places of precision. If that is the case, you could strip out all periods and commas and then divide by 100.
Maybe something like:
public decimal? CustomParse(string incomingValue)
{
decimal val;
if (!decimal.TryParse(incomingValue.Replace(",", "").Replace(".", ""), NumberStyles.Number, CultureInfo.InvariantCulture, out val))
return null;
return val / 100;
}
This will work, depending on your culture settings:
string s = "500.85";
decimal d = decimal.Parse(s);
If your culture does not by default allow , instead of . as a decimal point, you will probably need to:
s = s.Replace(',','.');
But will need to check for multiple .'s... this seems to boil down to more of an issue of input sanitization. If you are able to validate and sanitize the input to all conform to a set of rules, the conversion to decimal will be a lot easier.
Try this code below:
string numValue = "500,85";
System.Globalization.CultureInfo culInfo = new System.Globalization.CultureInfo("fr-FR");
decimal decValue;
bool decValid = decimal.TryParse(numValue, System.Globalization.NumberStyles.Number, culInfo.NumberFormat, out decValue);
if (decValid)
{
lblDecNum.Text = Convert.ToString(decValue, culInfo.NumberFormat);
}
Since I am giving a value of 500,85 I will assume that the culture is French and hence the decimal separator is ",". Then decimal.TryParse(numValue, System.Globalization.NumberStyles.Number, culInfo.NumberFormat,out decValue);
will return the value as 500.85 in decValue. Similarly if the user is English US then change the culInfo constructor.
There are numerous ways:
System.Convert.ToDecimal("232.23")
Double.Parse("232.23")
double test;
Double.TryParse("232.23", out test)
Make sure you try and catch...
This is a new feature called Digit Grouping Symbol.
Steps:
Open Region and Language in control panel
Click on Additional setting
On Numbers tab
Set Digit Grouping Symbol as custom setting.
Change comma; replace with (any character as A to Z or {/,}).
Digit Grouping Symbol=e;
Example:
string checkFormate = "123e123";
decimal outPut = 0.0M;
decimal.TryParse(checkFormate, out outPut);
Ans: outPut=123123;
Try This
public decimal AutoParse(string value)
{
if (Convert.ToDecimal("3.3") == ((decimal)3.3))
{
return Convert.ToDecimal(value.Replace(",", "."));
}
else
{
return Convert.ToDecimal(value.Replace(".", ","));
}
}

Convert DateTime with TypeDescriptor.GetConverter.ConvertFromString (using custom format)

Is there any way to make
TypeDescriptor.GetConverter.ConvertFromString convert to DateTime using a custom format e.g. "2011-04-21 13-03-56"?
If not, is there any DateTime-format out there that can be applied to a folder name (/,\,:,etc. are not allowed as you know)?
Help is very much appreciated. Thanks in advance
Random-I-Am
Edit:
Since my request still seems to not be understood correctly I am again trying to elaborate on my question. My users are creating folders with specific names. For example "1000_Link_Topic1_2011-01-25 14-12-10". They are free to combine their information as they like and omit information where needed. They don't even have to care about case sensitivity. So I could face another folder named "1000_link_Topic2".
What I have is a single class with a single property for each possible fragment of information. In this case I would have (Since I had to find a way of checking each properties default value regardless of the property type I am using nullable types):
Short? short_val;
EContentType? enum_val;
String string_val;
DateTime? datetime_val;
My code obviously splits the folder name at "_" and then tells for each fragment to which of the above property types it belongs. As soon as I know the corresponding type I am trying to convert to the Type, lets say t, using TypeDescriptor.GetConverter(t).ConvertFromString(info_frag[i]). I hope you now understand why I cannot use another conversion Method.
The code works for all the types mentioned above. My problem is to find a custom DateTime format that can be used on a folder name. All of the formats I know are using colons to separate hours from minutes and seconds.
What I want is a way to convert from a custom DateTime-format to a DateTime-object using TypeDescriptor.GetConverter.ConvertFromString. If that is not possible I either need to find a standard DateTime-format which can be assigned as a folder name without any further conversion or somehow extend the built-in DateTime-formats by my custom format. If it is going to be the latter I do not care about being able to use seconds or minutes. Hours will do the job.
Thanks again for being as patient as you are and helping me out with this one. Feel free to ask as many further questions as you like. I hope you can help me getting this one to work.
Random-I-Am
I have looked into your problem a little and found that the standard DateTimeTypeConverter uses DateTime.Parse internally which doesn't listen to CurrentCulture info at all. Instead you can trick the conversion process with your own TypeConverter!
I do not know how to attach the new typeconverter to the existing DateTime structure, if you even can, so I made a miniture AdvancedDateTime structure .. which is empty. Noone said your custom TypeConvert should even return such a thing! This one returns a regular DateTime. I provided the code below:
public class CustomDateTimeTypeConverter : TypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return DateTime.ParseExact(value.ToString(), "yyyy-MM-dd HH-mm-ss", culture);
}
}
[TypeConverter(typeof(CustomDateTimeTypeConverter))]
struct AdvancedDateTime
{
}
[TestFixture]
public class DateTime
{
[Test]
public void TypeConvert_StrangeFormat_ConvertsWithoutProblem()
{
string datetime = "2011-04-21 23-12-13";
TypeConverter converter = TypeDescriptor.GetConverter( typeof (AdvancedDateTime) );
var convertedFromString = converter.ConvertFromString(datetime);
Assert.AreEqual(new DateTime(2011,4,21, 23,12,13), convertedFromString);
}
}
Try this
string m_strDate = DateTime.Now.ToString("MM/dd/yyyy");
m_strDate = m_strDate.Replace("/", "");
Append this m_strDate to your folder
A sample i used for text file is as follows
strPath += "/FileHeader_" + m_strDate + ".txt";
Check this sample
DateTime dt=new DateTime(1990,5,6);
Console.WriteLine(TypeDescriptor.GetConverter(dt).ConvertTo(dt, typeof(string)));
string myStr="1991-10-10";
Console.WriteLine(TypeDescriptor.GetConverter(dt).ConvertFrom(myStr));
The sample code i written just to display as per you need try as per your requirement
string s = "Folder";
DateTime dt = new DateTime(1990, 5, 6);
string str = TypeDescriptor.GetConverter(dt).ConvertTo(dt, typeof(string)).ToString();
string myStr = "1991-10-10";
string str1 = TypeDescriptor.GetConverter(dt).ConvertFrom(myStr).ToString();
s = s + str1.Replace("/", "").Replace(":", "");
textBox1.Text = s;

C# Decimal.Parse issue with commas

Here's my problem (for en-US):
Decimal.Parse("1,2,3,4") returns 1234, instead of throwing an InvalidFormatException.
Most Windows applications (Excel en-US) do not drop the thousand separators and do not consider that value a decimal number. The same issue happens for other languages (although with different characters).
Are there any other decimal parsing libraries out there that solve this issue?
Thanks!
It's allowing thousands, because the default NumberStyles value used by Decimal.Parse (NumberStyles.Number) includes NumberStyles.AllowThousands.
If you want to disallow the thousands separators, you can just remove that flag, like this:
Decimal.Parse("1,2,3,4", NumberStyles.Number ^ NumberStyles.AllowThousands)
(the above code will throw an InvalidFormatException, which is what you want, right?)
I ended up having to write the code to verify the currency manually. Personally, for a framework that prides itself for having all the globalization stuff built in, it's amazing .NET doesn't have anything to handle this.
My solution is below. It works for all the locales in the framework. It doesn't support Negative numbers, as Orion pointed out below, though. What do you guys think?
public static bool TryParseCurrency(string value, out decimal result)
{
result = 0;
const int maxCount = 100;
if (String.IsNullOrEmpty(value))
return false;
const string decimalNumberPattern = #"^\-?[0-9]{{1,{4}}}(\{0}[0-9]{{{2}}})*(\{0}[0-9]{{{3}}})*(\{1}[0-9]+)*$";
NumberFormatInfo format = CultureInfo.CurrentCulture.NumberFormat;
int secondaryGroupSize = format.CurrencyGroupSizes.Length > 1
? format.CurrencyGroupSizes[1]
: format.CurrencyGroupSizes[0];
var r = new Regex(String.Format(decimalNumberPattern
, format.CurrencyGroupSeparator==" " ? "s" : format.CurrencyGroupSeparator
, format.CurrencyDecimalSeparator
, secondaryGroupSize
, format.CurrencyGroupSizes[0]
, maxCount), RegexOptions.Compiled | RegexOptions.CultureInvariant);
return !r.IsMatch(value.Trim()) ? false : Decimal.TryParse(value, NumberStyles.Any, CultureInfo.CurrentCulture, out result);
}
And here's one test to show it working (nUnit):
[Test]
public void TestCurrencyStrictParsingInAllLocales()
{
var originalCulture = CultureInfo.CurrentCulture;
var cultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
const decimal originalNumber = 12345678.98m;
foreach(var culture in cultures)
{
var stringValue = originalNumber.ToCurrencyWithoutSymbolFormat();
decimal resultNumber = 0;
Assert.IsTrue(DecimalUtils.TryParseCurrency(stringValue, out resultNumber));
Assert.AreEqual(originalNumber, resultNumber);
}
System.Threading.Thread.CurrentThread.CurrentCulture = originalCulture;
}
You might be able to do this in a two-phase process. First you could verify the thousands separator using the information in the CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator and CultureInfo.CurrentCulture.NumberFormat.NumberGroupSizes throwing an exception if it doesn't pass and then pass the number into the Decimal.Parse();
It is a common issue never solved by microsoft.
So, I don't understand why 1,2,3.00 (english culture for example) is valid!
You need to build an algorith to examine group size and return false/exception(like a failed double.parse) if the test is not passed.
I had a similar problem in a mvc application, which build in validator doesn't accept thousands..so i've overwrite it with a custom, using double/decimal/float.parse, but adding a logic to validate group size.
If you want read my solution (it is used for my mvc custom validator, but you can use it to have a better double/decimal/float.parse generic validator) go here
https://stackoverflow.com/a/41916721/3930528

Categories

Resources