diff --git a/UnitsNet.Tests/QuantityFormatterTests.cs b/UnitsNet.Tests/QuantityFormatterTests.cs new file mode 100644 index 0000000000..9a2a948ca1 --- /dev/null +++ b/UnitsNet.Tests/QuantityFormatterTests.cs @@ -0,0 +1,139 @@ +// Licensed under MIT No Attribution, see LICENSE file at the root. +// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. + +using Xunit; + +namespace UnitsNet.Tests +{ + public class QuantityFormatterTests + { + [Theory] + [InlineData("C")] + [InlineData("C0")] + [InlineData("C1")] + [InlineData("C2")] + [InlineData("C3")] + [InlineData("C4")] + [InlineData("C5")] + [InlineData("C6")] + [InlineData("c")] + [InlineData("c0")] + [InlineData("c1")] + [InlineData("c2")] + [InlineData("c3")] + [InlineData("c4")] + [InlineData("c5")] + [InlineData("c6")] + [InlineData("E")] + [InlineData("E0")] + [InlineData("E1")] + [InlineData("E2")] + [InlineData("E3")] + [InlineData("E4")] + [InlineData("E5")] + [InlineData("E6")] + [InlineData("e")] + [InlineData("e0")] + [InlineData("e1")] + [InlineData("e2")] + [InlineData("e3")] + [InlineData("e4")] + [InlineData("e5")] + [InlineData("e6")] + [InlineData("F")] + [InlineData("F0")] + [InlineData("F1")] + [InlineData("F2")] + [InlineData("F3")] + [InlineData("F4")] + [InlineData("F5")] + [InlineData("F6")] + [InlineData("f")] + [InlineData("f0")] + [InlineData("f1")] + [InlineData("f2")] + [InlineData("f3")] + [InlineData("f4")] + [InlineData("f5")] + [InlineData("f6")] + [InlineData("N")] + [InlineData("N0")] + [InlineData("N1")] + [InlineData("N2")] + [InlineData("N3")] + [InlineData("N4")] + [InlineData("N5")] + [InlineData("N6")] + [InlineData("n")] + [InlineData("n0")] + [InlineData("n1")] + [InlineData("n2")] + [InlineData("n3")] + [InlineData("n4")] + [InlineData("n5")] + [InlineData("n6")] + [InlineData("P")] + [InlineData("P0")] + [InlineData("P1")] + [InlineData("P2")] + [InlineData("P3")] + [InlineData("P4")] + [InlineData("P5")] + [InlineData("P6")] + [InlineData("p")] + [InlineData("p0")] + [InlineData("p1")] + [InlineData("p2")] + [InlineData("p3")] + [InlineData("p4")] + [InlineData("p5")] + [InlineData("p6")] + [InlineData("R")] + [InlineData("R0")] + [InlineData("R1")] + [InlineData("R2")] + [InlineData("R3")] + [InlineData("R4")] + [InlineData("R5")] + [InlineData("R6")] + [InlineData("r")] + [InlineData("r0")] + [InlineData("r1")] + [InlineData("r2")] + [InlineData("r3")] + [InlineData("r4")] + [InlineData("r5")] + [InlineData("r6")] + public static void StandardNumericFormatStrings_Equals_ValueWithFormatStringAndAbbreviation(string format) + { + var length = Length.FromMeters(123456789.987654321); + + var expected = string.Format($"{{0:{format}}} {{1:a}}", length.Value, length); + Assert.Equal(expected, QuantityFormatter.Format(length, format)); + } + + [Theory] + [InlineData("000")] + [InlineData("0.00")] + [InlineData("#####")] + [InlineData("#.##")] + [InlineData("##,#")] + [InlineData("#,#,,")] + [InlineData("%#0.00")] + [InlineData("##.0 %")] + [InlineData("#0.00‰")] + [InlineData("#0.0e0")] + [InlineData("0.0##e+00")] + [InlineData("0.0e+00")] + [InlineData(@"\###00\#")] + [InlineData("#0.0#;(#0.0#);-\0-")] + [InlineData("#0.0#;(#0.0#)")] + public static void CustomNumericFormatStrings_Equals_ValueWithFormatStringAndAbbreviation(string format) + { + var length = Length.FromMeters(123456789.987654321); + + var expected = string.Format($"{{0:{format}}} {{1:a}}", length.Value, length); + Assert.Equal(expected, QuantityFormatter.Format(length, format)); + } + } +} diff --git a/UnitsNet/QuantityFormatter.cs b/UnitsNet/QuantityFormatter.cs index a949f3b3d9..3a774b4427 100644 --- a/UnitsNet/QuantityFormatter.cs +++ b/UnitsNet/QuantityFormatter.cs @@ -14,7 +14,66 @@ namespace UnitsNet public class QuantityFormatter { /// - /// Formats the given quantity using the given format string and format provider. + /// The available UnitsNet custom format specifiers. + /// + private static readonly char[] UnitsNetFormatSpecifiers = { 'A', 'a', 'G', 'g', 'Q', 'q', 'S', 's', 'U', 'u', 'V', 'v' }; + + /// + /// Formats a quantity using the given format string and format provider. + /// + /// The quantity's unit type, for example . + /// The quantity to format. + /// The format string. + /// + /// The valid format strings are as follows: + /// + /// + /// A standard numeric format string. + /// Any of the standard numeric format for except for "G" or "g". + /// "C" or "c", "E" or "e", "F" or "f", "N" or "n", "P" or "p", "R" or "r" are all accepted. + /// + /// + /// + /// "G" or "g". + /// The value with 2 significant digits after the radix followed by the unit abbreviation, such as "1.23 m". + /// + /// + /// "A" or "a". + /// The default unit abbreviation for , such as "m". + /// + /// + /// "A0", "A1", ..., "An" or "a0", "a1", ..., "an". + /// The n-th unit abbreviation for . "a0" is the same as "a". + /// A will be thrown if the requested abbreviation index does not exist. + /// + /// + /// "V" or "v". + /// The string representation of using the default ToString method. + /// + /// + /// "U" or "u". + /// The enum name of , such as "Meter". + /// + /// + /// "Q" or "q". + /// The quantity name, such as "Length". + /// + /// + /// "S1", "S2", ..., "Sn" or "s1", "s2", ..., "sn". + /// The value with n significant digits after the radix followed by the unit abbreviation. For example, + /// "s4" would return "1.2345 m" if is 1.2345678. Trailing zeros are omitted. + /// + /// + /// + /// The string representation. + public static string Format(IQuantity quantity, string format) + where TUnitType : Enum + { + return Format(quantity, format, CultureInfo.CurrentUICulture); + } + + /// + /// Formats a quantity using the given format string and format provider. /// /// The quantity's unit type, for example . /// The quantity to format. @@ -23,57 +82,108 @@ public class QuantityFormatter /// if null. /// /// The valid format strings are as follows: - /// "g": The value with 2 significant digits after the radix followed by the unit abbreviation, such as "1.23 m". - /// "a": The default unit abbreviation for , such as "m". - /// "a0", "a1", ..., "aN": The Nth unit abbreviation for . "a0" is the same as "a". - /// A will be thrown if the requested abbreviation index does not exist. - /// "v": String representation of . - /// "u": The enum name of , such as "Meter". - /// "q": The quantity name, such as "Length". - /// "s1", "s2", ..., "sN": The value with N significant digits after the radix followed by the unit abbreviation. For example, - /// "s4" would return "1.2345 m" if is 1.2345678. Trailing zeros are omitted. + /// + /// + /// A standard numeric format string. + /// Any of the standard numeric format for except for "G" or "g". + /// "C" or "c", "E" or "e", "F" or "f", "N" or "n", "P" or "p", "R" or "r" are all accepted. + /// + /// + /// + /// "G" or "g". + /// The value with 2 significant digits after the radix followed by the unit abbreviation, such as "1.23 m". + /// + /// + /// "A" or "a". + /// The default unit abbreviation for , such as "m". + /// + /// + /// "A0", "A1", ..., "An" or "a0", "a1", ..., "an". + /// The n-th unit abbreviation for . "a0" is the same as "a". + /// A will be thrown if the requested abbreviation index does not exist. + /// + /// + /// "V" or "v". + /// The string representation of using the default ToString method. + /// + /// + /// "U" or "u". + /// The enum name of , such as "Meter". + /// + /// + /// "Q" or "q". + /// The quantity name, such as "Length". + /// + /// + /// "S1", "S2", ..., "Sn" or "s1", "s2", ..., "sn". + /// The value with n significant digits after the radix followed by the unit abbreviation. For example, + /// "s4" would return "1.2345 m" if is 1.2345678. Trailing zeros are omitted. + /// + /// /// /// The string representation. public static string Format(IQuantity quantity, string format, IFormatProvider? formatProvider) where TUnitType : Enum { - formatProvider = formatProvider ?? CultureInfo.CurrentUICulture; + formatProvider ??= CultureInfo.CurrentUICulture; - var number = 0; - var formatString = format; + if(string.IsNullOrWhiteSpace(format)) + format = "g"; - if(string.IsNullOrEmpty(formatString)) - formatString = "g"; + char formatSpecifier = format[0]; - if(formatString.StartsWith("a") || formatString.StartsWith("s")) + if(UnitsNetFormatSpecifiers.Any(unitsNetFormatSpecifier => unitsNetFormatSpecifier == formatSpecifier)) { - if(formatString.Length > 1 && !int.TryParse(formatString.Substring(1), out number)) - throw new FormatException($"The {format} format string is not supported."); + // UnitsNet custom format string - formatString = formatString.Substring(0, 1); - } + int precisionSpecifier = 0; - switch(formatString) - { - case "g": - return ToStringWithSignificantDigitsAfterRadix(quantity, formatProvider, 2); - case "a": - var abbreviations = UnitAbbreviationsCache.Default.GetUnitAbbreviations(quantity.Unit, formatProvider); + switch(formatSpecifier) + { + case 'A': + case 'a': + case 'S': + case 's': + if(format.Length > 1 && !int.TryParse(format.Substring(1), out precisionSpecifier)) + throw new FormatException($"The {format} format string is not supported."); + break; + } + + switch(formatSpecifier) + { + case 'G': + case 'g': + return ToStringWithSignificantDigitsAfterRadix(quantity, formatProvider, 2); + case 'A': + case 'a': + var abbreviations = UnitAbbreviationsCache.Default.GetUnitAbbreviations(quantity.Unit, formatProvider); + + if(precisionSpecifier >= abbreviations.Length) + throw new FormatException($"The {format} format string is invalid because the abbreviation index does not exist."); - if(number >= abbreviations.Length) - throw new FormatException($"The {format} format string is invalid because the abbreviation index does not exist."); + return abbreviations[precisionSpecifier]; + case 'V': + case 'v': + return quantity.Value.ToString(formatProvider); + case 'U': + case 'u': + return quantity.Unit.ToString(); + case 'Q': + case 'q': + return quantity.QuantityInfo.Name; + case 'S': + case 's': + return ToStringWithSignificantDigitsAfterRadix(quantity, formatProvider, precisionSpecifier); + default: + throw new FormatException($"The {format} format string is not supported."); + } + } + else + { + // Anything else is a standard numeric format string with default unit abbreviation postfix. - return abbreviations[number]; - case "v": - return quantity.Value.ToString(formatProvider); - case "u": - return quantity.Unit.ToString(); - case "q": - return quantity.QuantityInfo.Name; - case "s": - return ToStringWithSignificantDigitsAfterRadix(quantity, formatProvider, number); - default: - throw new FormatException($"The {format} format string is not supported."); + var abbreviations = UnitAbbreviationsCache.Default.GetUnitAbbreviations(quantity.Unit, formatProvider); + return string.Format(formatProvider, $"{{0:{format}}} {{1}}", quantity.Value, abbreviations.First()); } }