measure-convert
Version:
JS/TS package for managing units of measurement. Convert, add, subtract, multiply, divide, and compare units of measurement.
297 lines (296 loc) • 14.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Measurement = void 0;
// src/Measurement.ts
const Unit_1 = require("./units/Unit");
const UnitAngle_1 = require("./units/UnitAngle");
const UnitFuelEfficiency_1 = require("./units/UnitFuelEfficiency");
const UnitTemperature_1 = require("./units/UnitTemperature");
const UnitAcceleration_1 = require("./units/UnitAcceleration");
const UnitArea_1 = require("./units/UnitArea");
const UnitConcentrationMass_1 = require("./units/UnitConcentrationMass");
const UnitDispersion_1 = require("./units/UnitDispersion");
const UnitDuration_1 = require("./units/UnitDuration");
const UnitElectricCharge_1 = require("./units/UnitElectricCharge");
const UnitElectricCurrent_1 = require("./units/UnitElectricCurrent");
const UnitElectricPotentialDifference_1 = require("./units/UnitElectricPotentialDifference");
const UnitElectricResistance_1 = require("./units/UnitElectricResistance");
const UnitEnergy_1 = require("./units/UnitEnergy");
const UnitFrequency_1 = require("./units/UnitFrequency");
const UnitIlluminance_1 = require("./units/UnitIlluminance");
const UnitInformationStorage_1 = require("./units/UnitInformationStorage");
const UnitLength_1 = require("./units/UnitLength");
const UnitMass_1 = require("./units/UnitMass");
const UnitPower_1 = require("./units/UnitPower");
const UnitPressure_1 = require("./units/UnitPressure");
const UnitSpeed_1 = require("./units/UnitSpeed");
const UnitVolume_1 = require("./units/UnitVolume");
const UnitBiologicalActivity_1 = require("./units/UnitBiologicalActivity");
// Static registry of all unit classes for detection
const UNIT_CLASSES = [
{ name: 'UnitAcceleration', class: UnitAcceleration_1.UnitAcceleration },
{ name: 'UnitAngle', class: UnitAngle_1.UnitAngle },
{ name: 'UnitArea', class: UnitArea_1.UnitArea },
{ name: 'UnitConcentrationMass', class: UnitConcentrationMass_1.UnitConcentrationMass },
{ name: 'UnitDispersion', class: UnitDispersion_1.UnitDispersion },
{ name: 'UnitDuration', class: UnitDuration_1.UnitDuration },
{ name: 'UnitElectricCharge', class: UnitElectricCharge_1.UnitElectricCharge },
{ name: 'UnitElectricCurrent', class: UnitElectricCurrent_1.UnitElectricCurrent },
{ name: 'UnitElectricPotentialDifference', class: UnitElectricPotentialDifference_1.UnitElectricPotentialDifference },
{ name: 'UnitElectricResistance', class: UnitElectricResistance_1.UnitElectricResistance },
{ name: 'UnitEnergy', class: UnitEnergy_1.UnitEnergy },
{ name: 'UnitFrequency', class: UnitFrequency_1.UnitFrequency },
{ name: 'UnitFuelEfficiency', class: UnitFuelEfficiency_1.UnitFuelEfficiency },
{ name: 'UnitIlluminance', class: UnitIlluminance_1.UnitIlluminance },
{ name: 'UnitInformationStorage', class: UnitInformationStorage_1.UnitInformationStorage },
{ name: 'UnitLength', class: UnitLength_1.UnitLength },
{ name: 'UnitMass', class: UnitMass_1.UnitMass },
{ name: 'UnitPower', class: UnitPower_1.UnitPower },
{ name: 'UnitPressure', class: UnitPressure_1.UnitPressure },
{ name: 'UnitSpeed', class: UnitSpeed_1.UnitSpeed },
{ name: 'UnitTemperature', class: UnitTemperature_1.UnitTemperature },
{ name: 'UnitVolume', class: UnitVolume_1.UnitVolume },
{ name: 'UnitBiologicalActivity', class: UnitBiologicalActivity_1.UnitBiologicalActivity },
];
class Measurement {
constructor(value, unit) {
this.value = value;
this.unit = unit;
}
// Adjusted to handle specialized conversions
converted(targetUnit) {
if (this.unit.constructor !== targetUnit.constructor) {
throw new Error(`Cannot convert from ${this.unit.name} (${this.unit.constructor.name}) to ${targetUnit.name} (${targetUnit.constructor.name}): incompatible unit types.`);
}
// Handle specialized conversions if necessary
if (this.unit instanceof UnitFuelEfficiency_1.UnitFuelEfficiency && targetUnit instanceof UnitFuelEfficiency_1.UnitFuelEfficiency) {
const convertedValue = this.unit.convert(this.value, targetUnit);
return new Measurement(convertedValue, targetUnit);
}
if (this.unit instanceof UnitTemperature_1.UnitTemperature && targetUnit instanceof UnitTemperature_1.UnitTemperature) {
const convertedValue = this.unit.convert(this.value, targetUnit);
return new Measurement(convertedValue, targetUnit);
}
// Default linear conversion for all other units
const conversionFactor = this.unit.baseUnitConversionFactor / targetUnit.baseUnitConversionFactor;
return new Measurement(this.value * conversionFactor, targetUnit);
}
// Instance method for addition
add(other) {
return Measurement.add(this, other);
}
// Static method for addition
static add(measurement1, measurement2) {
if (measurement1.unit === measurement2.unit) {
return new Measurement(measurement1.value + measurement2.value, measurement1.unit);
}
else {
const convertedMeasurement2 = measurement2.converted(measurement1.unit);
return new Measurement(measurement1.value + convertedMeasurement2.value, measurement1.unit);
}
}
// Instance method for subtraction
subtract(other) {
return Measurement.subtract(this, other);
}
// Static method for subtraction
static subtract(measurement1, measurement2) {
if (measurement1.unit === measurement2.unit) {
return new Measurement(measurement1.value - measurement2.value, measurement1.unit);
}
else {
const convertedMeasurement2 = measurement2.converted(measurement1.unit);
return new Measurement(measurement1.value - convertedMeasurement2.value, measurement1.unit);
}
}
// Equality check
equals(other) {
return Measurement.equals(this, other);
}
static equals(measurement1, measurement2) {
const convertedMeasurement2 = measurement2.converted(measurement1.unit);
return measurement1.value === convertedMeasurement2.value;
}
closeTo(other, tolerance) {
return Measurement.closeTo(this, other, tolerance);
}
static closeTo(measurement1, measurement2, tolerance) {
const convertedMeasurement2 = measurement2.converted(measurement1.unit);
if (tolerance < 0) {
throw new Error("Tolerance must be a non-negative number.");
}
return Math.abs(measurement1.value - convertedMeasurement2.value) <= tolerance;
}
// Comparison methods implemented similarly
greaterThan(other) {
return Measurement.greaterThan(this, other);
}
static greaterThan(measurement1, measurement2) {
const convertedMeasurement2 = measurement2.converted(measurement1.unit);
return measurement1.value > convertedMeasurement2.value;
}
lessThan(other) {
return Measurement.lessThan(this, other);
}
static lessThan(measurement1, measurement2) {
const convertedMeasurement2 = measurement2.converted(measurement1.unit);
return measurement1.value < convertedMeasurement2.value;
}
greaterThanOrEqual(other) {
return Measurement.greaterThanOrEqual(this, other);
}
static greaterThanOrEqual(measurement1, measurement2) {
const convertedMeasurement2 = measurement2.converted(measurement1.unit);
return measurement1.value >= convertedMeasurement2.value;
}
lessThanOrEqual(other) {
return Measurement.lessThanOrEqual(this, other);
}
static lessThanOrEqual(measurement1, measurement2) {
const convertedMeasurement2 = measurement2.converted(measurement1.unit);
return measurement1.value <= convertedMeasurement2.value;
}
ratioTo(other) {
return Measurement.ratioTo(this, other);
}
static ratioTo(measurement1, measurement2) {
const convertedMeasurement2 = measurement2.converted(measurement1.unit);
if (convertedMeasurement2.value === 0) {
throw new Error("Cannot calculate ratio: denominator measurement has zero value");
}
return measurement1.value / convertedMeasurement2.value;
}
scaledBy(factor) {
return Measurement.scaledBy(this, factor);
}
static scaledBy(measurement, factor) {
return new Measurement(measurement.value * factor, measurement.unit);
}
// Helper method to format value with intelligent decimal places
formatValue(value, decimalPlaces, showApprox = false) {
if (decimalPlaces !== undefined) {
// Use specified decimal places, but avoid showing 0 for significant values
const rounded = Number(value.toFixed(decimalPlaces));
if (rounded === 0 && value > 0) {
// If rounding to 0 but value is positive, show with more decimals or approx
if (showApprox) {
return `~${rounded}`;
}
// Find minimum decimal places needed to avoid 0
for (let i = decimalPlaces + 1; i <= 6; i++) {
const testRounded = Number(value.toFixed(i));
if (testRounded > 0) {
return testRounded.toString();
}
}
return showApprox ? `~0` : `<0.000001`;
}
return rounded.toString();
}
// Intelligent default decimal places
const absValue = Math.abs(value);
let defaultDecimals;
if (absValue >= 100) {
defaultDecimals = 0;
}
else if (absValue >= 10) {
defaultDecimals = 1;
}
else if (absValue >= 1) {
defaultDecimals = 2;
}
else if (absValue >= 0.1) {
defaultDecimals = 3;
}
else {
defaultDecimals = 4;
}
const rounded = Number(value.toFixed(defaultDecimals));
if (rounded === 0 && value > 0) {
return showApprox ? `~0` : `<${Math.pow(10, -defaultDecimals)}`;
}
return rounded.toString();
}
// Customized short label method for handling degrees specifically
get shortLabel() {
return this.getShortLabel();
}
getShortLabel(decimalPlaces, showApprox = false) {
const formattedValue = this.formatValue(this.value, decimalPlaces, showApprox);
// Check if the unit is 'degrees'
if (this.unit === UnitAngle_1.UnitAngle.degrees) {
return `${formattedValue}${this.unit.symbol}`; // No space for degrees
}
return `${formattedValue} ${this.unit.symbol}`; // Default formatting for others
}
get longLabel() {
return this.getLongLabel();
}
getLongLabel(decimalPlaces, showApprox = false) {
const formattedValue = this.formatValue(this.value, decimalPlaces, showApprox);
return `${formattedValue} ${this.unit.name}`;
}
// Hydrate when you know the unit type
static hydrate(UnitClass, data) {
const { value, unit: unitData } = data;
// Find matching unit in the specified unit class
const staticProps = Object.getOwnPropertyNames(UnitClass);
for (const propName of staticProps) {
const staticUnit = UnitClass[propName];
// Skip non-unit properties
if (!staticUnit || typeof staticUnit !== 'object' || !(staticUnit instanceof Unit_1.Unit)) {
continue;
}
// Check if this unit matches our serialized data
if (staticUnit.name === unitData.name &&
staticUnit.symbol === unitData.symbol &&
staticUnit.baseUnitConversionFactor === unitData.baseUnitConversionFactor) {
return new Measurement(value, staticUnit);
}
}
throw new Error(`Unable to hydrate measurement: No matching unit found in ${UnitClass.name} for "${unitData.name}"`);
}
// Auto-hydrate by detecting the unit type first
static hydrateAuto(data) {
for (const { class: UnitClass } of UNIT_CLASSES) {
// Get all static properties that are unit instances
const staticProps = Object.getOwnPropertyNames(UnitClass);
for (const propName of staticProps) {
const staticUnit = UnitClass[propName];
// Skip non-unit properties
if (!staticUnit || typeof staticUnit !== 'object' || !(staticUnit instanceof Unit_1.Unit)) {
continue;
}
// Check if this unit matches our serialized data
if (staticUnit.name === data.unit.name &&
staticUnit.symbol === data.unit.symbol &&
staticUnit.baseUnitConversionFactor === data.unit.baseUnitConversionFactor) {
return new Measurement(data.value, staticUnit);
}
}
}
throw new Error(`Unable to hydrate measurement: Unknown unit with name "${data.unit.name}", symbol "${data.unit.symbol}"`);
}
// Detect what unit type a serialized measurement is (for advanced use cases)
static detectUnitType(data) {
for (const { name: className, class: UnitClass } of UNIT_CLASSES) {
// Get all static properties that are unit instances
const staticProps = Object.getOwnPropertyNames(UnitClass);
for (const propName of staticProps) {
const staticUnit = UnitClass[propName];
// Skip non-unit properties
if (!staticUnit || typeof staticUnit !== 'object' || !(staticUnit instanceof Unit_1.Unit)) {
continue;
}
// Check if this unit matches our serialized data
if (staticUnit.name === data.unit.name &&
staticUnit.symbol === data.unit.symbol &&
staticUnit.baseUnitConversionFactor === data.unit.baseUnitConversionFactor) {
return className;
}
}
}
return null;
}
}
exports.Measurement = Measurement;