diff --git a/docs/index.md b/docs/index.md
index 1559107..63ff4d6 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -8,7 +8,7 @@ _layout: landing
Primitively is a powerful C# source generator that transforms primitive identifiers and value objects into highly performant, customisable, read-only struct values that support ASP.NET model binding and validation (including FluentValidation), Open API standards, JSON and MongoDB BSON serialization, with zero or minimal configuration.
-The forthcoming 1.4 release supports the following platforms:
+The 1.4.x release supports the following platforms:
- .NET 8
- .NET 7
@@ -17,6 +17,4 @@ The forthcoming 1.4 release supports the following platforms:
- .NET Core 3.1¹
- .NET Standard 2.0¹
-Prior to the 1.4 release. Primitively supported .NET 6 only.
-
-¹ It is possible (but not recommended) to use Primitively on class libraries that target platforms that preceed .NET 6. Extra configuration is required for this to work because C# 10 is a minimum requirement. Please see the documentation for details.
\ No newline at end of file
+¹ It is possible (but not recommended) to use Primitively on class libraries that target platforms that preceed .NET 6. Extra configuration is required for this to work because C# 10 is a minimum requirement. To learn more, clone the Primitively git repo and open the example projects.
\ No newline at end of file
diff --git a/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/Acme.Temperature.UnitTests.csproj b/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/Acme.Temperature.UnitTests.csproj
new file mode 100644
index 0000000..667f061
--- /dev/null
+++ b/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/Acme.Temperature.UnitTests.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net8.0
+ enable
+ enable
+ false
+ true
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/CelsiusTests.cs b/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/CelsiusTests.cs
new file mode 100644
index 0000000..070af5d
--- /dev/null
+++ b/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/CelsiusTests.cs
@@ -0,0 +1,76 @@
+namespace Acme.Temperature.UnitTests;
+
+public class CelsiusTests
+{
+ [Fact]
+ public void AbsoluteZero_In_Sut_Scale_Converts_To_AbsoluteZero_In_Other_Scales()
+ {
+ // Assign
+ var celsius = Celsius.AbsoluteZero;
+
+ // Act
+ var fahrenheit = (Fahrenheit)celsius;
+ var kelvin = (Kelvin)celsius;
+ var rankine = (Rankine)celsius;
+
+ // Assert
+ celsius.Should().BeEquivalentTo(Celsius.AbsoluteZero);
+ fahrenheit.Should().BeEquivalentTo(Fahrenheit.AbsoluteZero);
+ kelvin.Should().BeEquivalentTo(Kelvin.AbsoluteZero);
+ rankine.Should().BeEquivalentTo(Rankine.AbsoluteZero);
+ }
+
+ [Fact]
+ public void WaterMeltingPoint_In_Sut_Scale_Converts_To_WaterMeltingPoint_In_Other_Scales()
+ {
+ // Assign
+ var celsius = Celsius.WaterMeltingPoint;
+
+ // Act
+ var fahrenheit = (Fahrenheit)celsius;
+ var kelvin = (Kelvin)celsius;
+ var rankine = (Rankine)celsius;
+
+ // Assert
+ celsius.Should().BeEquivalentTo(Celsius.WaterMeltingPoint);
+ fahrenheit.Should().BeEquivalentTo(Fahrenheit.WaterMeltingPoint);
+ kelvin.Should().BeEquivalentTo(Kelvin.WaterMeltingPoint);
+ rankine.Should().BeEquivalentTo(Rankine.WaterMeltingPoint);
+ }
+
+ [Fact]
+ public void WaterBoilingPoint_In_Sut_Scale_Converts_To_WaterBoilingPoint_In_Other_Scales()
+ {
+ // Assign
+ var celsius = Celsius.WaterBoilingPoint;
+
+ // Act
+ var fahrenheit = (Fahrenheit)celsius;
+ var kelvin = (Kelvin)celsius;
+ var rankine = (Rankine)celsius;
+
+ // Assert
+ celsius.Should().BeEquivalentTo(Celsius.WaterBoilingPoint);
+ fahrenheit.Should().BeEquivalentTo(Fahrenheit.WaterBoilingPoint);
+ kelvin.Should().BeEquivalentTo(Kelvin.WaterBoilingPoint);
+ rankine.Should().BeEquivalentTo(Rankine.WaterBoilingPoint);
+ }
+
+ [Fact]
+ public void Minimum_Sut_Scale_Converts_To_Minimum_In_Other_Scales()
+ {
+ // Assign
+ var celsius = new Celsius(Celsius.Minimum);
+
+ // Act
+ double kelvin = (Kelvin)celsius;
+ double fahrenheit = (Fahrenheit)celsius;
+ double rankine = (Rankine)celsius;
+
+ // Assert
+ celsius.Should().BeEquivalentTo((Celsius)Celsius.Minimum);
+ fahrenheit.Should().Be(Fahrenheit.Minimum);
+ kelvin.Should().Be(Kelvin.Minimum);
+ rankine.Should().Be(Rankine.Minimum);
+ }
+}
diff --git a/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/FahrenheitTests.cs b/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/FahrenheitTests.cs
new file mode 100644
index 0000000..7285222
--- /dev/null
+++ b/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/FahrenheitTests.cs
@@ -0,0 +1,76 @@
+namespace Acme.Temperature.UnitTests;
+
+public class FahrenheitTests
+{
+ [Fact]
+ public void AbsoluteZero_In_Sut_Scale_Converts_To_AbsoluteZero_In_Other_Scales()
+ {
+ // Assign
+ var fahrenheit = Fahrenheit.AbsoluteZero;
+
+ // Act
+ var celsius = (Celsius)fahrenheit;
+ var kelvin = (Kelvin)fahrenheit;
+ var rankine = (Rankine)fahrenheit;
+
+ // Assert
+ celsius.Should().BeEquivalentTo(Celsius.AbsoluteZero);
+ fahrenheit.Should().BeEquivalentTo(Fahrenheit.AbsoluteZero);
+ kelvin.Should().BeEquivalentTo(Kelvin.AbsoluteZero);
+ rankine.Should().BeEquivalentTo(Rankine.AbsoluteZero);
+ }
+
+ [Fact]
+ public void WaterMeltingPoint_In_Sut_Scale_Converts_To_WaterMeltingPoint_In_Other_Scales()
+ {
+ // Assign
+ var fahrenheit = Fahrenheit.WaterMeltingPoint;
+
+ // Act
+ var celsius = (Celsius)fahrenheit;
+ var kelvin = (Kelvin)fahrenheit;
+ var rankine = (Rankine)fahrenheit;
+
+ // Assert
+ celsius.Should().BeEquivalentTo(Celsius.WaterMeltingPoint);
+ fahrenheit.Should().BeEquivalentTo(Fahrenheit.WaterMeltingPoint);
+ kelvin.Should().BeEquivalentTo(Kelvin.WaterMeltingPoint);
+ rankine.Should().BeEquivalentTo(Rankine.WaterMeltingPoint);
+ }
+
+ [Fact]
+ public void WaterBoilingPoint_In_Sut_Scale_Converts_To_WaterBoilingPoint_In_Other_Scales()
+ {
+ // Assign
+ var fahrenheit = Fahrenheit.WaterBoilingPoint;
+
+ // Act
+ var celsius = (Celsius)fahrenheit;
+ var kelvin = (Kelvin)fahrenheit;
+ var rankine = (Rankine)fahrenheit;
+
+ // Assert
+ celsius.Should().BeEquivalentTo(Celsius.WaterBoilingPoint);
+ fahrenheit.Should().BeEquivalentTo(Fahrenheit.WaterBoilingPoint);
+ kelvin.Should().BeEquivalentTo(Kelvin.WaterBoilingPoint);
+ rankine.Should().BeEquivalentTo(Rankine.WaterBoilingPoint);
+ }
+
+ [Fact]
+ public void Minimum_Sut_Scale_Converts_To_Minimum_In_Other_Scales()
+ {
+ // Assign
+ var fahrenheit = new Fahrenheit(Fahrenheit.Minimum);
+
+ // Act
+ double celsius = (Celsius)fahrenheit;
+ double kelvin = (Kelvin)fahrenheit;
+ double rankine = (Rankine)fahrenheit;
+
+ // Assert
+ celsius.Should().Be(Celsius.Minimum);
+ fahrenheit.Should().BeEquivalentTo((Fahrenheit)Fahrenheit.Minimum);
+ kelvin.Should().Be(Kelvin.Minimum);
+ rankine.Should().Be(Rankine.Minimum);
+ }
+}
diff --git a/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/GlobalUsings.cs b/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/GlobalUsings.cs
new file mode 100644
index 0000000..30b9735
--- /dev/null
+++ b/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/GlobalUsings.cs
@@ -0,0 +1,2 @@
+global using Xunit;
+global using FluentAssertions;
diff --git a/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/KelvinTests.cs b/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/KelvinTests.cs
new file mode 100644
index 0000000..020f0ca
--- /dev/null
+++ b/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/KelvinTests.cs
@@ -0,0 +1,76 @@
+namespace Acme.Temperature.UnitTests;
+
+public class KelvinTests
+{
+ [Fact]
+ public void AbsoluteZero_In_Sut_Scale_Converts_To_AbsoluteZero_In_Other_Scales()
+ {
+ // Assign
+ var kelvin = Kelvin.AbsoluteZero;
+
+ // Act
+ var celsius = (Celsius)kelvin;
+ var fahrenheit = (Fahrenheit)kelvin;
+ var rankine = (Rankine)kelvin;
+
+ // Assert
+ celsius.Should().BeEquivalentTo(Celsius.AbsoluteZero);
+ fahrenheit.Should().BeEquivalentTo(Fahrenheit.AbsoluteZero);
+ kelvin.Should().BeEquivalentTo(Kelvin.AbsoluteZero);
+ rankine.Should().BeEquivalentTo(Rankine.AbsoluteZero);
+ }
+
+ [Fact]
+ public void WaterMeltingPoint_In_Sut_Scale_Converts_To_WaterMeltingPoint_In_Other_Scales()
+ {
+ // Assign
+ var kelvin = Kelvin.WaterMeltingPoint;
+
+ // Act
+ var celsius = (Celsius)kelvin;
+ var fahrenheit = (Fahrenheit)kelvin;
+ var rankine = (Rankine)kelvin;
+
+ // Assert
+ celsius.Should().BeEquivalentTo(Celsius.WaterMeltingPoint);
+ fahrenheit.Should().BeEquivalentTo(Fahrenheit.WaterMeltingPoint);
+ kelvin.Should().BeEquivalentTo(Kelvin.WaterMeltingPoint);
+ rankine.Should().BeEquivalentTo(Rankine.WaterMeltingPoint);
+ }
+
+ [Fact]
+ public void WaterBoilingPoint_In_Sut_Scale_Converts_To_WaterBoilingPoint_In_Other_Scales()
+ {
+ // Assign
+ var kelvin = Kelvin.WaterBoilingPoint;
+
+ // Act
+ var celsius = (Celsius)kelvin;
+ var fahrenheit = (Fahrenheit)kelvin;
+ var rankine = (Rankine)kelvin;
+
+ // Assert
+ celsius.Should().BeEquivalentTo(Celsius.WaterBoilingPoint);
+ fahrenheit.Should().BeEquivalentTo(Fahrenheit.WaterBoilingPoint);
+ kelvin.Should().BeEquivalentTo(Kelvin.WaterBoilingPoint);
+ rankine.Should().BeEquivalentTo(Rankine.WaterBoilingPoint);
+ }
+
+ [Fact]
+ public void Minimum_Sut_Scale_Converts_To_Minimum_In_Other_Scales()
+ {
+ // Assign
+ var kelvin = new Kelvin(Kelvin.Minimum);
+
+ // Act
+ double celsius = (Celsius)kelvin;
+ double fahrenheit = (Fahrenheit)kelvin;
+ double rankine = (Rankine)kelvin;
+
+ // Assert
+ celsius.Should().Be(Celsius.Minimum);
+ fahrenheit.Should().Be(Fahrenheit.Minimum);
+ kelvin.Should().BeEquivalentTo((Kelvin)Kelvin.Minimum);
+ rankine.Should().Be(Rankine.Minimum);
+ }
+}
diff --git a/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/RankineTests.cs b/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/RankineTests.cs
new file mode 100644
index 0000000..b4b8970
--- /dev/null
+++ b/examples/03-temperature-units-of-measure/Acme.Temperature.UnitTests/RankineTests.cs
@@ -0,0 +1,76 @@
+namespace Acme.Temperature.UnitTests;
+
+public class RankineTests
+{
+ [Fact]
+ public void AbsoluteZero_In_Sut_Scale_Converts_To_AbsoluteZero_In_Other_Scales()
+ {
+ // Assign
+ var rankine = Rankine.AbsoluteZero;
+
+ // Act
+ var celsius = (Celsius)rankine;
+ var fahrenheit = (Fahrenheit)rankine;
+ var kelvin = (Kelvin)rankine;
+
+ // Assert
+ celsius.Should().BeEquivalentTo(Celsius.AbsoluteZero);
+ fahrenheit.Should().BeEquivalentTo(Fahrenheit.AbsoluteZero);
+ kelvin.Should().BeEquivalentTo(Kelvin.AbsoluteZero);
+ rankine.Should().BeEquivalentTo(Rankine.AbsoluteZero);
+ }
+
+ [Fact]
+ public void WaterMeltingPoint_In_Sut_Scale_Converts_To_WaterMeltingPoint_In_Other_Scales()
+ {
+ // Assign
+ var rankine = Rankine.WaterMeltingPoint;
+
+ // Act
+ var celsius = (Celsius)rankine;
+ var fahrenheit = (Fahrenheit)rankine;
+ var kelvin = (Kelvin)rankine;
+
+ // Assert
+ celsius.Should().BeEquivalentTo(Celsius.WaterMeltingPoint);
+ fahrenheit.Should().BeEquivalentTo(Fahrenheit.WaterMeltingPoint);
+ kelvin.Should().BeEquivalentTo(Kelvin.WaterMeltingPoint);
+ rankine.Should().BeEquivalentTo(Rankine.WaterMeltingPoint);
+ }
+
+ [Fact]
+ public void WaterBoilingPoint_In_Sut_Scale_Converts_To_WaterBoilingPoint_In_Other_Scales()
+ {
+ // Assign
+ var rankine = Rankine.WaterBoilingPoint;
+
+ // Act
+ var celsius = (Celsius)rankine;
+ var fahrenheit = (Fahrenheit)rankine;
+ var kelvin = (Kelvin)rankine;
+
+ // Assert
+ celsius.Should().BeEquivalentTo(Celsius.WaterBoilingPoint);
+ fahrenheit.Should().BeEquivalentTo(Fahrenheit.WaterBoilingPoint);
+ kelvin.Should().BeEquivalentTo(Kelvin.WaterBoilingPoint);
+ rankine.Should().BeEquivalentTo(Rankine.WaterBoilingPoint);
+ }
+
+ [Fact]
+ public void Minimum_Sut_Scale_Converts_To_Minimum_In_Other_Scales()
+ {
+ // Assign
+ var rankine = new Rankine(Rankine.Minimum);
+
+ // Act
+ double celsius = (Celsius)rankine;
+ double fahrenheit = (Fahrenheit)rankine;
+ double kelvin = (Kelvin)rankine;
+
+ // Assert
+ celsius.Should().Be(Celsius.Minimum);
+ fahrenheit.Should().Be(Fahrenheit.Minimum);
+ kelvin.Should().Be(Kelvin.Minimum);
+ rankine.Should().BeEquivalentTo((Rankine)Rankine.Minimum);
+ }
+}
diff --git a/examples/03-temperature-units-of-measure/Acme.Temperature/Acme.Temperature.csproj b/examples/03-temperature-units-of-measure/Acme.Temperature/Acme.Temperature.csproj
new file mode 100644
index 0000000..67d8846
--- /dev/null
+++ b/examples/03-temperature-units-of-measure/Acme.Temperature/Acme.Temperature.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/examples/03-temperature-units-of-measure/Acme.Temperature/Acme.Temperature.sln b/examples/03-temperature-units-of-measure/Acme.Temperature/Acme.Temperature.sln
new file mode 100644
index 0000000..1119bf4
--- /dev/null
+++ b/examples/03-temperature-units-of-measure/Acme.Temperature/Acme.Temperature.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34622.214
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.Temperature", "Acme.Temperature.csproj", "{CF73B1AF-CBD9-4786-B550-E6EC52DC2549}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Acme.Temperature.UnitTests", "..\Acme.Temperature.UnitTests\Acme.Temperature.UnitTests.csproj", "{CF7BE825-46CE-4B66-97E5-66D282DE7DCF}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {CF73B1AF-CBD9-4786-B550-E6EC52DC2549}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CF73B1AF-CBD9-4786-B550-E6EC52DC2549}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CF73B1AF-CBD9-4786-B550-E6EC52DC2549}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CF73B1AF-CBD9-4786-B550-E6EC52DC2549}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CF7BE825-46CE-4B66-97E5-66D282DE7DCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CF7BE825-46CE-4B66-97E5-66D282DE7DCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CF7BE825-46CE-4B66-97E5-66D282DE7DCF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CF7BE825-46CE-4B66-97E5-66D282DE7DCF}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {6C5174C6-2AA7-44E7-AEAA-C98512786A87}
+ EndGlobalSection
+EndGlobal
diff --git a/examples/03-temperature-units-of-measure/Acme.Temperature/Celsius.cs b/examples/03-temperature-units-of-measure/Acme.Temperature/Celsius.cs
new file mode 100644
index 0000000..60529ea
--- /dev/null
+++ b/examples/03-temperature-units-of-measure/Acme.Temperature/Celsius.cs
@@ -0,0 +1,20 @@
+using Primitively;
+
+// https://en.wikipedia.org/wiki/Celsius
+// https://en.wikipedia.org/wiki/Conversion_of_scales_of_temperature
+
+namespace Acme.Temperature;
+
+[Double(2, Minimum = -273.15d)]
+public partial record struct Celsius : ITemperature
+{
+ public static Celsius AbsoluteZero => new(-273.15d);
+
+ public static Celsius WaterMeltingPoint => new(0d);
+
+ public static Celsius WaterBoilingPoint => new(99.9839d);
+
+ public static explicit operator Kelvin(Celsius value) => new(value + 273.15d);
+ public static explicit operator Fahrenheit(Celsius value) => new((value * (9d / 5d)) + 32d);
+ public static explicit operator Rankine(Celsius value) => new((value + 273.15d) * (9d / 5d));
+}
diff --git a/examples/03-temperature-units-of-measure/Acme.Temperature/Fahrenheit.cs b/examples/03-temperature-units-of-measure/Acme.Temperature/Fahrenheit.cs
new file mode 100644
index 0000000..4b1c7d2
--- /dev/null
+++ b/examples/03-temperature-units-of-measure/Acme.Temperature/Fahrenheit.cs
@@ -0,0 +1,20 @@
+using Primitively;
+
+// https://en.wikipedia.org/wiki/Celsius
+// https://en.wikipedia.org/wiki/Conversion_of_scales_of_temperature
+
+namespace Acme.Temperature;
+
+[Double(2, Minimum = -459.67d)]
+public partial record struct Fahrenheit : ITemperature
+{
+ public static Fahrenheit AbsoluteZero => new(-459.67d);
+
+ public static Fahrenheit WaterMeltingPoint => new(32d);
+
+ public static Fahrenheit WaterBoilingPoint => new(211.97102d);
+
+ public static explicit operator Celsius(Fahrenheit value) => new((value - 32d) * (5d / 9d));
+ public static explicit operator Kelvin(Fahrenheit value) => new((value + 459.67d) * (5d / 9d));
+ public static explicit operator Rankine(Fahrenheit value) => new(value + 459.67d);
+}
diff --git a/examples/03-temperature-units-of-measure/Acme.Temperature/ITemperature.cs b/examples/03-temperature-units-of-measure/Acme.Temperature/ITemperature.cs
new file mode 100644
index 0000000..904c15b
--- /dev/null
+++ b/examples/03-temperature-units-of-measure/Acme.Temperature/ITemperature.cs
@@ -0,0 +1,8 @@
+namespace Acme.Temperature;
+
+public interface ITemperature where T : ITemperature
+{
+ static abstract T AbsoluteZero { get; }
+ static abstract T WaterMeltingPoint { get; }
+ static abstract T WaterBoilingPoint { get; }
+}
diff --git a/examples/03-temperature-units-of-measure/Acme.Temperature/Kelvin.cs b/examples/03-temperature-units-of-measure/Acme.Temperature/Kelvin.cs
new file mode 100644
index 0000000..04d0fa3
--- /dev/null
+++ b/examples/03-temperature-units-of-measure/Acme.Temperature/Kelvin.cs
@@ -0,0 +1,20 @@
+using Primitively;
+
+// https://en.wikipedia.org/wiki/Celsius
+// https://en.wikipedia.org/wiki/Conversion_of_scales_of_temperature
+
+namespace Acme.Temperature;
+
+[Double(2, Minimum = 0d)]
+public partial record struct Kelvin : ITemperature
+{
+ public static Kelvin AbsoluteZero => new(0d);
+
+ public static Kelvin WaterMeltingPoint => new(273.15d);
+
+ public static Kelvin WaterBoilingPoint => new(373.1339d);
+
+ public static explicit operator Celsius(Kelvin value) => new(value - 273.15d);
+ public static explicit operator Fahrenheit(Kelvin value) => new((9d / 5d * value) - 459.67d);
+ public static explicit operator Rankine(Kelvin value) => new(9d / 5d * value);
+}
diff --git a/examples/03-temperature-units-of-measure/Acme.Temperature/Rankine.cs b/examples/03-temperature-units-of-measure/Acme.Temperature/Rankine.cs
new file mode 100644
index 0000000..08c57fb
--- /dev/null
+++ b/examples/03-temperature-units-of-measure/Acme.Temperature/Rankine.cs
@@ -0,0 +1,20 @@
+using Primitively;
+
+// https://en.wikipedia.org/wiki/Celsius
+// https://en.wikipedia.org/wiki/Conversion_of_scales_of_temperature
+
+namespace Acme.Temperature;
+
+[Double(2, Minimum = 0d)]
+public partial record struct Rankine : ITemperature
+{
+ public static Rankine AbsoluteZero => new(0d);
+
+ public static Rankine WaterMeltingPoint => new(491.67d);
+
+ public static Rankine WaterBoilingPoint => new(671.64102d);
+
+ public static explicit operator Celsius(Rankine value) => new((5d / 9d * value) - 273.15d);
+ public static explicit operator Fahrenheit(Rankine value) => new(value - 459.67d);
+ public static explicit operator Kelvin(Rankine value) => new(5d / 9d * value);
+}
diff --git a/test/Acme.TestLib/Temperature.cs b/test/Acme.TestLib/Temperature.cs
new file mode 100644
index 0000000..3710a40
--- /dev/null
+++ b/test/Acme.TestLib/Temperature.cs
@@ -0,0 +1,71 @@
+using Primitively;
+
+// https://en.wikipedia.org/wiki/Celsius
+// https://en.wikipedia.org/wiki/Conversion_of_scales_of_temperature
+
+namespace Acme.TestLib;
+
+[Double(2, Minimum = -273.15d)]
+public partial record struct Celsius : ITemperature
+{
+ public static Celsius AbsoluteZero => new(-273.15d);
+
+ public static Celsius WaterMeltingPoint => new(0d);
+
+ public static Celsius WaterBoilingPoint => new(99.9839d);
+
+ public static explicit operator Kelvin(Celsius value) => new(value + 273.15d);
+ public static explicit operator Fahrenheit(Celsius value) => new((9d / 5d * value) + 32d);
+ public static explicit operator Rankine(Celsius value) => new((value + 273.15d) * (9d / 5d));
+}
+
+[Double(Minimum = -459.67d)]
+public partial record struct Fahrenheit : ITemperature
+{
+ public static Fahrenheit AbsoluteZero => new(-459.67d);
+
+ public static Fahrenheit WaterMeltingPoint => new(32d);
+
+ public static Fahrenheit WaterBoilingPoint => new(211.97102d);
+
+ public static explicit operator Celsius(Fahrenheit value) => new((value - 32d) * (5d / 9d));
+ public static explicit operator Kelvin(Fahrenheit value) => new((value + 459.67d) * (5d / 9d));
+ public static explicit operator Rankine(Fahrenheit value) => new(value + 459.67d);
+}
+
+[Double(Minimum = 0d)]
+public partial record struct Kelvin : ITemperature
+{
+ public static Kelvin AbsoluteZero => new(0d);
+
+ public static Kelvin WaterMeltingPoint => new(273.15d);
+
+ public static Kelvin WaterBoilingPoint => new(373.1339d);
+
+ public static explicit operator Celsius(Kelvin value) => new(value - 273.15d);
+ public static explicit operator Fahrenheit(Kelvin value) => new((9d / 5d * value) - 459.67d);
+ public static explicit operator Rankine(Kelvin value) => new(9d / 5d * value);
+}
+
+[Double(Minimum = 0d)]
+public partial record struct Rankine : ITemperature
+{
+ public static Rankine AbsoluteZero => new(0d);
+
+ public static Rankine WaterMeltingPoint => new(491.67d);
+
+ public static Rankine WaterBoilingPoint => new(671.64102d);
+
+ public static explicit operator Celsius(Rankine value) => new((5d / 9d * value) - 273.15d);
+ public static explicit operator Fahrenheit(Rankine value) => new(value - 459.67d);
+ public static explicit operator Kelvin(Rankine value) => new(5d / 9d * value);
+}
+
+public interface ITemperature where T : ITemperature
+{
+#if NET7_0_OR_GREATER
+ static abstract T AbsoluteZero { get; }
+ static abstract T WaterMeltingPoint { get; }
+ static abstract T WaterBoilingPoint { get; }
+#endif
+}