Unit Conversion

Unit conversion is a fundamental operation in quantity formatting and parsing. Understanding how conversions work helps you correctly handle quantity values across different unit systems.

UnitConversionSpec

Unit conversion is performed through a UnitConversionSpec. These objects encapsulate the conversion factors and offsets needed to convert a value from one unit to another.

How UnitConversionSpec Works

Unit conversions are applied through the following steps:

  1. Pre-conversion inversion (if inversion === InvertPreConversion): value = 1/value
  2. Apply factor and offset: value = (value * factor) + offset
  3. Post-conversion inversion (if inversion === InvertPostConversion): value = 1/value

For most unit conversions (length, angle, area, etc.), only the factor is used (offset and inversion are zero/undefined). Temperature conversions use non-zero offsets. Inversion is used when converting to or from inverted units - An example would be dimensionless ratios where one unit is the mathematical inverse of another (e.g., vertical-per-horizontal slope ↔ horizontal-per-vertical slope).

Generating UnitConversionSpec

UnitConversionSpec objects are created from UnitConversionProps returned by a UnitsProvider. The provider's getConversion method calculates conversion properties based on unit definitions:

  • BasicUnitsProvider - Resolves conversions from the bundled BIS Units schema. Default provider.
  • SchemaUnitProvider - Calculates conversions from EC schema unit definitions
  • Custom providers - Can implement custom conversion logic

The FormatterSpec and ParserSpec classes create UnitConversionSpec instances from these properties during initialization.

When Conversions Are Cached

During initialization of FormatterSpec and ParserSpec, they request and cache UnitConversionSpec objects from the UnitsProvider:

  • FormatterSpec - Caches conversions from persistence unit to all format display units
  • ParserSpec - Caches conversions from all units in the phenomenon to the persistence unit

This caching strategy:

  • Eliminates async calls during formatting/parsing operations
  • Improves performance for repeated operations
  • Ensures consistent conversions throughout the spec's lifetime

Conversion in Formatting

When formatting a quantity value:

  1. The value starts in the persistence unit (e.g., meters)
  2. The FormatterSpec applies the cached UnitConversionSpec for each display unit
  3. For composite formats, each sub-unit conversion is applied in sequence
  4. The converted values are formatted according to the format specification

Example: Converting 1.5 meters to feet-inches, given a FormatProps that uses fractional, composite unit of feet and inches

1.5 m → 4.92126 ft → 4'-11 1/16"

Conversion in Parsing

When parsing a string to a quantity value:

  1. The string is tokenized to extract values and unit labels
  2. For each unit label, the appropriate UnitConversionSpec is retrieved from the cached specs
  3. Each value is converted to the persistence unit
  4. For composite values, converted sub-unit values are summed
  5. The final result is returned in the persistence unit

Example: Parsing "4'-11 1/16"" to meters, given a FormatProps that uses fractional, composite unit of feet and inches

4' → 1.2192 m 11 1/16" → 0.28098 m Total → 1.50018 m

Unit Family Validation

Before converting, the UnitsProvider validates that both units belong to the same phenomenon (unit family):

  • Valid: Length (meters) → Length (feet)
  • Valid: Angle (radians) → Angle (degrees)
  • Invalid: Length (meters) → Angle (degrees)

Attempting to convert between incompatible phenomena will result in an error.

Performance Considerations

To optimize conversion performance:

  1. Reuse specs - Create FormatterSpec and ParserSpec once and reuse them (e.g., store as class instance properties) rather than recreating on each operation
  2. Batch operations - Format/parse multiple values with the same spec
  3. Cache providers - Don't recreate UnitsProvider instances unnecessarily
  4. Use the default BasicUnitsProvider - It covers the full BIS unit set; only switch to SchemaUnitProvider when you need custom domain-specific units

Example: Direct Unit Conversion

While most conversions happen automatically through FormatterSpec and ParserSpec, you can also request conversions directly from a UnitsProvider:

Example Code
const context = schemaContext; // or from iModelDb.schemaContext or IModelConnection.schemaContext() const provider = new SchemaUnitProvider(context); const fromUnit = await provider.findUnitByName("Units.M"); const toUnit = await provider.findUnitByName("Units.FT"); const conversion = await provider.getConversion(fromUnit, toUnit); const quantity = new Quantity(fromUnit, 1.0); const converted = quantity.convertTo(toUnit, conversion); // converted.magnitude is 3.28084, that's the conversion factor from meters to feet

Built-in UnitConversions

Use UnitConversions for the built-in canonical unit set shipped with core-quantity. Pair it with the generated Units, Phenomena, and UnitSystems identifiers, with bundled units grouped by phenomenon for discovery. Its data is generated from the canonical units schema in @bentley/units-schema, so this path stays synchronous and does not require any app startup/init hook.

If your units are not from the built-in canonical set, use a UnitsProvider-based workflow instead.

For a one-off conversion, use UnitConversions's convert(...) helper:

Example Code
/** Convert a built-in canonical unit value synchronously. */ export function convertMetersToFeet() { return UnitConversions.convert( Units.LENGTH.M, Units.LENGTH.FT, 1, ); }

For repeated conversions within the same built-in unit pair, resolve once and reuse the conversion with UnitConversions's getConversion(...) and convertValue(...) helpers. getConversion(...) may still return UnitConversionProps with error: true for incompatible known units, so apply the result with convertValue(...) rather than using the raw factors directly:

Example Code
/** Resolve a built-in conversion once and reuse it. */ export function convertMetersToFeetRepeatedly() { const conversion = UnitConversions.getConversion( Units.LENGTH.M, Units.LENGTH.FT, ); return { feet1: UnitConversions.convertValue(1, conversion), feet2: UnitConversions.convertValue(2, conversion), }; }

Use UnitConversions's isCompatible(...) helper before attempting a conversion when you need a built-in canonical-unit boolean compatibility check instead of conversion metadata:

Example Code
/** Check whether two built-in canonical units are compatible. */ export function isLengthConversionCompatible() { return UnitConversions.isCompatible( Units.LENGTH.M, Units.LENGTH.FT, ); }

Note: UnitConversions's getConversion(...) and isCompatible(...) helpers operate only on the built-in canonical unit set; they are not package-wide helpers for schema-defined, custom, or provider-resolved units.

Note: UnitConversions's getConversion(...) helper may still return UnitConversionProps with error: true for incompatible known units. Its convertValue(...) and convert(...) helpers are the throwing application paths. Lookup-based helpers throw when a unit name cannot be resolved.

See Also

Last Updated: 01 June, 2026