The unit package provides a system for representing, converting, and operating on physical quantities with
units. It ensures that:
go get github.com/pdat-cz/go-unitimport "github.com/pdat-cz/go-unit"- Units are explicitly tracked with values
- Conversions between compatible units are handled automatically
- Type safety is maintained (preventing temperature from being added to pressure)
- Calculations involving different units work correctly
A Quantity represents a value with an associated unit. It combines a numeric value with a unit type to create a
complete representation of a physical quantity.
type Quantity[T UnitType] struct {
Value float64
Unit T
}The Quantity type is generic, allowing it to work with different unit types while maintaining type safety.
The package includes several predefined unit types:
TemperatureUnit: Celsius, Fahrenheit, KelvinPressureUnit: Pascal, Hectopascal, Kilopascal, Megapascal, Bar, Millibar, PSI, Atmosphere, MmHg (Torr), InchH2OFlowRateUnit: CubicMetersPerHour, CubicMetersPerSecond, LitersPerSecond, LitersPerMinute, LitersPerHour, GallonsPerMinute, CFMPowerUnit: Watt, Milliwatt, Kilowatt, Megawatt, Horsepower, BTUPerHourEnergyUnit: Joule, Kilojoule, Megajoule, Gigajoule, Terajoule, KilowattHour, BTULengthUnit: Meter, Kilometer, Centimeter, Millimeter, Micrometer, Nanometer, Inch, Foot, Yard, MileMassUnit: Kilogram, Gram, Milligram, Microgram, Pound, Ounce, Stone, MetricTon, TonDurationUnit: Second, Minute, Hour, Day, Millisecond, Microsecond, NanosecondAngleUnit: Radian, Degree, Arcminute, Arcsecond, Revolution, GradianAreaUnit: SquareMeter, SquareKilometer, SquareCentimeter, SquareMillimeter, SquareInch, SquareFoot, SquareYard, SquareMile, Acre, HectareVolumeUnit: CubicMeter, CubicKilometer, CubicCentimeter, CubicMillimeter, Liter, Milliliter, CubicInch, CubicFoot, CubicYard, Gallon, Quart, Pint, Cup, FluidOunceAccelerationUnit: MetersPerSecondSquared, G, FeetPerSecondSquaredConcentrationUnit: GramsPerLiter, MilligramsPerLiter, PartsPerMillion, PartsPerBillionDispersionUnit: PartsPerMillion, PartsPerBillion, PartsPerTrillion, PercentElectricChargeUnit: Coulomb, Millicoulomb, Microcoulomb, Ampere_Hour, Milliampere_HourElectricCurrentUnit: Ampere, Milliampere, Microampere, KiloampereElectricPotentialDifferenceUnit: Volt, Millivolt, Microvolt, Kilovolt, MegavoltSpeedUnit: MetersPerSecond, KilometersPerHour, MilesPerHour, FeetPerSecond, KnotFrequencyUnit: Hertz, Kilohertz, Megahertz, Gigahertz, Terahertz, RPMFuelEfficiencyUnit: KilometersPerLiter, MilesPerGallon, LitersPer100KilometersIlluminanceUnit: Lux, FootCandle, Phot, NoxInformationUnit: Bit, Byte, Kilobyte, Megabyte, Gigabyte, Terabyte, Petabyte, Kibibyte, Mebibyte, Gibibyte, Tebibyte, PebibyteGeneralUnit: A flexible unit type for custom units and project-specific measurements
Each unit type implements the UnitType interface, which provides methods for dimension information, unit conversion,
and equality checking.
// Create a temperature quantity of 22 degrees Celsius
temp := unit.NewTemperature(22.0, unit.Temperature.Celsius)
// Create a pressure quantity of 101.3 kPa
pressure := unit.NewPressure(101.3, unit.Pressure.Kilopascal)// Convert temperature from Celsius to Fahrenheit
tempF := temp.ConvertTo(unit.Temperature.Fahrenheit)
// tempF.Value is now 71.6
// Convert pressure from kPa to PSI
pressurePSI := pressure.ConvertTo(unit.Pressure.PSI)
// pressurePSI.Value is now 14.69// Add two temperatures (must be same dimension)
temp1 := unit.NewTemperature(22.0, unit.Temperature.Celsius)
temp2 := unit.NewTemperature(68.0, unit.Temperature.Fahrenheit)
sum := temp1.Add(temp2) // Automatically converts temp2 to Celsius before adding
// sum.Value is 42.0, sum.Unit is Celsius
// Multiply a quantity by a scalar
doubledTemp := temp1.MultiplyByScalar(2.0)
// doubledTemp.Value is 44.0, doubledTemp.Unit is Celsius// Parse a temperature from a string
temp, err := unit.ParseTemperature("25°C")
if err != nil {
// Handle error
}
// Parse a pressure from a string
pressure, err := unit.ParsePressure("101.325 kPa")
if err != nil {
// Handle error
}Quantities implement json.Marshaler and json.Unmarshaler interfaces, so you can use standard Go JSON functions:
temp := unit.NewTemperature(25, unit.Temperature.Celsius)
// Serialize using json.Marshal
data, err := json.Marshal(temp)
// Output: {"value":25,"unit":{"name":"Celsius","symbol":"°C"},"dimension":"temperature"}
// Deserialize using json.Unmarshal
var temp2 unit.Quantity[unit.TemperatureUnit]
err = json.Unmarshal(data, &temp2)You can also use the explicit functions for each dimension:
// Serialize a temperature quantity to JSON
tempJSON, err := unit.MarshalTemperature(temp)
if err != nil {
// Handle error
}
// Deserialize a temperature quantity from JSON
tempDeserialized, err := unit.UnmarshalTemperature(tempJSON)
if err != nil {
// Handle error
}When you don't know the dimension in advance, you can use the generic UnmarshalQuantity function:
// Deserialize a quantity without knowing its dimension in advance
anyQuantity, err := unit.UnmarshalQuantity(jsonData)
if err != nil {
// Handle error
}
// Check the dimension
dimension := anyQuantity.GetDimension()
fmt.Println("Dimension:", dimension)
// Convert to the specific type based on the dimension
switch dimension {
case "temperature":
temp, ok := anyQuantity.AsTemperature()
if ok {
fmt.Println("Temperature:", temp)
}
case "length":
length, ok := anyQuantity.AsLength()
if ok {
fmt.Println("Length:", length)
}
case "volume":
volume, ok := anyQuantity.AsVolume()
if ok {
fmt.Println("Volume:", volume)
}
// ... handle other dimensions
}This approach is useful when processing quantities from external sources where the dimension isn't known until runtime.
This allows quantities to be easily stored, transmitted, and reconstructed:
// Example of embedding quantities in a larger JSON structure
type SensorReading struct {
ID string `json:"id"`
Timestamp string `json:"timestamp"`
Temperature json.RawMessage `json:"temperature,omitempty"`
Pressure json.RawMessage `json:"pressure,omitempty"`
}
// Create a sensor reading with temperature
tempJSON, _ := unit.MarshalTemperature(temp)
reading := SensorReading{
ID: "sensor1",
Timestamp: "2023-06-15T12:34:56Z",
Temperature: tempJSON,
}
// Serialize the entire reading
readingJSON, _ := json.Marshal(reading)
// readingJSON contains the sensor ID, timestamp, and serialized temperatureFor systems that prefer snake_case keys (e.g., NATS subjects), use the Compact[T] wrapper type:
temp := unit.NewTemperature(25, unit.Temperature.Celsius)
// Serialize using Compact wrapper with json.Marshal
data, err := json.Marshal(unit.Compact[unit.TemperatureUnit]{temp})
// Output: {"value":25,"unit":"temperature_celsius","symbol":"°C"}
// Deserialize using json.Unmarshal
var compact unit.Compact[unit.TemperatureUnit]
err = json.Unmarshal(data, &compact)
temp2 := compact.QuantityYou can also use the explicit compact functions:
// Compact format: {"value":25,"unit":"temperature_celsius"}
data, err := unit.MarshalCompactTemperature(temp)
// With symbol included: {"value":25,"unit":"temperature_celsius","symbol":"°C"}
data, err := unit.MarshalCompactTemperatureWithSymbol(temp)
// Deserialize compact format
temp, err := unit.UnmarshalCompactTemperature(data)The generic UnmarshalMeasurement function auto-detects the format:
// Works with both standard and compact formats
am, err := unit.UnmarshalMeasurement(jsonData)| Type | JSON Output |
|---|---|
Quantity[T] |
{"value":25,"unit":{"name":"Celsius","symbol":"°C"},"dimension":"temperature"} |
Compact[T] |
{"value":25,"unit":"temperature_celsius","symbol":"°C"} |
The package supports defining custom units for project-specific needs using the GeneralUnit type:
// Create a custom unit
customUnit := unit.NewGeneralUnit("xyz", "Custom Unit")
// Create a quantity with the custom unit
myQuantity := unit.NewGeneral(42.0, customUnit)
// Create a unit with conversion factors
// For example: 1 abc = 2 xyz + 10
convertibleUnit := unit.NewGeneralUnitWithConversion("abc", "Another Unit", 2.0, 10.0)For more advanced extension options, including creating your own quantity types in your project, see the EXTENDING.md documentation.
- Always specify units when defining points that represent physical quantities
- Use the appropriate dimension for each physical quantity
- Avoid mixing dimensions in calculations
- When displaying values in UIs, show the unit symbol
- Allow users to choose their preferred units for display
- Compound units: Support for units like kWh/m² (energy per area)
- Dimensional analysis: Automatic tracking of dimensions in calculations
- Uncertainty: Track and propagate measurement uncertainty