andanteproject/measurement

Modern, type-safe PHP library for handling physical measurements with automatic unit conversion and internationalization

Installs: 0

Dependents: 0

Suggesters: 0

Security: 0

Stars: 2

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/andanteproject/measurement

v1.0.0 2026-01-28 20:38 UTC

This package is auto-updated.

Last update: 2026-01-28 20:42:08 UTC


README

Andante Project Logo

Measurement

PHP Library - AndanteProject

Latest Version CI Php8 PhpStan

A modern, type-safe PHP library for handling physical measurements with automatic unit conversion, internationalization, and precise calculations.

Table of Contents

Features

  • Type-Safe Quantities - Three levels of type safety (dimension, system, unit) with compile-time type checking and full IDE autocompletion
  • Dimensional Analysis - Automatic type resolution (Length × Length = Area, Length ÷ Time = Velocity) that prevents dimensional errors at compile time
  • Arbitrary Precision - Exact calculations using brick/math with no floating-point precision errors, handles very large and very small numbers accurately
  • Auto-Scaling - Smart unit selection (1000 g1 kg) that automatically chooses the most appropriate unit representation across all quantity types
  • String Parsing - Parse strings like "100 km", "5.5 kWh", "25 °C" with locale-aware number parsing, supports unit symbols and full unit names
  • Flexible Formatting - Multiple output styles (Short, Long, Narrow, Value-only, Unit-only) with locale-aware number formatting and translated unit names
  • Internationalization - 9 locales (en, de, es, fr, it, pt, ja, ru, zh) with separate locale support for numbers and unit names
  • Locale-Aware Numbers - Parse "1.500,5 m" (IT) or "1,500.5 m" (US) with custom thousand and decimal separators
  • Unit Systems - Complete SI & Metric support, full Imperial unit support with conversions, and Digital units with both SI (decimal) and IEC (binary) prefixes
  • Extensible - Easy to add custom dimensions and units with provider-based registration system and full integration with existing library features
  • Modern PHP 8.1+ - Enums, readonly properties, strict types, PHPStan level 9, immutable value objects, trait-based functionality composition

Installation

composer require andanteproject/measurement

Requirements

  • PHP 8.1 or higher
  • brick/math (for arbitrary precision arithmetic)

Quick Start

use Andante\Measurement\Math\NumberFactory;
use Andante\Measurement\Quantity\Length\Metric\Meter;
use Andante\Measurement\Quantity\Length\Metric\Kilometer;
use Andante\Measurement\Quantity\Energy\Electric\KilowattHour;
use Andante\Measurement\Quantity\Volume\Metric\CubicMeter;
use Andante\Measurement\Unit\Length\MetricLengthUnit;
use Andante\Measurement\Unit\Energy\SIEnergyUnit;
use Andante\Measurement\Parser\Parser;
use Andante\Measurement\Parser\ParseOptions;
use Andante\Measurement\Formatter\Formatter;
use Andante\Measurement\Formatter\FormatOptions;
use Andante\Measurement\Formatter\FormatStyle;
use Andante\Measurement\Unit\SymbolNotation;

// Create quantities using unit-specific classes
$distance = Meter::of(NumberFactory::create('5000'));
$energy = KilowattHour::of(NumberFactory::create('150'));

// Convert to different units
$inKilometers = $distance->to(MetricLengthUnit::Kilometer);  // 5 km
$inJoules = $energy->to(SIEnergyUnit::Joule);                 // 540,000,000 J

// Arithmetic operations
$doubled = $distance->multiplyBy(NumberFactory::create('2'));  // 10000 m
$sum = $distance->add(Kilometer::of(NumberFactory::create('2')));  // 7000 m
$diff = $distance->subtract(Kilometer::of(NumberFactory::create('1')));  // 4000 m
$half = $distance->divideBy(NumberFactory::create('2'));  // 2500 m

// Type-safe: incompatible types won't compile
// $distance->add($energy);  // ❌ Type error!

// Auto-scale to the most readable unit
$largeDistance = Meter::of(NumberFactory::create('5000'));
$scaled = $largeDistance->autoScale();  // 5 km

// Parse from strings with options
$parser = Parser::global();

// Simple parsing (no options)
$parsed = $parser->parse('100 km');

// Parse with Italian locale (for number formatting)
$parsed = $parser->parse(
    '1.234,56 m',
    ParseOptions::fromLocale('it_IT')
);  // Italian: 1234.56 m

// Parse with Italian locale (different number)
$parsed = $parser->parse(
    '1.500,5 km',
    ParseOptions::fromLocale('it_IT')
);  // Italian: 1500.5 km

// Parse with default unit (for numbers without units)
$parsed = $parser->parse(
    '100',
    ParseOptions::create()
        ->withDefaultUnit(MetricLengthUnit::Meter)
);  // 100 m

// Parse with custom thousand separator
$parsed = $parser->parse(
    '1 234.56 m',
    ParseOptions::create()
        ->withThousandSeparator(' ')
        ->withDecimalSeparator('.')
);  // 1234.56 m

// Parse with custom decimal separator
$parsed = $parser->parse(
    '1234,56 m',
    ParseOptions::create()
        ->withDecimalSeparator(',')
);  // 1234.56 m

// Parse with locale and default unit
$parsed = $parser->parse(
    '1.500',
    ParseOptions::fromLocale('it_IT')
        ->withDefaultUnit(MetricLengthUnit::Kilometer)
);  // 1500 km

// Parse with all custom separators
$parsed = $parser->parse(
    '1_234|56 m',
    ParseOptions::create()
        ->withThousandSeparator('_')
        ->withDecimalSeparator('|')
);  // 1234.56 m

// Format with options
$formatter = Formatter::global();

// Default formatting (short style with symbol)
echo $formatter->format($distance);  // "5,000 m"

// Format with Italian locale
echo $formatter->format(
    $distance,
    FormatOptions::fromLocale('it_IT')
);  // "5.000 m"

// Format with long style (translated unit names)
echo $formatter->format(
    $distance,
    FormatOptions::create()
        ->withStyle(FormatStyle::Long)
);  // "5,000 meters"

// Format with long style and Italian locale
echo $formatter->format(
    $distance,
    FormatOptions::fromLocale('it_IT')
        ->withStyle(FormatStyle::Long)
);  // "5.000 metri"

// Format with narrow style (no space)
echo $formatter->format(
    $distance,
    FormatOptions::create()
        ->withStyle(FormatStyle::Narrow)
);  // "5,000m"

// Format with narrow style and Italian locale
echo $formatter->format(
    $distance,
    FormatOptions::fromLocale('it_IT')
        ->withStyle(FormatStyle::Narrow)
);  // "5.000m"

// Format with value only (no unit)
echo $formatter->format(
    $distance,
    FormatOptions::create()
        ->withStyle(FormatStyle::ValueOnly)
);  // "5,000"

// Format with unit symbol only
echo $formatter->format(
    $distance,
    FormatOptions::create()
        ->withStyle(FormatStyle::UnitSymbolOnly)
);  // "m"

// Format with unit name only
echo $formatter->format(
    $distance,
    FormatOptions::create()
        ->withStyle(FormatStyle::UnitNameOnly)
);  // "meters"

// Format with unit name only and Italian locale
echo $formatter->format(
    $distance,
    FormatOptions::fromLocale('it_IT')
        ->withStyle(FormatStyle::UnitNameOnly)
);  // "metri"

// Format with fixed precision
echo $formatter->format(
    $distance,
    FormatOptions::create()
        ->withPrecision(2)
);  // "5,000.00 m"

// Format with precision and Italian locale
echo $formatter->format(
    $distance,
    FormatOptions::fromLocale('it_IT')
        ->withPrecision(2)
);  // "5.000,00 m"

// Format with precision and long style
echo $formatter->format(
    $distance,
    FormatOptions::create()
        ->withPrecision(3)
        ->withStyle(FormatStyle::Long)
);  // "5,000.000 meters"

// Format with custom thousand separator
echo $formatter->format(
    $distance,
    FormatOptions::create()
        ->withThousandSeparator(' ')
);  // "5 000 m"

// Format with custom decimal separator
$smallDistance = Meter::of(NumberFactory::create('1234.56'));
echo $formatter->format(
    $smallDistance,
    FormatOptions::create()
        ->withDecimalSeparator(',')
);  // "1,234,56 m"

// Format with custom separators
echo $formatter->format(
    $smallDistance,
    FormatOptions::create()
        ->withThousandSeparator('_')
        ->withDecimalSeparator('|')
);  // "1_234|56 m"

// Format with separate unit locale (Italian numbers, English units)
echo $formatter->format(
    $distance,
    FormatOptions::fromLocale('it_IT')
        ->withUnitLocale('en_US')
        ->withStyle(FormatStyle::Long)
);  // "5.000 meters" (Italian numbers, English unit name)

// Format with IEEE symbol notation
$energy = KilowattHour::of(NumberFactory::create('150'));
echo $formatter->format(
    $energy,
    FormatOptions::create()
        ->withSymbolNotation(SymbolNotation::IEEE)
);  // "150 kW·h"

// Format with ASCII symbol notation
echo $formatter->format(
    $energy,
    FormatOptions::create()
        ->withSymbolNotation(SymbolNotation::ASCII)
);  // "150 kW*h"

// Format with Unicode symbol notation
$volume = CubicMeter::of(NumberFactory::create('1000000'));
echo $formatter->format(
    $volume,
    FormatOptions::create()
        ->withSymbolNotation(SymbolNotation::Unicode)
);  // "1,000,000 m³"

// Format with multiple options combined
echo $formatter->format(
    $smallDistance,
    FormatOptions::fromLocale('it_IT')
        ->withStyle(FormatStyle::Long)
        ->withPrecision(2)
);  // "1.234,56 metri"

// Format with all options
echo $formatter->format(
    $distance,
    FormatOptions::fromLocale('it_IT')
        ->withUnitLocale('en_US')
        ->withStyle(FormatStyle::Long)
        ->withPrecision(3)
        ->withSymbolNotation(SymbolNotation::Unicode)
);  // "5.000,000 meters"

Core Concepts

Dimension vs Unit vs Quantity

  • Dimension: The physical nature (Length, Mass, Energy)
  • Unit: A specific scale (meter, kilometer, foot)
  • Quantity: A value + unit (5 meters)

Three Levels of Type Safety

The library provides three levels of specificity for type-hinting quantities. This allows you to be as strict or flexible as your use case requires - from accepting any length unit, to requiring a specific measurement system, to enforcing an exact unit.

use Andante\Measurement\Quantity\Length\Length;
use Andante\Measurement\Quantity\Length\MetricLength;
use Andante\Measurement\Quantity\Length\Metric\Meter;
use Andante\Measurement\Unit\Length\MetricLengthUnit;

// Level 1: Generic - accepts any length unit (meters, feet, miles, etc.)
function calculateArea(Length $width, Length $height): Area {
    return $width->multiply($height);
}

// Level 2: System-specific - only metric units allowed (meters, kilometers, etc.)
function europeanDistance(MetricLength $distance): void {
    // Rejects imperial units like feet or miles at compile time
}

// Level 3: Unit-specific - only meters, nothing else
function precisionMeasurement(Meter $length): void {
    // Even kilometers or centimeters are rejected
}

Creating Quantities

There are multiple ways to create quantities, each corresponding to the three levels of type safety. Use NumberFactory::create() to create precise numeric values from strings, integers, or floats.

use Andante\Measurement\Math\NumberFactory;
use Andante\Measurement\Quantity\Length\Length;
use Andante\Measurement\Quantity\Length\MetricLength;
use Andante\Measurement\Quantity\Length\Metric\Meter;
use Andante\Measurement\Unit\Length\MetricLengthUnit;
use Andante\Measurement\Unit\Length\ImperialLengthUnit;

// Unit-specific class - most type-safe
$meter = Meter::of(NumberFactory::create('100'));

// Mid-level class with unit - system-constrained
$length = MetricLength::of(
    NumberFactory::create('100'),
    MetricLengthUnit::Kilometer
);

// Generic class with any unit - most flexible
$imperial = Length::of(
    NumberFactory::create('5280'),
    ImperialLengthUnit::Foot
);

Working with Quantities

Once you have quantity objects, you can convert between units, perform arithmetic, compare values, and more. All operations are immutable - they return new quantity objects rather than modifying the original.

Conversions

Use the to() method to convert a quantity to a different unit within the same dimension. The conversion is handled automatically using the library's conversion factor registry.

use Andante\Measurement\Unit\Length\MetricLengthUnit;
use Andante\Measurement\Unit\Length\ImperialLengthUnit;

$meters = Meter::of(NumberFactory::create('1000'));

// Convert to another unit in the same dimension
$kilometers = $meters->to(MetricLengthUnit::Kilometer);  // 1 km
$feet = $meters->to(ImperialLengthUnit::Foot);           // 3280.84 ft

// Get the numeric value
echo $kilometers->getValue()->value();  // "1"
echo $kilometers->getUnit()->symbol();  // "km"

Arithmetic

Quantities support addition, subtraction, multiplication by scalars, and division by scalars. When adding or subtracting quantities with different units, the right operand is automatically converted to the left operand's unit.

$a = Meter::of(NumberFactory::create('100'));
$b = Meter::of(NumberFactory::create('50'));

// Same-unit arithmetic
$sum = $a->add($b);                                    // 150 m
$diff = $a->subtract($b);                              // 50 m
$scaled = $a->multiplyBy(NumberFactory::create('2'));  // 200 m
$half = $a->divideBy(NumberFactory::create('2'));      // 50 m

// Cross-unit arithmetic (auto-converts to left operand's unit)
$km = Kilometer::of(NumberFactory::create('1'));
$result = $km->add($a);  // 1.1 km (100m converted to 0.1km)

Dimensional Analysis

When multiplying or dividing quantities, the library automatically determines the result type:

use Andante\Measurement\Quantity\Length\Metric\Meter;
use Andante\Measurement\Quantity\Time\SI\Second;
use Andante\Measurement\Quantity\Mass\Metric\Kilogram;

// Length × Length = Area
$width = Meter::of(NumberFactory::create('5'));
$height = Meter::of(NumberFactory::create('3'));
$area = $width->multiply($height);  // SquareMeter (15 m²)

// Length ÷ Time = Velocity
$distance = Meter::of(NumberFactory::create('100'));
$time = Second::of(NumberFactory::create('10'));
$velocity = $distance->divide($time);  // MeterPerSecond (10 m/s)

// Mass × Acceleration = Force
$mass = Kilogram::of(NumberFactory::create('10'));
$accel = MeterPerSecondSquared::of(NumberFactory::create('9.8'));
$force = $mass->multiply($accel);  // Newton (98 N)

Comparisons

Compare quantities using intuitive methods. Comparisons work across different units - the library automatically converts to a common unit before comparing.

$a = Meter::of(NumberFactory::create('100'));
$b = Meter::of(NumberFactory::create('50'));

$a->isGreaterThan($b);     // true
$a->isLessThan($b);        // false
$a->equals($b);            // false
$a->isGreaterOrEqual($b);  // true

// Cross-unit comparisons work too
$km = Kilometer::of(NumberFactory::create('1'));
$a->isLessThan($km);  // true (100m < 1000m)

Auto-Scaling

The autoScale() method automatically converts a quantity to the most human-readable unit. It prefers values between 1 and 1000, choosing larger or smaller units as appropriate.

// Automatically choose the most readable unit
$grams = Gram::of(NumberFactory::create('5000'));
$scaled = $grams->autoScale();  // Kilogram (5 kg)

$bytes = Byte::of(NumberFactory::create('1048576'));
$scaled = $bytes->autoScale();  // Megabyte (1 MB)

// Works with all quantity types
$watts = Watt::of(NumberFactory::create('1500000'));
$scaled = $watts->autoScale();  // Megawatt (1.5 MW)

Parsing and Formatting

The library includes powerful parsing and formatting capabilities with full internationalization support. Parse measurement strings from user input, and format quantities for display in any locale.

Parsing Strings

Convert strings like "100 km" or "5.5 kWh" into quantity objects. The parser handles unit symbols, full unit names, and locale-specific number formats.

use Andante\Measurement\Parser\Parser;
use Andante\Measurement\Parser\ParseOptions;

$parser = Parser::global();

// Simple parsing
$length = $parser->parse('100 km');
$energy = $parser->parse('5.5 kWh');
$temp = $parser->parse('25 °C');

// With locale (for number formatting)
$options = ParseOptions::fromLocale('it_IT');
$length = $parser->parse('1.234,56 m', $options);  // Italian: 1234.56 m

$options = ParseOptions::fromLocale('en_US');
$length = $parser->parse('1,234.56 m', $options);  // US: 1234.56 m

// With default unit (for numbers without units)
$options = ParseOptions::create()->withDefaultUnit(MetricLengthUnit::Meter);
$length = $parser->parse('100', $options);  // 100 m

// Safe parsing (returns null on failure)
$result = $parser->tryParse('invalid');  // null

ParseOptions Reference

Option Method Default Description
locale fromLocale($locale) / withLocale($locale) null Locale for number formatting (e.g., 'it_IT', 'de_DE'). Determines thousand/decimal separators via ICU.
thousandSeparator withThousandSeparator($sep) ',' Character used as thousand separator. Overrides locale setting.
decimalSeparator withDecimalSeparator($sep) '.' Character used as decimal separator. Overrides locale setting.
defaultUnit withDefaultUnit($unit) null Unit to assume when parsing numbers without unit symbols (e.g., '100'100 m).

Formatting Output

Format quantities for display with control over style, precision, and locale. The formatter supports multiple styles (Short, Long, Narrow) and translates unit names into 9 languages.

use Andante\Measurement\Formatter\Formatter;
use Andante\Measurement\Formatter\FormatOptions;
use Andante\Measurement\Formatter\FormatStyle;

$formatter = Formatter::global();
$length = Meter::of(NumberFactory::create('1500'));

// Default (short style with symbol)
echo $formatter->format($length);  // "1,500 m"

// Long style with unit name
$options = FormatOptions::create()->withStyle(FormatStyle::Long);
echo $formatter->format($length, $options);  // "1,500 meters"

// With locale
$options = FormatOptions::fromLocale('de_DE');
echo $formatter->format($length, $options);  // "1.500 m"

// Long style with locale (translated unit names)
$options = FormatOptions::fromLocale('it_IT')->withStyle(FormatStyle::Long);
echo $formatter->format($length, $options);  // "1.500 metri"

// Fixed precision
$options = FormatOptions::create()->withPrecision(2);
echo $formatter->format($length, $options);  // "1,500.00 m"

// Narrow style (no space between number and unit)
$options = FormatOptions::create()->withStyle(FormatStyle::Narrow);
echo $formatter->format($length, $options);  // "1,500m"

FormatOptions Reference

Option Method Default Description
locale fromLocale($locale) / withLocale($locale) null Locale for number formatting (thousand/decimal separators).
unitLocale withUnitLocale($locale) same as locale Separate locale for unit name translations. Allows Italian numbers with English unit names.
precision withPrecision($int) null (auto) Number of decimal places. null preserves input precision and removes trailing zeros.
thousandSeparator withThousandSeparator($sep) ',' Character used as thousand separator. Overrides locale setting.
decimalSeparator withDecimalSeparator($sep) '.' Character used as decimal separator. Overrides locale setting.
style withStyle($style) FormatStyle::Short Output style (see FormatStyle table below).
notation withSymbolNotation($notation) SymbolNotation::Default Symbol notation style (see SymbolNotation table below).

FormatStyle Options

Style Example Description
FormatStyle::Short "1,500 m" Unit symbol with space (default).
FormatStyle::Long "1,500 meters" Full unit name, translated if available.
FormatStyle::Narrow "1,500m" Unit symbol without space.
FormatStyle::ValueOnly "1,500" Numeric value only, no unit. Useful for charts and data export.
FormatStyle::UnitSymbolOnly "m" Unit symbol only, no value. Useful for table headers.
FormatStyle::UnitNameOnly "meters" Full unit name only, no value. Useful for legends.

SymbolNotation Options

Notation Example Description
SymbolNotation::Default "Gbps", "kWh", "m³" Most common/recognizable form.
SymbolNotation::IEEE "Gbit/s", "kW·h" Technical/standards-compliant form.
SymbolNotation::ASCII "m3", "um", "kW*h" Keyboard-friendly, no special characters.
SymbolNotation::Unicode "m³", "μm", "kW·h" Proper Unicode symbols.

Registries

The library uses four registries to manage units, conversions, and dimensional analysis. All registries are pre-configured with built-in quantities.

UnitRegistry

Maps units to their quantity classes. Used for parsing and creating quantities.

use Andante\Measurement\Registry\UnitRegistry;

$registry = UnitRegistry::global();

// Find a unit by symbol
$unit = $registry->findBySymbol('km');  // MetricLengthUnit::Kilometer

// Get the quantity class for a unit
$class = $registry->getQuantityClass(MetricLengthUnit::Meter);  // Meter::class

// Get all units for a dimension
$lengthUnits = $registry->getUnitsForDimension(Length::instance());

// Get only metric units
$metricUnits = $registry->getMetricUnits(Length::instance());

ConversionFactorRegistry

Stores conversion factors from each unit to its dimension's base unit.

use Andante\Measurement\Registry\ConversionFactorRegistry;

$registry = ConversionFactorRegistry::global();

// Get conversion rule for a unit
$rule = $registry->get(MetricLengthUnit::Kilometer);
// factor: 1000 (1 km = 1000 m)

// Temperature uses affine conversions (factor + offset)
$rule = $registry->get(TemperatureUnit::Celsius);
// factor: 1, offset: 273.15 (K = °C + 273.15)

ResultQuantityRegistry

Determines what quantity class to return from dimensional operations.

use Andante\Measurement\Registry\ResultQuantityRegistry;

$registry = ResultQuantityRegistry::global();

// What class should Meter × Meter return?
$resultClass = $registry->getResultClass(
    Meter::class,
    new DimensionalFormula(length: 2)  // L²
);
// Returns: MetricArea::class (preserves metric system)

FormulaUnitRegistry

Maps dimensional formulas to default units for each system. When the library creates a result from dimensional analysis, it uses this registry to determine which unit to express the result in.

use Andante\Measurement\Registry\FormulaUnitRegistry;

$registry = FormulaUnitRegistry::global();

// Default unit for velocity (L¹T⁻¹)
$unit = $registry->getDefaultUnit(new DimensionalFormula(length: 1, time: -1));
// Returns: MetricVelocityUnit::MeterPerSecond

// Imperial default
$unit = $registry->getDefaultUnit(
    new DimensionalFormula(length: 1, time: -1),
    UnitSystem::Imperial
);
// Returns: ImperialVelocityUnit::FootPerSecond

QuantityDefaultConfigProviderInterface

The QuantityDefaultConfigProviderInterface is the recommended way to register custom quantities with all four registries at once. Instead of manually registering with each registry, implement this interface to centralize your quantity's configuration.

use Andante\Measurement\Contract\Registry\QuantityDefaultConfigProviderInterface;
use Andante\Measurement\Registry\UnitRegistry;
use Andante\Measurement\Registry\ConversionFactorRegistry;
use Andante\Measurement\Registry\ResultQuantityRegistry;
use Andante\Measurement\Registry\FormulaUnitRegistry;

final class MyQuantityProvider implements QuantityDefaultConfigProviderInterface
{
    public function registerUnits(UnitRegistry $registry): void
    {
        // Register unit → quantity class mappings
    }

    public function registerConversionFactors(ConversionFactorRegistry $registry): void
    {
        // Register unit → base unit conversion factors
    }

    public function registerResultMappings(ResultQuantityRegistry $registry): void
    {
        // Register dimensional formula → result class mappings
    }

    public function registerFormulaUnits(FormulaUnitRegistry $registry): void
    {
        // Register dimensional formula → default unit mappings
    }
}

// Register with all registries at once
$provider = MyQuantityProvider::global();
$provider->registerUnits(UnitRegistry::global());
$provider->registerConversionFactors(ConversionFactorRegistry::global());
$provider->registerResultMappings(ResultQuantityRegistry::global());
$provider->registerFormulaUnits(FormulaUnitRegistry::global());

Adding Custom Quantities

The library is fully extensible - you can add your own quantity types that integrate seamlessly with the existing system. This section walks through creating a custom "Viscosity" quantity as an example. The same pattern applies to any physical quantity you need to add.

Step 1: Create the Dimension

A dimension represents the physical nature of a quantity, defined by its dimensional formula using the seven SI base dimensions: Length (L), Mass (M), Time (T), Electric Current (I), Temperature (Θ), Amount of Substance (N), and Luminous Intensity (J).

Examples of dimensional formulas:

  • Velocity = L¹T⁻¹ (length per time)
  • Force = L¹M¹T⁻² (mass times acceleration)
  • Pressure = L⁻¹M¹T⁻² (force per area)
  • Viscosity = L⁻¹M¹T⁻¹ (used in this example)
use Andante\Measurement\Contract\DimensionInterface;
use Andante\Measurement\Dimension\DimensionalFormula;

final class Viscosity implements DimensionInterface
{
    private static ?self $instance = null;
    private static ?DimensionalFormula $formula = null;

    private function __construct() {}

    public static function instance(): self
    {
        return self::$instance ??= new self();
    }

    public function getFormula(): DimensionalFormula
    {
        // Dynamic viscosity: M¹L⁻¹T⁻¹ (kg/(m·s))
        return self::$formula ??= new DimensionalFormula(
            mass: 1,
            length: -1,
            time: -1
        );
    }
}

Step 2: Create the Unit Enum

Units are implemented as PHP enums that implement UnitInterface. Each unit defines its symbol, name, dimension, and measurement system (SI, Metric, or Imperial). The enum pattern provides type safety and IDE autocompletion.

use Andante\Measurement\Contract\UnitInterface;
use Andante\Measurement\Unit\UnitSystem;
use Andante\Measurement\Unit\SymbolNotation;

enum ViscosityUnit: string implements UnitInterface
{
    case PascalSecond = 'Pa·s';
    case Poise = 'P';
    case Centipoise = 'cP';

    public function symbol(SymbolNotation $notation = SymbolNotation::Default): string
    {
        return match ($notation) {
            SymbolNotation::ASCII => match ($this) {
                self::PascalSecond => 'Pa*s',
                self::Poise => 'P',
                self::Centipoise => 'cP',
            },
            default => $this->value,
        };
    }

    public function name(): string
    {
        return match ($this) {
            self::PascalSecond => 'Pascal second',
            self::Poise => 'Poise',
            self::Centipoise => 'Centipoise',
        };
    }

    public function dimension(): DimensionInterface
    {
        return Viscosity::instance();
    }

    public function system(): UnitSystem
    {
        return UnitSystem::SI;
    }
}

Step 3: Create the Quantity Interface

Create an interface that extends QuantityInterface. This enables type-hinting for your quantity in function signatures and allows for the three-level type safety pattern (generic → system-specific → unit-specific).

use Andante\Measurement\Contract\QuantityInterface;

interface ViscosityInterface extends QuantityInterface
{
}

Step 4: Create Quantity Classes

Create the actual quantity classes that hold values. Use the provided traits (ConvertibleTrait, ComparableTrait, CalculableTrait, AutoScalableTrait) to get conversion, comparison, arithmetic, and auto-scaling functionality without writing boilerplate code.

use Andante\Measurement\Contract\Math\NumberInterface;
use Andante\Measurement\Contract\UnitInterface;
use Andante\Measurement\Contract\QuantityFactoryInterface;
use Andante\Measurement\Contract\ConvertibleInterface;
use Andante\Measurement\Contract\ComparableInterface;
use Andante\Measurement\Contract\CalculableInterface;
use Andante\Measurement\Quantity\Trait\ConvertibleTrait;
use Andante\Measurement\Quantity\Trait\ComparableTrait;
use Andante\Measurement\Quantity\Trait\CalculableTrait;
use Andante\Measurement\Exception\InvalidUnitException;

// Unit-specific class
final class PascalSecond implements
    ViscosityInterface,
    QuantityFactoryInterface,
    ConvertibleInterface,
    ComparableInterface,
    CalculableInterface
{
    use ConvertibleTrait;
    use ComparableTrait;
    use CalculableTrait;

    private function __construct(
        private readonly NumberInterface $value,
        private readonly UnitInterface $unit,
    ) {}

    public static function of(NumberInterface $value): self
    {
        return new self($value, ViscosityUnit::PascalSecond);
    }

    public static function from(NumberInterface $value, UnitInterface $unit): self
    {
        if (ViscosityUnit::PascalSecond !== $unit) {
            throw InvalidUnitException::forInvalidUnit($unit, ViscosityUnit::PascalSecond, self::class);
        }
        return new self($value, $unit);
    }

    public function getValue(): NumberInterface
    {
        return $this->value;
    }

    public function getUnit(): UnitInterface
    {
        return $this->unit;
    }
}

Step 5: Create a Provider

The provider centralizes all registry configuration for your quantity. It defines which units exist, their conversion factors to the base unit, and how dimensional analysis results should be mapped. This is the recommended pattern used by all built-in quantities.

use Andante\Measurement\Contract\Registry\QuantityDefaultConfigProviderInterface;
use Andante\Measurement\Converter\ConversionRule;
use Andante\Measurement\Math\NumberFactory;

final class ViscosityProvider implements QuantityDefaultConfigProviderInterface
{
    private static ?self $instance = null;

    private function __construct() {}

    public static function global(): self
    {
        return self::$instance ??= new self();
    }

    private function getUnits(): array
    {
        return [
            [ViscosityUnit::PascalSecond, PascalSecond::class, '1'],
            [ViscosityUnit::Poise, Poise::class, '0.1'],
            [ViscosityUnit::Centipoise, Centipoise::class, '0.001'],
        ];
    }

    public function registerUnits(UnitRegistry $registry): void
    {
        foreach ($this->getUnits() as [$unit, $quantityClass, $factor]) {
            $registry->register($unit, $quantityClass);
        }
    }

    public function registerConversionFactors(ConversionFactorRegistry $registry): void
    {
        foreach ($this->getUnits() as [$unit, $quantityClass, $factor]) {
            $registry->register($unit, ConversionRule::factor(NumberFactory::create($factor)));
        }
    }

    public function registerResultMappings(ResultQuantityRegistry $registry): void
    {
        $formula = Viscosity::instance()->getFormula();

        foreach ($this->getUnits() as [$unit, $quantityClass, $factor]) {
            $registry->register($quantityClass, $formula, DynamicViscosity::class);
        }

        $registry->registerGeneric($formula, DynamicViscosity::class);
    }

    public function registerFormulaUnits(FormulaUnitRegistry $registry): void
    {
        $registry->register(
            Viscosity::instance()->getFormula(),
            ViscosityUnit::PascalSecond
        );
    }
}

Step 6: Register with the Library

Call your provider's registration methods during application bootstrap to make your quantity available throughout the library. After registration, parsing, formatting, conversions, and dimensional analysis will all work with your custom quantity.

// In your application bootstrap
ViscosityProvider::global()->registerUnits(UnitRegistry::global());
ViscosityProvider::global()->registerConversionFactors(ConversionFactorRegistry::global());
ViscosityProvider::global()->registerResultMappings(ResultQuantityRegistry::global());
ViscosityProvider::global()->registerFormulaUnits(FormulaUnitRegistry::global());

// Now you can use your custom quantity!
$viscosity = PascalSecond::of(NumberFactory::create('0.001'));
$inCentipoise = $viscosity->to(ViscosityUnit::Centipoise);  // 1 cP

Step 7: Add Translations (Optional)

To enable localized formatting with FormatStyle::Long, register translations programmatically using the TranslationLoader::registerTranslation() method.

use Andante\Measurement\Translation\TranslationLoader;

// Get or create a translation loader for your locale
$loader = new TranslationLoader('en');

// Register translations for your custom units
$loader->registerTranslation(ViscosityUnit::PascalSecond, [
    'one' => 'pascal second',
    'other' => 'pascal seconds',
]);
$loader->registerTranslation(ViscosityUnit::Poise, [
    'one' => 'poise',
    'other' => 'poise',
]);
$loader->registerTranslation(ViscosityUnit::Centipoise, [
    'one' => 'centipoise',
    'other' => 'centipoise',
]);

// Use the loader with the formatter
$formatter = new Formatter($loader);

The registerTranslation() method accepts a unit and an array mapping plural rules (one, other) to translated names. Registered translations take precedence over built-in translations, so you can also use this to override existing unit names if needed.

Available Quantities

The library provides 29 quantity types with 200+ units. Each quantity includes:

  • A generic class accepting any unit in the dimension
  • Mid-level classes for system-specific type safety (Metric/Imperial/SI)
  • Unit-specific classes for maximum type safety

Length [L¹]

The SI base unit of distance. Supports metric and imperial systems.

Scope Class Unit Symbol
Generic Length any -
System-specific MetricLength any metric (MetricLengthUnit) -
System-specific ImperialLength any imperial (ImperialLengthUnit) -
Unit-specific Meter Meter m
Unit-specific Kilometer Kilometer km
Unit-specific Hectometer Hectometer hm
Unit-specific Decameter Decameter dam
Unit-specific Decimeter Decimeter dm
Unit-specific Centimeter Centimeter cm
Unit-specific Millimeter Millimeter mm
Unit-specific Micrometer Micrometer μm
Unit-specific Nanometer Nanometer nm
Unit-specific Foot Foot ft
Unit-specific Inch Inch in
Unit-specific Yard Yard yd
Unit-specific Mile Mile mi
Unit-specific NauticalMile NauticalMile nmi

Mass [M¹]

The SI base unit of mass. Supports metric and imperial systems.

Scope Class Unit Symbol
Generic Mass any -
System-specific MetricMass any metric (MetricMassUnit) -
System-specific ImperialMass any imperial (ImperialMassUnit) -
Unit-specific Kilogram Kilogram kg
Unit-specific Gram Gram g
Unit-specific Milligram Milligram mg
Unit-specific Microgram Microgram μg
Unit-specific Tonne Tonne t
Unit-specific Hectogram Hectogram hg
Unit-specific Decagram Decagram dag
Unit-specific Decigram Decigram dg
Unit-specific Centigram Centigram cg
Unit-specific Pound Pound lb
Unit-specific Ounce Ounce oz
Unit-specific Stone Stone st
Unit-specific ShortTon ShortTon ton
Unit-specific LongTon LongTon long ton

Time [T¹]

The SI base unit of time. All Time classes include PHP DateInterval integration:

  • toPhpDateInterval() - convert to PHP \DateInterval
  • ofPhpDateInterval(\DateInterval $interval) - create from PHP \DateInterval
// Convert to DateInterval
$hours = Hour::of(NumberFactory::create('2.5'));
$interval = $hours->toPhpDateInterval(); // PT2H30M

// Create from DateInterval
$interval = new \DateInterval('PT1H30M45S');
$seconds = Second::ofPhpDateInterval($interval);
// $seconds->getValue()->value() = '5445'
Scope Class Unit Symbol
Generic Time any (TimeUnit) -
Unit-specific Second Second s
Unit-specific Millisecond Millisecond ms
Unit-specific Microsecond Microsecond μs
Unit-specific Nanosecond Nanosecond ns
Unit-specific Minute Minute min
Unit-specific Hour Hour h
Unit-specific Day Day d
Unit-specific Week Week wk

Temperature [Θ¹]

Uses affine conversions (factor + offset) for Celsius and Fahrenheit.

Scope Class Unit Symbol
Generic Temperature any (TemperatureUnit) -
Unit-specific Kelvin Kelvin K
Unit-specific Celsius Celsius °C
Unit-specific Fahrenheit Fahrenheit °F

Electric Current [I¹]

The SI base unit of electric current.

Scope Class Unit Symbol
Generic ElectricCurrent any (ElectricCurrentUnit) -
Unit-specific Ampere Ampere A
Unit-specific Kiloampere Kiloampere kA
Unit-specific Milliampere Milliampere mA
Unit-specific Microampere Microampere μA
Unit-specific Nanoampere Nanoampere nA

Area [L²]

Derived from Length × Length.

Scope Class Unit Symbol
Generic Area any -
System-specific MetricArea any metric (MetricAreaUnit) -
System-specific ImperialArea any imperial (ImperialAreaUnit) -
Unit-specific SquareMeter SquareMeter
Unit-specific SquareKilometer SquareKilometer km²
Unit-specific SquareCentimeter SquareCentimeter cm²
Unit-specific SquareMillimeter SquareMillimeter mm²
Unit-specific SquareDecimeter SquareDecimeter dm²
Unit-specific Hectare Hectare ha
Unit-specific Are Are a
Unit-specific SquareFoot SquareFoot ft²
Unit-specific SquareInch SquareInch in²
Unit-specific SquareYard SquareYard yd²
Unit-specific SquareMile SquareMile mi²
Unit-specific Acre Acre ac

Volume [L³]

Derived from Length × Length × Length. Includes gas measurement units.

Scope Class Unit Symbol
Generic Volume any -
System-specific MetricVolume any metric (MetricVolumeUnit) -
System-specific ImperialVolume any imperial (ImperialVolumeUnit) -
System-specific GasVolume gas measurement (GasVolumeUnit) -
Unit-specific CubicMeter CubicMeter
Unit-specific CubicDecimeter CubicDecimeter dm³
Unit-specific CubicCentimeter CubicCentimeter cm³
Unit-specific CubicMillimeter CubicMillimeter mm³
Unit-specific Liter Liter L
Unit-specific Deciliter Deciliter dL
Unit-specific Centiliter Centiliter cL
Unit-specific Milliliter Milliliter mL
Unit-specific Hectoliter Hectoliter hL
Unit-specific Kiloliter Kiloliter kL
Unit-specific CubicFoot CubicFoot ft³
Unit-specific CubicInch CubicInch in³
Unit-specific CubicYard CubicYard yd³
Unit-specific USGallon USGallon gal
Unit-specific USQuart USQuart qt
Unit-specific USPint USPint pt
Unit-specific USCup USCup cup
Unit-specific USFluidOunce USFluidOunce fl oz
Unit-specific USTablespoon USTablespoon tbsp
Unit-specific USTeaspoon USTeaspoon tsp
Unit-specific ImperialGallon ImperialGallon imp gal
Unit-specific ImperialQuart ImperialQuart imp qt
Unit-specific ImperialPint ImperialPint imp pt
Unit-specific ImperialFluidOunce ImperialFluidOunce imp fl oz
Unit-specific StandardCubicMeter StandardCubicMeter Smc
Unit-specific NormalCubicMeter NormalCubicMeter Nmc
Unit-specific StandardCubicFoot StandardCubicFoot scf
Unit-specific ThousandCubicFeet ThousandCubicFeet Mcf

Velocity [L¹T⁻¹]

Derived from Length ÷ Time.

Scope Class Unit Symbol
Generic Velocity any -
System-specific MetricVelocity any metric (MetricVelocityUnit) -
System-specific ImperialVelocity any imperial (ImperialVelocityUnit) -
Unit-specific MeterPerSecond MeterPerSecond m/s
Unit-specific KilometerPerHour KilometerPerHour km/h
Unit-specific CentimeterPerSecond CentimeterPerSecond cm/s
Unit-specific MillimeterPerSecond MillimeterPerSecond mm/s
Unit-specific MilePerHour MilePerHour mph
Unit-specific FootPerSecond FootPerSecond ft/s
Unit-specific Knot Knot kn

Acceleration [L¹T⁻²]

Derived from Velocity ÷ Time.

Scope Class Unit Symbol
Generic Acceleration any -
System-specific MetricAcceleration any metric (MetricAccelerationUnit) -
System-specific ImperialAcceleration any imperial (ImperialAccelerationUnit) -
Unit-specific MeterPerSecondSquared MeterPerSecondSquared m/s²
Unit-specific CentimeterPerSecondSquared CentimeterPerSecondSquared cm/s²
Unit-specific MillimeterPerSecondSquared MillimeterPerSecondSquared mm/s²
Unit-specific Gal Gal Gal
Unit-specific StandardGravity StandardGravity g
Unit-specific FootPerSecondSquared FootPerSecondSquared ft/s²
Unit-specific InchPerSecondSquared InchPerSecondSquared in/s²

Force [L¹M¹T⁻²]

Derived from Mass × Acceleration.

Scope Class Unit Symbol
Generic Force any -
System-specific SIForce any SI (SIForceUnit) -
System-specific ImperialForce any imperial (ImperialForceUnit) -
Unit-specific Newton Newton N
Unit-specific Kilonewton Kilonewton kN
Unit-specific Meganewton Meganewton MN
Unit-specific Millinewton Millinewton mN
Unit-specific Micronewton Micronewton μN
Unit-specific Dyne Dyne dyn
Unit-specific PoundForce PoundForce lbf
Unit-specific OunceForce OunceForce ozf
Unit-specific Kip Kip kip
Unit-specific Poundal Poundal pdl

Pressure [L⁻¹M¹T⁻²]

Derived from Force ÷ Area.

Scope Class Unit Symbol
Generic Pressure any -
System-specific SIPressure any SI (SIPressureUnit) -
System-specific ImperialPressure any imperial (ImperialPressureUnit) -
Unit-specific Pascal Pascal Pa
Unit-specific Hectopascal Hectopascal hPa
Unit-specific Kilopascal Kilopascal kPa
Unit-specific Megapascal Megapascal MPa
Unit-specific Gigapascal Gigapascal GPa
Unit-specific Bar Bar bar
Unit-specific Millibar Millibar mbar
Unit-specific Atmosphere Atmosphere atm
Unit-specific Torr Torr Torr
Unit-specific PoundPerSquareInch PoundPerSquareInch psi
Unit-specific PoundPerSquareFoot PoundPerSquareFoot psf
Unit-specific InchOfMercury InchOfMercury inHg
Unit-specific InchOfWater InchOfWater inH₂O

Energy [L²M¹T⁻²]

Work and heat. Includes electrical and thermal units.

Scope Class Unit Symbol
Generic Energy any -
System-specific SIEnergy any SI (SIEnergyUnit) -
System-specific ElectricEnergy any electric (ElectricEnergyUnit) -
System-specific ThermalEnergy any thermal (ThermalEnergyUnit) -
Unit-specific Joule Joule J
Unit-specific Kilojoule Kilojoule kJ
Unit-specific Megajoule Megajoule MJ
Unit-specific WattHour WattHour Wh
Unit-specific KilowattHour KilowattHour kWh
Unit-specific MegawattHour MegawattHour MWh
Unit-specific GigawattHour GigawattHour GWh
Unit-specific Calorie Calorie cal
Unit-specific Kilocalorie Kilocalorie kcal
Unit-specific BritishThermalUnit BritishThermalUnit BTU

Power [L²M¹T⁻³]

Derived from Energy ÷ Time.

Scope Class Unit Symbol
Generic Power any -
System-specific SIPower any SI (SIPowerUnit) -
System-specific ImperialPower any imperial (ImperialPowerUnit) -
Unit-specific Watt Watt W
Unit-specific Milliwatt Milliwatt mW
Unit-specific Kilowatt Kilowatt kW
Unit-specific Megawatt Megawatt MW
Unit-specific Gigawatt Gigawatt GW
Unit-specific MechanicalHorsepower MechanicalHorsepower hp
Unit-specific ElectricalHorsepower ElectricalHorsepower hp(E)
Unit-specific MetricHorsepower MetricHorsepower PS
Unit-specific FootPoundPerSecond FootPoundPerSecond ft⋅lbf/s
Unit-specific BTUPerHour BTUPerHour BTU/h

Density [L⁻³M¹]

Derived from Mass ÷ Volume.

Scope Class Unit Symbol
Generic Density any -
System-specific SIDensity any SI (SIDensityUnit) -
System-specific ImperialDensity any imperial (ImperialDensityUnit) -
Unit-specific KilogramPerCubicMeter KilogramPerCubicMeter kg/m³
Unit-specific GramPerCubicMeter GramPerCubicMeter g/m³
Unit-specific GramPerCubicCentimeter GramPerCubicCentimeter g/cm³
Unit-specific GramPerLiter GramPerLiter g/L
Unit-specific KilogramPerLiter KilogramPerLiter kg/L
Unit-specific MilligramPerCubicMeter MilligramPerCubicMeter mg/m³
Unit-specific TonnePerCubicMeter TonnePerCubicMeter t/m³
Unit-specific PoundPerCubicFoot PoundPerCubicFoot lb/ft³
Unit-specific PoundPerCubicInch PoundPerCubicInch lb/in³
Unit-specific PoundPerGallon PoundPerGallon lb/gal
Unit-specific OuncePerCubicInch OuncePerCubicInch oz/in³
Unit-specific SlugPerCubicFoot SlugPerCubicFoot slug/ft³

Frequency [T⁻¹]

Derived from 1 ÷ Time.

Scope Class Unit Symbol
Generic Frequency any (FrequencyUnit) -
Unit-specific Hertz Hertz Hz
Unit-specific Millihertz Millihertz mHz
Unit-specific Kilohertz Kilohertz kHz
Unit-specific Megahertz Megahertz MHz
Unit-specific Gigahertz Gigahertz GHz
Unit-specific Terahertz Terahertz THz
Unit-specific RevolutionPerMinute RevolutionPerMinute RPM
Unit-specific RevolutionPerSecond RevolutionPerSecond RPS
Unit-specific BeatsPerMinute BeatsPerMinute BPM

Angle [dimensionless]

Plane angle measurement.

Scope Class Unit Symbol
Generic Angle any (AngleUnit) -
Unit-specific Radian Radian rad
Unit-specific Milliradian Milliradian mrad
Unit-specific Degree Degree °
Unit-specific Arcminute Arcminute
Unit-specific Arcsecond Arcsecond
Unit-specific Gradian Gradian gon
Unit-specific Revolution Revolution rev
Unit-specific Turn Turn tr

Electric Potential [L²M¹T⁻³I⁻¹]

Voltage. Derived from Power ÷ Current.

Scope Class Unit Symbol
Generic ElectricPotential any (ElectricPotentialUnit) -
Unit-specific Volt Volt V
Unit-specific Megavolt Megavolt MV
Unit-specific Kilovolt Kilovolt kV
Unit-specific Millivolt Millivolt mV
Unit-specific Microvolt Microvolt μV

Electric Resistance [L²M¹T⁻³I⁻²]

Derived from Voltage ÷ Current.

Scope Class Unit Symbol
Generic ElectricResistance any (ElectricResistanceUnit) -
Unit-specific Ohm Ohm Ω
Unit-specific Megaohm Megaohm
Unit-specific Kiloohm Kiloohm
Unit-specific Milliohm Milliohm
Unit-specific Microohm Microohm μΩ

Electric Capacitance [L⁻²M⁻¹T⁴I²]

Ability to store electric charge.

Scope Class Unit Symbol
Generic ElectricCapacitance any (ElectricCapacitanceUnit) -
Unit-specific Farad Farad F
Unit-specific Millifarad Millifarad mF
Unit-specific Microfarad Microfarad μF
Unit-specific Nanofarad Nanofarad nF
Unit-specific Picofarad Picofarad pF

Electric Charge [T¹I¹]

Derived from Current × Time.

Scope Class Unit Symbol
Generic ElectricCharge any (ElectricChargeUnit) -
Unit-specific Coulomb Coulomb C
Unit-specific Millicoulomb Millicoulomb mC
Unit-specific Microcoulomb Microcoulomb μC
Unit-specific AmpereHour AmpereHour Ah
Unit-specific MilliampereHour MilliampereHour mAh

Inductance [L²M¹T⁻²I⁻²]

Property of an electrical conductor.

Scope Class Unit Symbol
Generic Inductance any (InductanceUnit) -
Unit-specific Henry Henry H
Unit-specific Millihenry Millihenry mH
Unit-specific Microhenry Microhenry μH
Unit-specific Nanohenry Nanohenry nH

Magnetic Flux [L²M¹T⁻²I⁻¹]

Measure of total magnetic field through a surface.

Scope Class Unit Symbol
Generic MagneticFlux any (MagneticFluxUnit) -
Unit-specific Weber Weber Wb
Unit-specific Milliweber Milliweber mWb
Unit-specific Microweber Microweber μWb
Unit-specific Maxwell Maxwell Mx

Luminous Intensity [J¹]

The SI base unit of luminous intensity.

Scope Class Unit Symbol
Generic LuminousIntensity any (LuminousIntensityUnit) -
Unit-specific Candela Candela cd
Unit-specific Kilocandela Kilocandela kcd
Unit-specific Millicandela Millicandela mcd
Unit-specific Microcandela Microcandela μcd

Luminous Flux [J¹]

Total amount of visible light emitted.

Scope Class Unit Symbol
Generic LuminousFlux any (LuminousFluxUnit) -
Unit-specific Lumen Lumen lm
Unit-specific Kilolumen Kilolumen klm
Unit-specific Millilumen Millilumen mlm

Illuminance [L⁻²J¹]

Derived from Luminous Flux ÷ Area.

Scope Class Unit Symbol
Generic Illuminance any (IlluminanceUnit) -
Unit-specific Lux Lux lx
Unit-specific Kilolux Kilolux klx
Unit-specific Millilux Millilux mlx
Unit-specific FootCandle FootCandle fc

Calorific Value [L⁻¹M¹T⁻²]

Energy per unit volume. Used for gas billing.

Scope Class Unit Symbol
Generic CalorificValue any -
System-specific MetricCalorificValue any metric (MetricCalorificValueUnit) -
System-specific ImperialCalorificValue any imperial (ImperialCalorificValueUnit) -
Unit-specific JoulePerCubicMeter JoulePerCubicMeter J/m³
Unit-specific KilojoulePerCubicMeter KilojoulePerCubicMeter kJ/m³
Unit-specific MegajoulePerCubicMeter MegajoulePerCubicMeter MJ/m³
Unit-specific GigajoulePerCubicMeter GigajoulePerCubicMeter GJ/m³
Unit-specific BTUPerCubicFoot BTUPerCubicFoot BTU/ft³
Unit-specific ThermPerCubicFoot ThermPerCubicFoot thm/ft³

Digital Information [D¹]

Data storage capacity. Supports SI (decimal) and IEC (binary) prefixes.

Scope Class Unit Symbol
Generic DigitalInformation any -
System-specific SI\DigitalInformation any SI (SIDigitalUnit) -
System-specific SI\BitDigitalInformation any SI bit (SIBitUnit) -
System-specific SI\ByteDigitalInformation any SI byte (SIByteUnit) -
System-specific IEC\DigitalInformation any IEC (IECDigitalUnit) -
System-specific IEC\BitDigitalInformation any IEC bit (IECBitUnit) -
System-specific IEC\ByteDigitalInformation any IEC byte (IECByteUnit) -

SI Units (decimal, powers of 10):

Scope Class Unit Symbol
Unit-specific Bit Bit b
Unit-specific Kilobit Kilobit Kb
Unit-specific Megabit Megabit Mb
Unit-specific Gigabit Gigabit Gb
Unit-specific Terabit Terabit Tb
Unit-specific Petabit Petabit Pb
Unit-specific Byte Byte B
Unit-specific Kilobyte Kilobyte KB
Unit-specific Megabyte Megabyte MB
Unit-specific Gigabyte Gigabyte GB
Unit-specific Terabyte Terabyte TB
Unit-specific Petabyte Petabyte PB

IEC Units (binary, powers of 2):

Scope Class Unit Symbol
Unit-specific Kibibit Kibibit Kib
Unit-specific Mebibit Mebibit Mib
Unit-specific Gibibit Gibibit Gib
Unit-specific Tebibit Tebibit Tib
Unit-specific Pebibit Pebibit Pib
Unit-specific Kibibyte Kibibyte KiB
Unit-specific Mebibyte Mebibyte MiB
Unit-specific Gibibyte Gibibyte GiB
Unit-specific Tebibyte Tebibyte TiB
Unit-specific Pebibyte Pebibyte PiB

Data Transfer Rate [D¹T⁻¹]

Measures the speed of data transmission, commonly used for network bandwidth, internet connection speeds, and file transfer rates. Like Digital Information, this quantity supports both SI (decimal) and IEC (binary) prefixes. ISPs and network equipment typically advertise speeds using SI units (e.g., "100 Mbps fiber"), while operating systems often report actual transfer rates in IEC units (e.g., "12 MiB/s"). This distinction matters because 100 Mbps (megabits per second) equals 12.5 MB/s (megabytes per second) but only about 11.92 MiB/s (mebibytes per second).

Scope Class Unit Symbol
Generic DataTransferRate any -
System-specific SI\TransferRate any SI (SITransferRateUnit) -
System-specific SI\BitTransferRate any SI bit (BitTransferRateUnit) -
System-specific SI\ByteTransferRate any SI byte (ByteTransferRateUnit) -
System-specific IEC\TransferRate any IEC (IECTransferRateUnit) -
System-specific IEC\BitTransferRate any IEC bit (IECBitTransferRateUnit) -
System-specific IEC\ByteTransferRate any IEC byte (IECByteTransferRateUnit) -

SI Units:

Scope Class Unit Symbol
Unit-specific BitPerSecond BitPerSecond bps
Unit-specific KilobitPerSecond KilobitPerSecond kbps
Unit-specific MegabitPerSecond MegabitPerSecond Mbps
Unit-specific GigabitPerSecond GigabitPerSecond Gbps
Unit-specific BytePerSecond BytePerSecond B/s
Unit-specific KilobytePerSecond KilobytePerSecond KB/s
Unit-specific MegabytePerSecond MegabytePerSecond MB/s
Unit-specific GigabytePerSecond GigabytePerSecond GB/s

IEC Units:

Scope Class Unit Symbol
Unit-specific KibibitPerSecond KibibitPerSecond Kibps
Unit-specific MebibitPerSecond MebibitPerSecond Mibps
Unit-specific GibibitPerSecond GibibitPerSecond Gibps
Unit-specific KibibytePerSecond KibibytePerSecond KiB/s
Unit-specific MebibytePerSecond MebibytePerSecond MiB/s
Unit-specific GibibytePerSecond GibibytePerSecond GiB/s

Testing

# Run tests
make tests

# Run PHPStan
make phpstan

# Generate coverage report
make coverage

# Show coverage in terminal
make coverage-text

Docker Support

# Build container
make setup

# Get inside PHP container
make php 

Built with ❤️ by AndanteProject team.