Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions UnitsNet.Tests/UnitConverterTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace UnitsNet.Tests
public class UnitConverterTest
{
[Theory]
[InlineData(0, 0, "length", "meter", "centimeter")]
[InlineData(0, 0, "Length", "Meter", "Centimeter")]
[InlineData(100, 1, "Length", "Meter", "Centimeter")]
[InlineData(1, 1000, "Mass", "Gram", "Kilogram")]
Expand All @@ -35,6 +36,18 @@ public void ConvertByName_ConvertsTheValueToGivenUnit(double expectedValue, doub
Assert.Equal(expectedValue, UnitConverter.ConvertByName(inputValue, quantityTypeName, fromUnit, toUnit));
}

[Fact]
public void ConvertByName_QuantityCaseInsensitive()
{
Assert.Equal(0, UnitConverter.ConvertByName(0, "length", "Meter", "Centimeter"));
}

[Fact]
public void ConvertByName_UnitTypeCaseInsensitive()
{
Assert.Equal(0, UnitConverter.ConvertByName(0, "Length", "meter", "Centimeter"));
}

[Theory]
[InlineData(1, "UnknownQuantity", "Meter", "Centimeter")]
public void ConvertByName_ThrowsQuantityNotFoundExceptionOnUnknownQuantity(double inputValue, string quantityTypeName, string fromUnit, string toUnit)
Expand Down
24 changes: 24 additions & 0 deletions UnitsNet.Tests/UnitParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ public void Parse_ReturnsUnitMappedByCustomAbbreviation(string customAbbreviatio
Assert.Equal(expected, actual);
}

[Fact]
public void Parse_AbbreviationCaseInsensitive_Lowercase_years()
{
var abbreviation = "years";
var expected = DurationUnit.Year365;
var parser = UnitParser.Default;

var actual = parser.Parse<DurationUnit>(abbreviation);

Assert.Equal(expected, actual);
}

[Fact]
public void Parse_AbbreviationCaseInsensitive_Uppercase_Years()
{
var abbreviation = "Years";
var expected = DurationUnit.Year365;
var parser = UnitParser.Default;

var actual = parser.Parse<DurationUnit>(abbreviation);

Assert.Equal(expected, actual);
}

[Fact]
public void Parse_UnknownAbbreviationThrowsUnitNotFoundException()
{
Expand Down
12 changes: 7 additions & 5 deletions UnitsNet/CustomCode/UnitValueAbbreviationLookup.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com).
// Copyright (c) 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com).
// https://github.com/angularsen/UnitsNet
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
Expand Down Expand Up @@ -63,19 +63,21 @@ internal List<string> GetAbbreviationsForUnit(int unit)

internal List<int> GetUnitsForAbbreviation(string abbreviation)
{
if(!abbreviationToUnitMap.TryGetValue(abbreviation, out var units))
abbreviationToUnitMap[abbreviation] = units = new List<int>();
var lowerCaseAbbreviation = abbreviation.ToLower();
if(!abbreviationToUnitMap.TryGetValue(lowerCaseAbbreviation, out var units))
abbreviationToUnitMap[lowerCaseAbbreviation] = units = new List<int>();

return units.Distinct().ToList();
}

internal void Add(int unit, string abbreviation, bool setAsDefault = false)
{
var lowerCaseAbbreviation = abbreviation.ToLower();
if(!unitToAbbreviationMap.TryGetValue(unit, out var abbreviationsForUnit))
abbreviationsForUnit = unitToAbbreviationMap[unit] = new List<string>();

if(!abbreviationToUnitMap.TryGetValue(abbreviation, out var unitsForAbbreviation))
abbreviationToUnitMap[abbreviation] = unitsForAbbreviation = new List<int>();
if(!abbreviationToUnitMap.TryGetValue(lowerCaseAbbreviation, out var unitsForAbbreviation))
abbreviationToUnitMap[lowerCaseAbbreviation] = unitsForAbbreviation = new List<int>();

abbreviationsForUnit.Remove(abbreviation);
unitsForAbbreviation.Remove(unit);
Expand Down
9 changes: 9 additions & 0 deletions UnitsNet/InternalHelpers/ReflectionBridgeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ internal static bool IsEnum(this Type type)
#endif
}

internal static bool IsClass(this Type type)
{
#if !(NET40 || NET35 || NET20 || SILVERLIGHT)
return type.GetTypeInfo().IsClass;
#else
return type.IsClass;
#endif
}

internal static bool IsValueType(this Type type)
{
#if !(NET40 || NET35 || NET20 || SILVERLIGHT)
Expand Down
39 changes: 20 additions & 19 deletions UnitsNet/UnitConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,18 @@ namespace UnitsNet
/// </summary>
public static class UnitConverter
{
private static readonly string QuantityNamespace = typeof(Length).Namespace;
private static readonly string UnitTypeNamespace = typeof(LengthUnit).Namespace;
private static readonly Assembly UnitsNetAssembly = typeof(Length).GetAssembly();

private static readonly Type[] QuantityTypes = UnitsNetAssembly.GetTypes()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These include a lot more types than just the quantity structs and the unit enums.
I'm adding a fix.

.Where(typeof(IQuantity).IsAssignableFrom)
.Where(x => x.IsClass() || x.IsValueType()) // Future-proofing: we are discussing changing quantities from struct to class
.ToArray();

private static readonly Type[] UnitTypes = UnitsNetAssembly.GetTypes()
.Where(x => x.Namespace == UnitTypeNamespace && x.IsEnum() && x.Name.EndsWith("Unit"))
.ToArray();

/// <summary>
/// Convert between any two quantity units by their names, such as converting a "Length" of N "Meter" to "Centimeter".
/// This is particularly useful for creating things like a generated unit conversion UI,
Expand Down Expand Up @@ -72,7 +80,7 @@ public static class UnitConverter
/// <returns>Output value as the result of converting to <paramref name="toUnit" />.</returns>
/// <exception cref="QuantityNotFoundException">No quantities were found that match <paramref name="quantityName" />.</exception>
/// <exception cref="UnitNotFoundException">No units match the abbreviation.</exception>
/// <exception cref="AmbiguousUnitParseException">More than one unit matches the abbrevation.</exception>
/// <exception cref="AmbiguousUnitParseException">More than one unit matches the abbreviation.</exception>
public static double ConvertByName(FromValue fromValue, string quantityName, string fromUnit, string toUnit)
{
if(!TryGetQuantityType(quantityName, out var quantityType))
Expand Down Expand Up @@ -219,7 +227,7 @@ public static double ConvertByAbbreviation(FromValue fromValue, string quantityN
/// <returns>Output value as the result of converting to <paramref name="toUnitAbbrev" />.</returns>
/// <exception cref="QuantityNotFoundException">No quantity types match the <paramref name="quantityName"/>.</exception>
/// <exception cref="UnitNotFoundException">No unit types match the prefix of <paramref name="quantityName"/> or no units are mapped to the abbreviation.</exception>
/// <exception cref="AmbiguousUnitParseException">More than one unit matches the abbrevation.</exception>
/// <exception cref="AmbiguousUnitParseException">More than one unit matches the abbreviation.</exception>
public static double ConvertByAbbreviation(FromValue fromValue, string quantityName, string fromUnitAbbrev, string toUnitAbbrev, string culture)
{
if(!TryGetQuantityType(quantityName, out var quantityType))
Expand Down Expand Up @@ -384,37 +392,30 @@ private static bool HasParameterTypes(MethodInfo methodInfo, params Type[] expec
private static bool TryParseUnit(Type unitType, string unitName, out object unitValue)
{
unitValue = null;

if(!Enum.IsDefined(unitType, unitName))
var eNames = Enum.GetNames(unitType);
unitName = eNames.FirstOrDefault(x => x.Equals(unitName, StringComparison.OrdinalIgnoreCase));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably use Enum.TryParse which includes a bool parameter to specify ignoring case. See here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would also prefer this method, but I can't use that here without using reflection, which I was trying to avoid. The try parse method you linked takes a generic type argument, which means that the type has to be known at compile type. We have a Type parameter known only at run time. Is there another TryParse that you know of that takes the enum type "Type" as parameter?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enum.TryParse<T>(string, bool, out T) exists, but I suspect since we target .NET 4.0 is why we can't use it.

if(unitName == null)
return false;

unitValue = Enum.Parse(unitType, unitName);
if(unitValue == null)
return false;

return true;
}

private static bool TryGetUnitType(string quantityName, out Type unitType)
{
string unitTypeName = $"{UnitTypeNamespace}.{quantityName}Unit";
var unitTypeName = quantityName + "Unit"; // ex. LengthUnit

unitType = UnitsNetAssembly.GetType(unitTypeName); // ex: UnitsNet.Units.LengthUnit enum
if(unitType == null)
return false;
unitType = UnitTypes.FirstOrDefault(x =>
x.Name.Equals(unitTypeName, StringComparison.OrdinalIgnoreCase));

return true;
return unitType != null;
}

private static bool TryGetQuantityType(string quantityName, out Type quantityType)
{
string quantityTypeName = $"{QuantityNamespace}.{quantityName}";
quantityType = QuantityTypes.FirstOrDefault(x => x.Name.Equals(quantityName, StringComparison.OrdinalIgnoreCase));

quantityType = UnitsNetAssembly.GetType(quantityTypeName); // ex: UnitsNet.Length struct
if(quantityType == null)
return false;

return true;
return quantityType != null;
}
}
}