Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic unit conversions #588

Merged
merged 29 commits into from
Jul 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
16a4854
Allowing custom unit conversion functions to be specified
tmilnthorp Jan 24, 2019
fa2a2c1
Registering default base->unit and unit->base conversion functions in…
tmilnthorp Jan 25, 2019
a9beb6e
Whitespace consistency
tmilnthorp Jan 25, 2019
b4a4277
Add some overloads and allowing source/target quantities to be different
tmilnthorp Jan 25, 2019
f552e8b
Inline initialization and style update for field
tmilnthorp Jan 28, 2019
de56c47
PR feedback updates
tmilnthorp Jan 28, 2019
c5b039a
Merge branch 'master' into UnitConversions
tmilnthorp Jan 28, 2019
66c1eab
Merge branch 'master' into UnitConversions
tmilnthorp Feb 19, 2019
ad313fa
Regen WRC
tmilnthorp Feb 19, 2019
c9e6f46
Merge remote-tracking branch 'origin/master' into tmilnthorp-UnitConv…
angularsen Feb 23, 2019
beedad4
Use IQuantity.Value in test case
angularsen Feb 23, 2019
bc3a808
Rename typeparams, remove empty ctor
angularsen Feb 23, 2019
619de36
Micro optimization of lookup of IQuantity type
angularsen Feb 23, 2019
6a2befd
UnitConverter: Move static field initialization to static ctor
angularsen Feb 23, 2019
1dc6c27
Add typed conversion function for same quantity conversion functions
angularsen Feb 23, 2019
b27f3d5
Use typed conversions in RegisterDefaultConversions
angularsen Feb 23, 2019
9f74054
Merge branch 'master' into UnitConversions
tmilnthorp Mar 1, 2019
5c49f66
Header update
tmilnthorp Mar 1, 2019
050dc49
Merge remote-tracking branch 'origin/master' into UnitConversions
angularsen Apr 22, 2019
8cc352e
Add xmldoc and minor renaming
angularsen Apr 22, 2019
16dca81
Add test cases for Type parameters
angularsen Apr 22, 2019
995be8c
QuantityParser: Make field readonly
angularsen Apr 22, 2019
e1b1176
Merge remote-tracking branch 'origin/master' into HEAD
angularsen Apr 22, 2019
b60a940
UnitParser: Add test parsing HowMuch custom unit
angularsen Apr 22, 2019
605386d
UnitConverter: Echo quantity when converting to same unit, test custo…
angularsen Apr 22, 2019
3aca89c
Merge remote-tracking branch 'origin/master' into UnitConversions
angularsen May 1, 2019
dff33f0
Port codegen scripts to C#
angularsen May 1, 2019
04f1c2f
Merge remote-tracking branch 'origin/master' into UnitConversions
angularsen Jul 17, 2019
53c43cf
Regen code
angularsen Jul 17, 2019
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
11 changes: 11 additions & 0 deletions CodeGen/Generators/UnitsNetGen/QuantityGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,17 @@ IQuantity IQuantity.ToUnit(Enum unit)
}}
}}

/// <summary>
/// Converts the current value + unit to the base unit.
/// This is typically the first step in converting from one unit to another.
/// </summary>
/// <returns>The value in the base unit representation.</returns>
internal {_quantity.Name} ToBaseUnit()
{{
var baseUnitValue = GetValueInBaseUnit();
return new {_quantity.Name}(baseUnitValue, BaseUnit);
}}

private {_valueType} GetValueAs({_unitEnumName} unit)
{{
if(Unit == unit)
Expand Down
56 changes: 56 additions & 0 deletions CodeGen/Generators/UnitsNetGen/UnitConverterGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Licensed under MIT No Attribution, see LICENSE file at the root.
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.

using CodeGen.Helpers;
using CodeGen.JsonTypes;

namespace CodeGen.Generators.UnitsNetGen
{
internal class UnitConverterGenerator : GeneratorBase
{
private readonly Quantity[] _quantities;

public UnitConverterGenerator(Quantity[] quantities)
{
_quantities = quantities;
}

public override string Generate()
{
Writer.WL(GeneratedFileHeader);
Writer.WL($@"
using UnitsNet.Units;

// ReSharper disable RedundantCommaInArrayInitializer
// ReSharper disable once CheckNamespace

namespace UnitsNet
{{
public sealed partial class UnitConverter
{{
/// <summary>
/// Registers the default conversion functions in the given <see cref=""UnitConverter""/> instance.
/// </summary>
/// <param name=""unitConverter"">The <see cref=""UnitConverter""/> to register the default conversion functions in.</param>
public static void RegisterDefaultConversions(UnitConverter unitConverter)
{{");
foreach (Quantity quantity in _quantities)
foreach (Unit unit in quantity.Units)
{
Writer.WL(quantity.BaseUnit == unit.SingularName
? $@"
unitConverter.SetConversionFunction<{quantity.Name}>({quantity.Name}.BaseUnit, {quantity.Name}.BaseUnit, q => q);"
: $@"
unitConverter.SetConversionFunction<{quantity.Name}>({quantity.Name}.BaseUnit, {quantity.Name}Unit.{unit.SingularName}, q => q.ToUnit({quantity.Name}Unit.{unit.SingularName}));
unitConverter.SetConversionFunction<{quantity.Name}>({quantity.Name}Unit.{unit.SingularName}, {quantity.Name}.BaseUnit, q => q.ToBaseUnit());");
}

Writer.WL($@"
}}
}}
}}");

return Writer.ToString();
}
}
}
8 changes: 8 additions & 0 deletions CodeGen/Generators/UnitsNetGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public static void Generate(string rootDir, Quantity[] quantities)
GenerateUnitAbbreviationsCache(quantities, $"{outputDir}/UnitAbbreviationsCache.g.cs");
GenerateQuantityType(quantities, $"{outputDir}/QuantityType.g.cs");
GenerateStaticQuantity(quantities, $"{outputDir}/Quantity.g.cs");
GenerateUnitConverter(quantities, $"{outputDir}/UnitConverter.g.cs");

var unitCount = quantities.SelectMany(q => q.Units).Count();
Log.Information("");
Expand Down Expand Up @@ -119,5 +120,12 @@ private static void GenerateStaticQuantity(Quantity[] quantities, string filePat
File.WriteAllText(filePath, content, Encoding.UTF8);
Log.Information("Quantity.g.cs: ".PadRight(AlignPad) + "(OK)");
}

private static void GenerateUnitConverter(Quantity[] quantities, string filePath)
{
var content = new UnitConverterGenerator(quantities).Generate();
File.WriteAllText(filePath, content, Encoding.UTF8);
Log.Information("UnitConverter.g.cs: ".PadRight(AlignPad) + "(OK)");
}
}
}
103 changes: 103 additions & 0 deletions UnitsNet.Tests/UnitConverterTest.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,115 @@
// Licensed under MIT No Attribution, see LICENSE file at the root.
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.

using System;
using UnitsNet.Tests.CustomQuantities;
using UnitsNet.Units;
using Xunit;

namespace UnitsNet.Tests
{
public class UnitConverterTest
{
[Fact]
public void CustomConversionWithSameQuantityType()
{
Length ConversionFunction(Length from) => Length.FromInches(18);

var unitConverter = new UnitConverter();
unitConverter.SetConversionFunction<Length>(LengthUnit.Meter, LengthUnit.Inch, ConversionFunction);

var foundConversionFunction = unitConverter.GetConversionFunction<Length>(LengthUnit.Meter, LengthUnit.Inch);
var converted = foundConversionFunction(Length.FromMeters(1.0));

Assert.Equal(Length.FromInches(18), converted);
}

[Fact]
public void CustomConversionWithSameQuantityTypeByTypeParam()
{
Length ConversionFunction(Length from) => Length.FromInches(18);

var unitConverter = new UnitConverter();
unitConverter.SetConversionFunction(LengthUnit.Meter, LengthUnit.Inch, (ConversionFunction<Length>) ConversionFunction);

var foundConversionFunction = unitConverter.GetConversionFunction(typeof(Length), LengthUnit.Meter, typeof(Length), LengthUnit.Inch);
var converted = foundConversionFunction(Length.FromMeters(1.0));

Assert.Equal(Length.FromInches(18), converted);
}

[Fact]
public void CustomConversionWithDifferentQuantityTypes()
{
IQuantity ConversionFunction(IQuantity from) => Length.FromInches(18);

var unitConverter = new UnitConverter();
unitConverter.SetConversionFunction<Mass, Length>(MassUnit.Grain, LengthUnit.Inch, ConversionFunction);

var foundConversionFunction = unitConverter.GetConversionFunction<Mass, Length>(MassUnit.Grain, LengthUnit.Inch);
var converted = foundConversionFunction(Mass.FromGrains(100));

Assert.Equal(Length.FromInches(18), converted);
}

[Fact]
public void CustomConversionWithDifferentQuantityTypesByTypeParam()
{
IQuantity ConversionFunction(IQuantity from) => Length.FromInches(18);

var unitConverter = new UnitConverter();
unitConverter.SetConversionFunction<Mass, Length>(MassUnit.Grain, LengthUnit.Inch, ConversionFunction);

var foundConversionFunction = unitConverter.GetConversionFunction(typeof(Mass), MassUnit.Grain, typeof(Length), LengthUnit.Inch);
var converted = foundConversionFunction(Mass.FromGrains(100));

Assert.Equal(Length.FromInches(18), converted);
}

[Fact]
public void TryCustomConversionForOilBarrelsToUsGallons()
{
Volume ConversionFunction(Volume from) => Volume.FromUsGallons(from.Value * 42);

var unitConverter = new UnitConverter();
unitConverter.SetConversionFunction<Volume>(VolumeUnit.OilBarrel, VolumeUnit.UsGallon, ConversionFunction);

var foundConversionFunction = unitConverter.GetConversionFunction<Volume>(VolumeUnit.OilBarrel, VolumeUnit.UsGallon);
var converted = foundConversionFunction(Volume.FromOilBarrels(1));

Assert.Equal(Volume.FromUsGallons(42), converted);
}

[Fact]
public void ConversionToSameUnit_ReturnsSameQuantity()
{
var unitConverter = new UnitConverter();

var foundConversionFunction = unitConverter.GetConversionFunction<HowMuch>(HowMuchUnit.ATon, HowMuchUnit.ATon);
var converted = foundConversionFunction(new HowMuch(39, HowMuchUnit.Some)); // Intentionally pass the wrong unit here, to test that the exact same quantity is returned

Assert.Equal(39, converted.Value);
Assert.Equal(HowMuchUnit.Some, converted.Unit);
}

[Theory]
[InlineData(1, HowMuchUnit.Some, HowMuchUnit.Some, 1)]
[InlineData(1, HowMuchUnit.Some, HowMuchUnit.ATon, 2)]
[InlineData(1, HowMuchUnit.Some, HowMuchUnit.AShitTon, 10)]
public void ConversionForUnitsOfCustomQuantity(double fromValue, Enum fromUnit, Enum toUnit, double expectedValue)
{
// Intentionally don't map conversion Some->Some, it is not necessary
var unitConverter = new UnitConverter();
unitConverter.SetConversionFunction<HowMuch>(HowMuchUnit.Some, HowMuchUnit.ATon, x => new HowMuch(x.Value * 2, HowMuchUnit.ATon));
unitConverter.SetConversionFunction<HowMuch>(HowMuchUnit.Some, HowMuchUnit.AShitTon, x => new HowMuch(x.Value * 10, HowMuchUnit.AShitTon));

var foundConversionFunction = unitConverter.GetConversionFunction<HowMuch>(fromUnit, toUnit);
var converted = foundConversionFunction(new HowMuch(fromValue, fromUnit));

Assert.Equal(expectedValue, converted.Value);
Assert.Equal(toUnit, converted.Unit);
}

[Theory]
[InlineData(0, 0, "length", "meter", "centimeter")]
[InlineData(0, 0, "Length", "Meter", "Centimeter")]
Expand Down
13 changes: 13 additions & 0 deletions UnitsNet.Tests/UnitParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Globalization;
using UnitsNet.Tests.CustomQuantities;
using UnitsNet.Units;
using Xunit;

Expand Down Expand Up @@ -124,5 +125,17 @@ public void ParseMassUnit_GivenCulture(string str, string cultureName, Enum expe
{
Assert.Equal(expectedUnit, UnitParser.Default.Parse<MassUnit>(str, new CultureInfo(cultureName)));
}

[Fact]
public void Parse_MappedCustomUnit()
{
var unitAbbreviationsCache = new UnitAbbreviationsCache();
unitAbbreviationsCache.MapUnitToAbbreviation(HowMuchUnit.Some, "fooh");
var unitParser = new UnitParser(unitAbbreviationsCache);

var parsedUnit = unitParser.Parse<HowMuchUnit>("fooh");

Assert.Equal(HowMuchUnit.Some, parsedUnit);
}
}
}
2 changes: 1 addition & 1 deletion UnitsNet/CustomCode/QuantityParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal class QuantityParser
private const NumberStyles ParseNumberStyles = NumberStyles.Number | NumberStyles.Float | NumberStyles.AllowExponent;

private readonly UnitAbbreviationsCache _unitAbbreviationsCache;
private UnitParser _unitParser;
private readonly UnitParser _unitParser;

public static QuantityParser Default { get; }

Expand Down
11 changes: 11 additions & 0 deletions UnitsNet/GeneratedCode/Quantities/Acceleration.g.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions UnitsNet/GeneratedCode/Quantities/AmountOfSubstance.g.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions UnitsNet/GeneratedCode/Quantities/AmplitudeRatio.g.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions UnitsNet/GeneratedCode/Quantities/Angle.g.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions UnitsNet/GeneratedCode/Quantities/ApparentEnergy.g.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions UnitsNet/GeneratedCode/Quantities/ApparentPower.g.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions UnitsNet/GeneratedCode/Quantities/Area.g.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions UnitsNet/GeneratedCode/Quantities/AreaDensity.g.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading