I have a bug report where double.Parse(input) is throwing the following exception with the input "0.69803923368454":
FormatException: Unknown char: .
System.Double.Parse (System.String s, NumberStyles style, IFormatProvider provider) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Double.cs:209)
System.Double.Parse (System.String s) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Double.cs:180)
After some searching it seems that this issue occurs when the default culture does not support the decimal character . (see float.Parse fails on decimals and commas);
I need to create a unit test which reproduces this issue by forcing a different default culture for the duration of the test. Naturally this must not interfere with any existing unit test. I am using the unit testing framework which is provided with Visual Studio.
Here is what I have tried, but unfortunately this does not cause the reported error to occur:
[TestMethod]
private void DoubleParseWithCultureOverride() {
var restoreCulture = Thread.CurrentThread.CurrentCulture;
var restoreUICulture = Thread.CurrentThread.CurrentUICulture;
try {
// Arrange
Thread.CurrentThread.CurrentCulture = new CultureInfo("ko-KR");
Thread.CurrentThread.CurrentUICulture = new CultureInfo("ko-KR");
// Act
double value = double.Parse("0.69803923368454");
// Assert
Assert.AreEqual(0.69803923368454, value);
}
finally {
Thread.CurrentThread.CurrentCulture = restoreCulture;
Thread.CurrentThread.CurrentUICulture = restoreUICulture;
}
}
I was expecting the above unit test to fail (i.e. become red in the test explorer panel), but it passed. At the moment I am purely attempting to force the error with standard Mono/.NET usage. I intend to replace the "Act" section with application specific logic.
You've just picked a culture which happens to use . as a decimal point:
var culture = new CultureInfo("ko-KR");
Console.WriteLine(culture.NumberFormat.NumberDecimalSeparator); // Prints .
I typically use French (fr-FR) for this - and that does fail with your current code.
Alternatively, you could construct your own CultureInfo specifically for testing, with whatever separator you want.
For testing like this, you might want a simpler way of setting the culture, too. Options:
Write a method taking an action to execute "within" a culture, then call it as:
ExecuteInCulture("fr-Fr", () =>
{
// Parse a double, or whatever
});
Create an IDisposable implementation which sets the culture and restores it on Dispose:
using (CultureHelper.SetCulture("fr-FR"))
{
// Parse a double, or whatever
}
The former approach is probably cleaner - it's not like you've really got a "resource" here.
Related
I am trying to change the format of ToLongDateString to be in my local culture (da-DK).
For now I am doing the following, and if it can be done in a better way, then it will be very appreciated:
CultureInfo cCulture = new CultureInfo("da-DK");
string dateTimeStr = freeSeat.FreeDate.ToLongDateString().ToString(cCulture);
This works as expected on my local development environment, but when I deploy it to AWS lambda, I just get the English culture format. I am pretty sure the issue is that in AWS the C# core code is running on Linux.
I hope someone can give me some input how to solve this.
The ToLongDateString method returns a string formatted according to the current thread culture. Calling ToString method later is useless.
Do it like this:
CultureInfo cCulture = new CultureInfo("da-DK");
string format = cCulture.DateTimeFormat.LongDatePattern;
string dateTimeStr = freeSeat.FreeDate.ToString(format, cCulture);
We are experiencing weird behaviour between a web application and windows service when trying to perform a ToString() on a DateTime value.
See the example below.
DateTime parsedReportDate;
reportDate = DateTime.Now.ToString("yyyyMMdd");
reportDateWithSlash = DateTime.Now.ToString("dd/MM/yyyy");
if (DateTime.TryParse(MyDateValue, out parsedReportDate))
{
reportDate = parsedReportDate.ToString("yyyyMMdd");
reportDateWithSlash = parsedReportDate.ToString("dd/MM/yyyy");
}
--reportDateWithSlash on Web Application: 28/03/2017
--reportDateWithSlash on Windows Service: 28-03-2017
The Windows Service calls the same function as the Web Application does, so why is the formatting different then?
The formatting of dates to strings uses a CultureInfo object to know what format to use.
Each Thread has a Thread.CurrentCulture property.
You can find out what CultureInfo the current Thread is set by getting the current Thread using Thread.CurrentThread and then inspecting it's Thread.CurrentCulture property.
public class Program
{
public static void Main()
{
Console.WriteLine(Thread.CurrentThread.CurrentCulture.Name);
}
}
https://dotnetfiddle.net/dsA3VT
Output: en-US
You can set the CultureInfo for the the Thread, or pass it with each call to ToString.
Setting Thread.CultureInfo
You can set the Thread.CultureInfo using the same property as you use to read it.
Thread.CurrentCulture = new CultureInfo("en-gb");
Unfortunately .Net Fiddle doesn't support changing thread properties.
I didn't know this, but bradbury9 pointed out that since .net 4.6 you can set the CultureInfo.CurrentCulture property as well.
CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("nl-NL");
Unfortunately .Net Fiddle doesn't support changing the culture this way either.
Passing CultureInfo to ToString
'DateTime.ToString' has overloads which can take an IFormatProvider, and CultureInfo impliments IFormatProvider.
DateTime.Now.ToString(new CultureInfo("en-gb"));
https://dotnetfiddle.net/qkS5HF
public class Program
{
public static void Main()
{
var dateTime = DateTime.Now;
Console.WriteLine(Thread.CurrentThread.CurrentCulture.Name);
Console.WriteLine(dateTime.ToString(CultureInfo.InvariantCulture));
Console.WriteLine(dateTime.ToString(new CultureInfo("en-us")));
}
}
Output:
en-US
03/28/2017 09:43:49
3/28/2017 9:43:49 AM
The problem must come from having different cultures. Using the DateTime.ToString (String, IFormatProvider) overload with the CultureInfo.InvariantCulture property should solve the problem:
DateTime.Now.ToString("dd/MM/yyyy", System.Globalization.CultureInfo.InvariantCulture);
it may be what is calling the Windows service is formatting the date. the code certainly is clear enough. Try debugging the windows service by attaching to the running process and see what it generates. If your service consumer is a web app, look at F12 developer tools and see what is getting sent back int he response stream.
In a MVC project, I have a variable set in the web.Config of my project like this:
Then in my code, I get that variable and parse it as decimal:
As you can see, this works fine, the problem is that when I run my code on Google Chrome or Mozilla Firefox, I have diferent results:
I dont undestand why that happens, as not happen in all machines that run the web on Chrome, all I can think is that it seems to be something on the browser config but its a standard instalation, nothing different.
Anyone can point me in the right direction? Or has an idea of what can be causing this behavior?
UPDATE:
Code in text (I don't know why, but ok)
For easy-debugging I have this:
public static decimal ServiceFee
{
get
{
var webConfigVar = ConfigurationManager.AppSettings["ServiceFee"];
decimal webConfigVarDecimal = decimal.Parse(webConfigVar ?? "0");
return webConfigVarDecimal;
}
}
Normally, is like this
public static decimal ServiceFee
{
get
{
return decimal.Parse(ConfigurationManager.AppSettings["ServiceFee"] ?? "0");
}
}
And the Web.Config
<appSettings>
<add key="ServiceFee" value="0.024" />
</appSettings>
UPDATE 2
I know that the code run on the server, but the only difference is the Browser, and its always with those browsers on a few machines.
No matter if the server is running local or on production
Decimal.Parse uses the CultureInfo of the current request request-handling thread, which ASP.NET can (though not by default) set according to the browser's Accept header - so that browsers set to French or German will use their formatting rules (where comma ',' is the radix place, not a dot '.'). This is probably what's happening: your Chrome browser is set to use a different culture.
The fix is to specify CultureInfo.InvariantCulture when calling any Parse or ToString method if it is interacting with human-readable text (e.g. when loading a config file).
This is why static analysis is important (the "Analyze" menu in Visual Studio) - it can point out these bugs.
(My own personal opinion is that the Parse method should be removed from .NET and replaced with explicit ParseFormatted(IFormatProvider, String) and ParseInvariant(String) - but that's just me :)
I note that is inefficient to always call Parse in your property-getter. You should just cache it statically (using the new C# 6.0 read-only property syntax):
using System.Globalization;
public static decimal ServiceFee { get; } =
Decimal.Parse(
ConfigurationManager.AppSettings["ServiceFee"] ?? "0",
NumberStyles.Number,
CultureInfo.InvariantCulture
);
If you do this frequently you might want a reusable method:
public static Decimal GetAppSettingDecimal(String name) {
String textualValue = ConfigurationManager.AppSettings[ name ];
Decimal ret;
return Decimal.TryParse( textualValue, NumberStyles.Number, CultureInfo.InvariantCulture, out ret ) ? ret : 0;
}
public static Decimal ServiceFee { get; } = GetAppSettingDecimal("ServiceFee");
This "problem" will probably be hard to test/reproduce for someone whose Windows version is installed with English as the only language. I'm doing this on a Windows 7 machine installed with "Danish (Denmark)" ("da-DK" culture), .NET version 4.5.
I experience the following surprising behavior. The code:
Console.WriteLine("Now using intalled UI culture");
Thread.CurrentThread.CurrentUICulture = CultureInfo.InstalledUICulture;
Console.WriteLine(new ArgumentNullException().Message);
Console.WriteLine(new ArgumentOutOfRangeException().Message);
Console.WriteLine();
Console.WriteLine("Now using invariant culture");
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
Console.WriteLine(new ArgumentNullException().Message);
Console.WriteLine(new ArgumentOutOfRangeException().Message);
produces the following output:
Now using intalled UI culture
En værdi må ikke være null.
Det angivne argument lå uden for det gyldige værdiområde.
Now using invariant culture
Value cannot be null.
Det angivne argument lå uden for det gyldige værdiområde.
Explanation: In the first section, we see two localized exception messages as expected when the UI thread is Danish. But in the second section we see one English ("invariant") message, and one Danish message, even if the UI culture is not related to Danish anymore.
I tried this with a lot of other mscorlib exception classes as well, and it looks like only System.ArgumentOutOfRangeException does not respect the change of UI culture.
Why is ArgumentOutOfRangeException different than other exceptions in this respect?
(It would be interesting to hear if others (other versions of OS and Framework, and/or other languages) see the same behavior.)
PS! Pasting the following lines into PowerShell (which targets another .NET version) on the same machine gives the opposite problem with ArgumentOutOfRangeException?!
[Threading.Thread]::CurrentThread.CurrentUICulture = [Globalization.CultureInfo]::InstalledUICulture; New-Object ArgumentNullException;
[Threading.Thread]::CurrentThread.CurrentUICulture = [Globalization.CultureInfo]::InstalledUICulture; New-Object ArgumentOutOfRangeException;
[Threading.Thread]::CurrentThread.CurrentUICulture = [Globalization.CultureInfo]::InvariantCulture; New-Object ArgumentNullException;
[Threading.Thread]::CurrentThread.CurrentUICulture = [Globalization.CultureInfo]::InvariantCulture; New-Object ArgumentOutOfRangeException;
(Keep the long lines to make sure both statetments of each line are executed on the same thread by PowerShell.)
If you look into the source code of both exceptions you can see the problem fast. Here is the constructor of ArgumentNullException:
public ArgumentNullException() :
base(Environment.GetResourceString("ArgumentNull_Generic")) {
base.SetErrorCode(-2147467261);
}
As you can see the error message is loaded everytime out of the ressources. While the construcor of ArgumentOutOfRangeException looks like this:
public ArgumentOutOfRangeException() : base(RangeMessage) {
base.SetErrorCode(-2146233086);
}
Where RangeMessage is a private static property:
private static string RangeMessage {
get {
if(_rangeMessage == null) {
_rangeMessage = Environment.GetResourceString(
"Arg_ArgumentOutOfRangeException");
}
return _rangeMessage;
}
}
Here you can see is the problem the error message is cached. So it won't be updated. The only way I see is to access that private static volatile string _rangeMessage; with reflection and set it back to null.
I'm trying this in two application; a console application and a web application.
In the console app when I try Double.Parse("0.5") it gives 0.5 or Double.Parse(".5") gives 0.5
But in the web application Double.Parse("0.5") gives 5.0 and Double.Parse(".5") gives exception
Input string was not in a correct format.
Can any one tell how can resolve the issue in web app?
You should provide culture information otherwise it uses the culture info from the currently running thread. Try this instead:
CultureInfo cultureInfo = CultureInfo.InvariantCulture; // or whatever you prefer
double result = double.Parse(".5", cultureInfo);