diff --git a/UnitsNet.Tests/QuantityIFormattableTests.cs b/UnitsNet.Tests/QuantityIFormattableTests.cs index 059b5b22e2..118c83f32c 100644 --- a/UnitsNet.Tests/QuantityIFormattableTests.cs +++ b/UnitsNet.Tests/QuantityIFormattableTests.cs @@ -65,6 +65,36 @@ public void SFormatEqualsSignificantDigits(string sFormatString, string expected Assert.Equal(expected, length.ToString(sFormatString, NumberFormatInfo.InvariantInfo)); } + /// + /// This verifies that the culture is correctly considered when formatting objects with an explicit culture. + /// + [Fact] + public void FormattingUsesSuppliedLocale() + { + Temperature t = Temperature.FromDegreesCelsius(2012.1234); + CultureInfo c = new CultureInfo("de-CH", false); + string formatted = string.Format(c, "{0:g}", t); + // Let's be very explicit here + Assert.Equal("2" + c.NumberFormat.NumberGroupSeparator + "012" + c.NumberFormat.NumberDecimalSeparator + "12 °C", formatted); + } + + /// + /// This verifies that the culture is correctly considered when using + /// + [Fact] + public void FormatStringWorksWithSuppliedLocale() + { + Temperature t = Temperature.FromDegreesCelsius(2012.1234); + CultureInfo c = new CultureInfo("de-CH", false); + + FormattableString f = $"{t:g}"; + Assert.Equal("2" + c.NumberFormat.NumberGroupSeparator + "012" + c.NumberFormat.NumberDecimalSeparator + "12 °C", f.ToString(c)); + + // This does not work. Looks like a compiler bug to me. + // string f2 = $"{t:g}".ToString(c); + // Assert.Equal("2'012.12 °C", f2.ToString(c)); // Actual value is formatted according to CurrentUiCulture. + } + [Fact] public void UFormatEqualsUnitToString() { diff --git a/UnitsNet/CustomCode/Quantities/Length.extra.cs b/UnitsNet/CustomCode/Quantities/Length.extra.cs index c69d65fabf..622834d60e 100644 --- a/UnitsNet/CustomCode/Quantities/Length.extra.cs +++ b/UnitsNet/CustomCode/Quantities/Length.extra.cs @@ -59,6 +59,27 @@ public static Length ParseFeetInches([NotNull] string str, IFormatProvider? form return result; } + /// + /// Try to parse a string with one or two quantities of the format "<quantity> <unit>". + /// + /// String to parse. Typically in the form: {number} {unit} + /// Format to use when parsing number and unit. Defaults to if null. + /// Allowed number styles + /// Resulting unit quantity if successful. + /// True if successful, otherwise false. + /// + /// Length.Parse("5.5 m", new CultureInfo("en-US")); + /// + private static bool TryParse(string? str, IFormatProvider? provider, NumberStyles allowedNumberStyles, out Length result) + { + return QuantityParser.Default.TryParse( + str, + provider, + From, + allowedNumberStyles, + out result); + } + /// /// Special parsing of feet/inches strings, commonly used. /// 2 feet 4 inches is sometimes denoted as 2′−4″, 2′ 4″, 2′4″, 2 ft 4 in. @@ -80,7 +101,8 @@ public static bool TryParseFeetInches(string? str, out Length result, IFormatPro str = str.Trim(); // This succeeds if only feet or inches are given, not both - if (TryParse(str, formatProvider, out result)) + // Do not allow thousands separator here, since it may be equal to the unit abbreviation ('). + if (TryParse(str, formatProvider, NumberStyles.Float, out result)) return true; var quantityParser = QuantityParser.Default; diff --git a/UnitsNet/CustomCode/QuantityParser.cs b/UnitsNet/CustomCode/QuantityParser.cs index a0054f5779..b190d735fd 100644 --- a/UnitsNet/CustomCode/QuantityParser.cs +++ b/UnitsNet/CustomCode/QuantityParser.cs @@ -69,10 +69,21 @@ internal TQuantity Parse([NotNull] string str, return ParseWithRegex(valueString!, unitString!, fromDelegate, formatProvider); } + internal bool TryParse(string? str, + IFormatProvider? formatProvider, + [NotNull] QuantityFromDelegate fromDelegate, + out TQuantity result) + where TQuantity : struct, IQuantity + where TUnitType : struct, Enum + { + return TryParse(str, formatProvider, fromDelegate, ParseNumberStyles, out result); + } + [SuppressMessage("ReSharper", "UseStringInterpolation")] internal bool TryParse(string? str, IFormatProvider? formatProvider, [NotNull] QuantityFromDelegate fromDelegate, + NumberStyles allowedNumberStyles, out TQuantity result) where TQuantity : struct, IQuantity where TUnitType : struct, Enum @@ -94,21 +105,22 @@ internal bool TryParse(string? str, if (!TryExtractValueAndUnit(regex, str, out var valueString, out var unitString)) return false; - return TryParseWithRegex(valueString, unitString, fromDelegate, formatProvider, out result); + return TryParseWithRegex(valueString, unitString, fromDelegate, formatProvider, allowedNumberStyles, out result); } /// /// Workaround for C# not allowing to pass on 'out' param from type Length to IQuantity, even though the are compatible. /// [SuppressMessage("ReSharper", "UseStringInterpolation")] - internal bool TryParse([NotNull] string str, + internal bool TryParse(string? str, IFormatProvider? formatProvider, [NotNull] QuantityFromDelegate fromDelegate, + NumberStyles allowedNumberStyles, out IQuantity? result) where TQuantity : struct, IQuantity where TUnitType : struct, Enum { - if (TryParse(str, formatProvider, fromDelegate, out TQuantity parsedQuantity)) + if (TryParse(str, formatProvider, fromDelegate, allowedNumberStyles, out TQuantity parsedQuantity)) { result = parsedQuantity; return true; @@ -118,6 +130,19 @@ internal bool TryParse([NotNull] string str, return false; } + /// + /// Workaround for C# not allowing to pass on 'out' param from type Length to IQuantity, even though the are compatible. + /// + internal bool TryParse(string? str, + IFormatProvider? formatProvider, + [NotNull] QuantityFromDelegate fromDelegate, + out IQuantity? result) + where TQuantity : struct, IQuantity + where TUnitType : struct, Enum + { + return TryParse(str, formatProvider, fromDelegate, ParseNumberStyles, out result); + } + internal string CreateRegexPatternForUnit( TUnitType unit, IFormatProvider? formatProvider, @@ -164,13 +189,14 @@ private bool TryParseWithRegex(string? valueString, string? unitString, QuantityFromDelegate fromDelegate, IFormatProvider? formatProvider, + NumberStyles allowedNumberStyles, out TQuantity result) where TQuantity : struct, IQuantity where TUnitType : struct, Enum { result = default; - if (!double.TryParse(valueString, ParseNumberStyles, formatProvider, out var value)) + if (!double.TryParse(valueString, allowedNumberStyles, formatProvider, out var value)) return false; if (!_unitParser.TryParse(unitString, formatProvider, out var parsedUnit))