Repository: chrisguidry/measured (language: python)

Issue:
There is a cubic ton of odd and wonderful measurements to incorporate on Wikipedia's List of unusual units of measurement
https://en.wikipedia.org/wiki/List_of_unusual_units_of_measurement


Discussion / hints:

Fix patch:
diff --git a/src/measured/__init__.py b/src/measured/__init__.py
index 362475f..01f0237 100644
--- a/src/measured/__init__.py
+++ b/src/measured/__init__.py
@@ -1606,6 +1606,7 @@ def assert_approximates(self, other: "Quantity", within: float = 1e-7) -> None:
 
 Area = Dimension.derive(Length * Length, name="area")
 Volume = Dimension.derive(Area * Length, name="volume")
+VolumetricFlow = Dimension.derive(Volume / Time, name="flow")
 
 PlaneAngle = Length / Length
 SolidAngle = Area / Area
@@ -1638,6 +1639,7 @@ def assert_approximates(self, other: "Quantity", within: float = 1e-7) -> None:
 MagneticFlux = Dimension.derive(Potential * Time, name="magnetic flux")
 MagneticBField = Dimension.derive(Potential * Time / Area, name="magnetic B-field")
 
+
 LuminousFlux = LuminousIntensity * SolidAngle
 Illuminance = Dimension.derive(LuminousIntensity / Area, name="illuminance")
 
diff --git a/src/measured/astronomical.py b/src/measured/astronomical.py
index 3975bdb..fb306bc 100644
--- a/src/measured/astronomical.py
+++ b/src/measured/astronomical.py
@@ -35,10 +35,10 @@
     JupiterMass (Unit): The mass of Jupiter
 """
 
-from measured import Length, Mass, Time, Unit, si
+from measured import Area, Length, Mass, Power, Time, Unit, si
 from measured.geometry import π
 from measured.physics import c
-from measured.si import Kilogram, Meter, Second
+from measured.si import Kilogram, Meter, Second, Watt
 
 # Measures of Time
 
@@ -59,6 +59,10 @@
 LightYear = Length.unit("light-year", "ly")
 LightYear.equals((c * JulianYear).in_unit(Meter))
 
+# https://en.wikipedia.org/wiki/Spat_(distance_unit)
+Spat = Length.unit("spat", "spat")
+Spat.equals(1e12 * Meter)
+
 # https://en.wikipedia.org/wiki/Parsec#Calculating_the_value_of_a_parsec
 Parsec = Length.unit("parsec", "pc")
 Parsec.equals(96939420213600600 / π * Meter)
@@ -78,3 +82,9 @@
 
 JupiterMass = Mass.unit("jupiter mass", "M-jup")
 JupiterMass.equals(1.89813e27 * Kilogram)
+
+
+# Derived
+
+Jansky = (Power * Time / Area).unit("jansky", "Jy")
+Jansky.equals(1e-26 * Watt * Second / Meter**2)
diff --git a/src/measured/energy.py b/src/measured/energy.py
index 3d5411b..a72029d 100644
--- a/src/measured/energy.py
+++ b/src/measured/energy.py
@@ -1,5 +1,5 @@
-from measured import Energy, Power
-from measured.si import Hour, Joule, Kilo, Watt
+from measured import Area, Energy, Power
+from measured.si import Hour, Joule, Kilo, Meter, Watt
 
 # https://en.wikipedia.org/wiki/Calorie
 Calorie = Energy.unit(name="calorie", symbol="cal")
@@ -18,3 +18,7 @@
 TonneOfTNT = Energy.unit(name="ton of TNT", symbol="tₜₙₜ")
 TonneOfTNT.equals(4.184e9 * Joule)
 TonneOfTNT.equals(1e9 * Calorie)
+
+# https://en.wikipedia.org/wiki/Langley_(unit)
+Langley = (Energy / Area).unit("langley", "Ly")
+Langley.equals(41840 * Joule / Meter**2)
diff --git a/src/measured/iso.py b/src/measured/iso.py
new file mode 100644
index 0000000..971b3a6
--- /dev/null
+++ b/src/measured/iso.py
@@ -0,0 +1,13 @@
+from measured import Length
+from measured.si import Meter
+
+# ISO 2848
+
+BasicModule = Length.unit("basic module", "basic-module")
+BasicModule.equals(0.1 * Meter)
+
+MetricInch = Length.unit("metric inch", "metric-foot")
+MetricInch.equals(0.25 * BasicModule)
+
+MetricFoot = Length.unit("metric foot", "metric-inch")
+MetricInch.equals(3 * BasicModule)
diff --git a/src/measured/metric.py b/src/measured/metric.py
new file mode 100644
index 0000000..fd655c3
--- /dev/null
+++ b/src/measured/metric.py
@@ -0,0 +1,63 @@
+# Units based on the meter, gram, and second, but not part of the SI system of units
+# https://en.wikipedia.org/wiki/List_of_metric_units
+#
+# Includes specialized units compatible with SI and accepted for use with it by either
+# the BIPM or NIST
+# https://www.nist.gov/pml/special-publication-811/nist-guide-si-chapter-5-units-outside-si
+
+from measured import Area, Frequency, Length, Time, Volume, VolumetricFlow
+from measured.si import Femto, Hertz, Liter, Meter, Micro, Second, Tera
+
+# Length, Area, and Volume
+
+Ångström = Length.unit("Ångström", "Å")
+Ångström.equals(1e-10 * Meter)
+
+Fermi = Length.unit("fermi", "fermi")
+Fermi.equals(1 * Femto * Meter)
+
+Micron = Length.unit("micron", "μ")
+Micron.equals(1 * Micro * Meter)
+
+MetricMile = Length.unit("metric mile", "metric-mile")
+MetricMile.equals(1500 * Meter)
+
+Lieue = Length.unit("lieue", "lieue")
+Lieue.equals(4000 * Meter)
+
+Lambda = Volume.unit("lambda", "λ")
+Lambda.equals(1 * Micro * Liter)
+
+Stere = Volume.unit("stere", "st")
+Stere.equals(1 * Meter**3)
+
+# From the domain of high-energy and atomic physics
+# https://en.wikipedia.org/wiki/Barn_(unit)
+# https://en.wikipedia.org/wiki/Shake_(unit)
+
+Barn = Area.unit("barn", "barn")
+Barn.equals(1e-28 * Meter**2)
+
+Outhouse = Area.unit("outhouse", "outhouse")
+Outhouse.equals(1e-34 * Meter**2)
+
+Shed = Area.unit("shed", "shed")
+Shed.equals(1e-52 * Meter**2)
+
+
+# Time
+
+Svedberg = Time.unit("svedberg", "svedberg")
+Svedberg.equals(100 * Femto * Second)
+
+Shake = Time.unit("shake", "shake")
+Shake.equals(1e-8 * Second)
+
+Fresnel = Frequency.unit("fresnel", "fresnel")
+Fresnel.equals(1 * Tera * Hertz)
+
+
+# Derived
+
+Sverdrup = VolumetricFlow.unit("sverdrup", "sverdrup")
+Sverdrup.equals(1000000 * Meter**3 / Second)
diff --git a/src/measured/si.py b/src/measured/si.py
index 294943e..77aaa11 100644
--- a/src/measured/si.py
+++ b/src/measured/si.py
@@ -227,9 +227,14 @@
 # SI Accepted units
 # https://en.wikipedia.org/wiki/Non-SI_units_mentioned_in_the_SI
 
+# Length, Area, Volume
+Hectare = Area.unit(name="hectare", symbol="ha")
+Hectare.equals(1 * (Hecto * Meter) ** 2)
+
 Liter = Volume.unit(name="liter", symbol="L")
 Liter.equals(0.001 * Meter**3)
 
+# Time
 Minute = Time.unit("minute", "min")
 Minute.equals(60 * Second)
 
@@ -237,13 +242,6 @@
 Hour.equals(3600 * Second)
 Hour.equals(60 * Minute)
 
-Dalton = Mass.unit("dalton", "Da")
-Dalton.equals(1.66053906660e-27 * Kilogram)
-
-UnifiedAtomicMass = Mass.unit("unified atomic mass", "u")
-UnifiedAtomicMass.equals(1.660538782e-27 * Kilogram)
-
-
 # This is the SI-compatible "day" as a unit of measure, defined as 86,400 seconds.
 # https://en.wikipedia.org/wiki/Day#International_System_of_Units_(SI)
 Day = Time.unit("day", "d")
@@ -251,6 +249,20 @@
 Day.equals(1440 * Minute)
 Day.equals(24 * Hour)
 
+# Mass
+Dalton = Mass.unit("dalton", "Da")
+Dalton.equals(1.66053906660e-27 * Kilogram)
+
+Tonne = Mass.unit("tonne", "t")
+Tonne.equals(1000 * Kilogram)
+
+ElectronVolt = Energy.unit("electron-volt", "eV")
+ElectronVolt.equals(1.602176634e-19 * Joule)
+
+UnifiedAtomicMass = Mass.unit("unified atomic mass", "u")
+UnifiedAtomicMass.equals(1.660538782e-27 * Kilogram)
+
+# Angles
 Degree = PlaneAngle.unit("degree", "°")
 Degree.alias(symbol="deg")
 Degree.equals(π / 180 * Radian)
@@ -263,30 +275,3 @@
 Arcsecond.equals(π / (180 * 3600) * Radian)
 Arcsecond.equals(1 / 3600 * Degree)
 Arcsecond.equals(1 / 60 * Arcminute)
-
-
-# Specialized units compatible with SI and accepted for use with it by either
-# the BIPM or NIST
-# https://www.nist.gov/pml/special-publication-811/nist-guide-si-chapter-5-units-outside-si
-
-# From the domain of high-energy and atomic physics
-# https://en.wikipedia.org/wiki/Barn_(unit)
-# https://en.wikipedia.org/wiki/Shake_(unit)
-
-Ångström = Length.unit("Ångström", symbol="Å")
-Ångström.equals(1e-10 * Meter)
-
-Barn = Area.unit("barn", "barn")
-Barn.equals(1e-28 * Meter**2)
-
-Outhouse = Area.unit("outhouse", "outhouse")
-Outhouse.equals(1e-34 * Meter**2)
-
-Shed = Area.unit("shed", "shed")
-Shed.equals(1e-52 * Meter**2)
-
-Shake = Time.unit("shake", "shake")
-Shake.equals(1e-8 * Second)
-
-ElectronVolt = Energy.unit("electron-volt", "eV")
-ElectronVolt.equals(1.602176634e-19 * Joule)
diff --git a/src/measured/systems.py b/src/measured/systems.py
index ca81c81..0469423 100644
--- a/src/measured/systems.py
+++ b/src/measured/systems.py
@@ -2,9 +2,12 @@
     astronomical,
     avoirdupois,
     computing,
+    energy,
     eu,
     fff,
     iec,
+    iso,
+    metric,
     natural,
     si,
     troy,


Test patch:
diff --git a/tests/si/test_length_area_volume.py b/tests/si/test_length_area_volume.py
index b608019..0d8118b 100644
--- a/tests/si/test_length_area_volume.py
+++ b/tests/si/test_length_area_volume.py
@@ -1,17 +1,4 @@
-from measured import Area
-from measured.si import (
-    Barn,
-    Centi,
-    Deci,
-    Kilo,
-    Liter,
-    Meter,
-    Micro,
-    Milli,
-    Outhouse,
-    Shed,
-    Yocto,
-)
+from measured.si import Centi, Deci, Kilo, Liter, Meter, Milli
 
 cm = Centi * Meter
 km = Kilo * Meter
@@ -43,29 +30,6 @@ def test_area_conversions() -> None:
     assert (1 * Meter**2).in_unit(km**2).magnitude == 0.000001
 
 
-def test_barn() -> None:
-    assert Barn.dimension is Area
-    assert Barn.name == "barn"
-    assert Barn.symbol == "barn"  # we already have a 'b' symbol at measured.iec.Bit
-    (1 * Barn).assert_approximates(1e-28 * Meter**2)
-
-
-def test_outhouse() -> None:
-    assert Outhouse.dimension is Area
-    assert Outhouse.name == "outhouse"
-    assert Outhouse.symbol == "outhouse"
-    (1 * Outhouse).assert_approximates(1 * Micro * Barn)
-    (1 * Outhouse).assert_approximates(1e-34 * Meter**2)
-
-
-def test_shed() -> None:
-    assert Shed.dimension is Area
-    assert Shed.name == "shed"
-    assert Shed.symbol == "shed"
-    (1 * Shed).assert_approximates(1 * Yocto * Barn)
-    (1 * Shed).assert_approximates(1e-52 * Meter**2)
-
-
 def test_volume_equivalence() -> None:
     assert 1 * Liter == 0.001 * Meter**3
     assert 1 * Liter == 1 * (Deci * Meter) ** 3
diff --git a/tests/si/test_time.py b/tests/si/test_time.py
index 5af633e..9d89dbb 100644
--- a/tests/si/test_time.py
+++ b/tests/si/test_time.py
@@ -1,7 +1,7 @@
 import pytest
 
 from measured import Quantity
-from measured.si import Day, Hour, Minute, Nano, Second, Shake
+from measured.si import Day, Hour, Minute, Second
 
 
 @pytest.mark.parametrize(
@@ -32,7 +32,3 @@ def test_time_conversions(
     seconds.assert_approximates(days)
     assert seconds == hours
     assert seconds == minutes
-
-
-def test_shake() -> None:
-    assert 1 * Shake == 10 * Nano * Second
diff --git a/tests/test_astronomical.py b/tests/test_astronomical.py
index 61ba808..06f6bf3 100644
--- a/tests/test_astronomical.py
+++ b/tests/test_astronomical.py
@@ -1,6 +1,7 @@
 from measured.astronomical import (
     AstronomicalUnit,
     EarthMass,
+    Jansky,
     JulianYear,
     JupiterMass,
     LightYear,
@@ -11,7 +12,7 @@
 )
 from measured.geometry import π
 from measured.physics import G
-from measured.si import Hour, Meter
+from measured.si import Hertz, Hour, Meter, Watt
 
 
 def test_astronomical_unit() -> None:
@@ -47,3 +48,7 @@ def test_solar_system_masses() -> None:
 
 def test_sidereal_day() -> None:
     (1 * SiderealDay).assert_approximates(23.9344696 * Hour, within=7e-10)
+
+
+def test_jansky() -> None:
+    assert 1 * Jansky == 1e-26 * Watt / Meter**2 / Hertz
diff --git a/tests/test_cli.py b/tests/test_cli.py
index b268fb9..0d826d7 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -42,9 +42,9 @@ def test_can_print_conversions() -> None:
 
 
 def test_can_handle_no_conversions() -> None:
-    output = run("measured 1 hertz")
+    output = run("measured 1 steradian")
     assert "Magnitude: 1" in output
-    assert "Unit: hertz" in output
+    assert "Unit: steradian" in output
 
 
 def test_handles_parse_errors_gracefully() -> None:
diff --git a/tests/test_metric.py b/tests/test_metric.py
new file mode 100644
index 0000000..cdd5075
--- /dev/null
+++ b/tests/test_metric.py
@@ -0,0 +1,30 @@
+from measured import Area
+from measured.metric import Barn, Outhouse, Shake, Shed
+from measured.si import Meter, Micro, Nano, Second, Yocto
+
+
+def test_barn() -> None:
+    assert Barn.dimension is Area
+    assert Barn.name == "barn"
+    assert Barn.symbol == "barn"  # we already have a 'b' symbol at measured.iec.Bit
+    (1 * Barn).assert_approximates(1e-28 * Meter**2)
+
+
+def test_outhouse() -> None:
+    assert Outhouse.dimension is Area
+    assert Outhouse.name == "outhouse"
+    assert Outhouse.symbol == "outhouse"
+    (1 * Outhouse).assert_approximates(1 * Micro * Barn)
+    (1 * Outhouse).assert_approximates(1e-34 * Meter**2)
+
+
+def test_shed() -> None:
+    assert Shed.dimension is Area
+    assert Shed.name == "shed"
+    assert Shed.symbol == "shed"
+    (1 * Shed).assert_approximates(1 * Yocto * Barn)
+    (1 * Shed).assert_approximates(1e-52 * Meter**2)
+
+
+def test_shake() -> None:
+    assert 1 * Shake == 10 * Nano * Second