Module: Philiprehberger::MetricUnits
- Defined in:
- lib/philiprehberger/metric_units.rb,
lib/philiprehberger/metric_units/version.rb
Defined Under Namespace
Classes: Error
Constant Summary collapse
- LENGTH_FACTORS =
Conversion factors to base unit per category Length: base = meters Weight: base = grams Volume: base = liters
{ km: 1000.0, m: 1.0, cm: 0.01, mm: 0.001, miles: 1609.344, yards: 0.9144, feet: 0.3048, inches: 0.0254 }.freeze
- WEIGHT_FACTORS =
{ kg: 1000.0, g: 1.0, mg: 0.001, lbs: 453.59237, oz: 28.349523125 }.freeze
- VOLUME_FACTORS =
{ liters: 1.0, ml: 0.001, gallons: 3.785411784, quarts: 0.946352946, pints: 0.473176473, cups: 0.2365882365 }.freeze
- SPEED_FACTORS =
{ meters_per_second: 1.0, kilometers_per_hour: 1.0 / 3.6, miles_per_hour: 0.44704, knots: 0.514444, feet_per_second: 0.3048 }.freeze
- PRESSURE_FACTORS =
{ pascals: 1.0, kilopascals: 1000.0, bar: 100_000.0, psi: 6894.757, atmospheres: 101_325.0, mmhg: 133.322 }.freeze
- ENERGY_FACTORS =
{ joules: 1.0, kilojoules: 1000.0, calories: 4.184, kilocalories: 4184.0, watt_hours: 3600.0, kilowatt_hours: 3_600_000.0, btu: 1055.06 }.freeze
- DATA_FACTORS =
Data: base = bytes. Includes both decimal (SI) and binary (IEC) units.
{ bytes: 1.0, kilobytes: 1_000.0, megabytes: 1_000_000.0, gigabytes: 1_000_000_000.0, terabytes: 1_000_000_000_000.0, petabytes: 1_000_000_000_000_000.0, kibibytes: 1024.0, mebibytes: 1_048_576.0, gibibytes: 1_073_741_824.0, tebibytes: 1_099_511_627_776.0, pebibytes: 1_125_899_906_842_624.0 }.freeze
- TEMPERATURE_UNITS =
%i[celsius fahrenheit kelvin].freeze
- CATEGORY_MAP =
{ length: LENGTH_FACTORS, weight: WEIGHT_FACTORS, volume: VOLUME_FACTORS, temperature: nil, speed: SPEED_FACTORS, pressure: PRESSURE_FACTORS, energy: ENERGY_FACTORS, data: DATA_FACTORS }.freeze
- ABBREVIATIONS =
{ # Length km: 'km', m: 'm', cm: 'cm', mm: 'mm', miles: 'mi', yards: 'yd', feet: 'ft', inches: 'in', # Weight kg: 'kg', g: 'g', mg: 'mg', lbs: 'lb', oz: 'oz', # Volume liters: 'L', ml: 'mL', gallons: 'gal', quarts: 'qt', pints: 'pt', cups: 'cup', # Temperature celsius: "\u00B0C", fahrenheit: "\u00B0F", kelvin: 'K', # Speed meters_per_second: 'm/s', kilometers_per_hour: 'km/h', miles_per_hour: 'mph', knots: 'kn', feet_per_second: 'ft/s', # Pressure pascals: 'Pa', kilopascals: 'kPa', bar: 'bar', psi: 'psi', atmospheres: 'atm', mmhg: 'mmHg', # Energy joules: 'J', kilojoules: 'kJ', calories: 'cal', kilocalories: 'kcal', watt_hours: 'Wh', kilowatt_hours: 'kWh', btu: 'BTU', # Data bytes: 'B', kilobytes: 'kB', megabytes: 'MB', gigabytes: 'GB', terabytes: 'TB', petabytes: 'PB', kibibytes: 'KiB', mebibytes: 'MiB', gibibytes: 'GiB', tebibytes: 'TiB', pebibytes: 'PiB' }.freeze
- ALIASES =
Alias map: any token a user might write (symbols, abbreviations, plurals) mapped to the canonical unit symbol used by the CATEGORY_MAP.
{ # Length 'kilometer' => :km, 'kilometers' => :km, 'kilometre' => :km, 'kilometres' => :km, 'km' => :km, 'meter' => :m, 'meters' => :m, 'metre' => :m, 'metres' => :m, 'm' => :m, 'centimeter' => :cm, 'centimeters' => :cm, 'cm' => :cm, 'millimeter' => :mm, 'millimeters' => :mm, 'mm' => :mm, 'mile' => :miles, 'miles' => :miles, 'mi' => :miles, 'yard' => :yards, 'yards' => :yards, 'yd' => :yards, 'foot' => :feet, 'feet' => :feet, 'ft' => :feet, 'inch' => :inches, 'inches' => :inches, 'in' => :inches, # Weight 'kilogram' => :kg, 'kilograms' => :kg, 'kg' => :kg, 'gram' => :g, 'grams' => :g, 'g' => :g, 'milligram' => :mg, 'milligrams' => :mg, 'mg' => :mg, 'pound' => :lbs, 'pounds' => :lbs, 'lb' => :lbs, 'lbs' => :lbs, 'ounce' => :oz, 'ounces' => :oz, 'oz' => :oz, # Volume 'liter' => :liters, 'liters' => :liters, 'litre' => :liters, 'litres' => :liters, 'l' => :liters, 'milliliter' => :ml, 'milliliters' => :ml, 'ml' => :ml, 'gallon' => :gallons, 'gallons' => :gallons, 'gal' => :gallons, 'quart' => :quarts, 'quarts' => :quarts, 'qt' => :quarts, 'pint' => :pints, 'pints' => :pints, 'pt' => :pints, 'cup' => :cups, 'cups' => :cups, # Temperature 'c' => :celsius, 'celsius' => :celsius, "\u00B0c" => :celsius, 'f' => :fahrenheit, 'fahrenheit' => :fahrenheit, "\u00B0f" => :fahrenheit, 'k' => :kelvin, 'kelvin' => :kelvin, # Speed 'm/s' => :meters_per_second, 'mps' => :meters_per_second, 'km/h' => :kilometers_per_hour, 'kph' => :kilometers_per_hour, 'kmh' => :kilometers_per_hour, 'mph' => :miles_per_hour, 'mi/h' => :miles_per_hour, 'kn' => :knots, 'kt' => :knots, 'knot' => :knots, 'knots' => :knots, 'ft/s' => :feet_per_second, 'fps' => :feet_per_second, # Pressure 'pa' => :pascals, 'pascal' => :pascals, 'pascals' => :pascals, 'kpa' => :kilopascals, 'kilopascal' => :kilopascals, 'kilopascals' => :kilopascals, 'bar' => :bar, 'bars' => :bar, 'psi' => :psi, 'atm' => :atmospheres, 'atmosphere' => :atmospheres, 'atmospheres' => :atmospheres, 'mmhg' => :mmhg, 'torr' => :mmhg, # Energy 'j' => :joules, 'joule' => :joules, 'joules' => :joules, 'kj' => :kilojoules, 'kilojoule' => :kilojoules, 'kilojoules' => :kilojoules, 'cal' => :calories, 'calorie' => :calories, 'calories' => :calories, 'kcal' => :kilocalories, 'kilocalorie' => :kilocalories, 'kilocalories' => :kilocalories, 'wh' => :watt_hours, 'watt_hour' => :watt_hours, 'watt_hours' => :watt_hours, 'kwh' => :kilowatt_hours, 'kilowatt_hour' => :kilowatt_hours, 'kilowatt_hours' => :kilowatt_hours, 'btu' => :btu, # Data 'b' => :bytes, 'byte' => :bytes, 'bytes' => :bytes, 'kb' => :kilobytes, 'kilobyte' => :kilobytes, 'kilobytes' => :kilobytes, 'mb' => :megabytes, 'megabyte' => :megabytes, 'megabytes' => :megabytes, 'gb' => :gigabytes, 'gigabyte' => :gigabytes, 'gigabytes' => :gigabytes, 'tb' => :terabytes, 'terabyte' => :terabytes, 'terabytes' => :terabytes, 'pb' => :petabytes, 'petabyte' => :petabytes, 'petabytes' => :petabytes, 'kib' => :kibibytes, 'kibibyte' => :kibibytes, 'kibibytes' => :kibibytes, 'mib' => :mebibytes, 'mebibyte' => :mebibytes, 'mebibytes' => :mebibytes, 'gib' => :gibibytes, 'gibibyte' => :gibibytes, 'gibibytes' => :gibibytes, 'tib' => :tebibytes, 'tebibyte' => :tebibytes, 'tebibytes' => :tebibytes, 'pib' => :pebibytes, 'pebibyte' => :pebibytes, 'pebibytes' => :pebibytes }.freeze
- HUMANIZE_BYTES_DECIMAL =
Ordered scales used by .humanize_bytes for auto-scaling
%i[bytes kilobytes megabytes gigabytes terabytes petabytes].freeze
- HUMANIZE_BYTES_BINARY =
%i[bytes kibibytes mebibytes gibibytes tebibytes pebibytes].freeze
- VERSION =
'0.3.0'
Class Method Summary collapse
-
.abbreviation(unit) ⇒ String?
Return the standard abbreviation for a unit.
-
.categories ⇒ Array<Symbol>
Return all available categories.
-
.category_for(unit) ⇒ Symbol?
Return the category a unit belongs to.
-
.convert(value, from:, to:) ⇒ Float
Convert a value from one unit to another.
-
.convert_str(string, to:) ⇒ Float
Parse a string and convert it to another unit in one step.
-
.format(value, unit, precision: 2) ⇒ String
Format a value with its unit abbreviation.
-
.humanize_bytes(bytes, binary: false, precision: 2) ⇒ String
Auto-scale a byte count to a human-readable string.
-
.parse(string) ⇒ Array(Float, Symbol)
Parse a string like “5 km”, “3.14kg”, or “72°F” into [value, unit_symbol].
-
.units_for(category) ⇒ Array<Symbol>
Return all units for a given category.
Class Method Details
.abbreviation(unit) ⇒ String?
Return the standard abbreviation for a unit
245 246 247 |
# File 'lib/philiprehberger/metric_units.rb', line 245 def self.abbreviation(unit) ABBREVIATIONS[unit.to_sym] end |
.categories ⇒ Array<Symbol>
Return all available categories
223 224 225 |
# File 'lib/philiprehberger/metric_units.rb', line 223 def self.categories CATEGORY_MAP.keys end |
.category_for(unit) ⇒ Symbol?
Return the category a unit belongs to.
269 270 271 272 273 274 |
# File 'lib/philiprehberger/metric_units.rb', line 269 def self.category_for(unit) unit_sym = unit.to_sym return :temperature if TEMPERATURE_UNITS.include?(unit_sym) internal_category_for(unit_sym) end |
.convert(value, from:, to:) ⇒ Float
Convert a value from one unit to another
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/philiprehberger/metric_units.rb', line 200 def self.convert(value, from:, to:) raise Error, 'value must be numeric' unless value.is_a?(Numeric) from = from.to_sym to = to.to_sym return convert_temperature(value, from, to) if temperature_unit?(from) || temperature_unit?(to) from_category = internal_category_for(from) to_category = internal_category_for(to) raise Error, "unknown unit: #{from}" unless from_category raise Error, "unknown unit: #{to}" unless to_category raise Error, "cannot convert between #{from_category} and #{to_category}" unless from_category == to_category factors = CATEGORY_MAP[from_category] base_value = value * factors[from] base_value / factors[to] end |
.convert_str(string, to:) ⇒ Float
Parse a string and convert it to another unit in one step.
304 305 306 307 |
# File 'lib/philiprehberger/metric_units.rb', line 304 def self.convert_str(string, to:) value, from = parse(string) convert(value, from: from, to: to) end |
.format(value, unit, precision: 2) ⇒ String
Format a value with its unit abbreviation
256 257 258 259 260 261 262 263 |
# File 'lib/philiprehberger/metric_units.rb', line 256 def self.format(value, unit, precision: 2) raise Error, 'value must be numeric' unless value.is_a?(Numeric) abbr = abbreviation(unit) raise Error, "unknown unit abbreviation: #{unit}" unless abbr "#{value.round(precision)} #{abbr}" end |
.humanize_bytes(bytes, binary: false, precision: 2) ⇒ String
Auto-scale a byte count to a human-readable string.
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/philiprehberger/metric_units.rb', line 316 def self.humanize_bytes(bytes, binary: false, precision: 2) raise Error, 'bytes must be numeric' unless bytes.is_a?(Numeric) raise Error, 'precision must be a non-negative integer' unless precision.is_a?(Integer) && precision >= 0 sign = bytes.negative? ? -1 : 1 abs = bytes.abs.to_f units = binary ? HUMANIZE_BYTES_BINARY : HUMANIZE_BYTES_DECIMAL step = binary ? 1024.0 : 1000.0 unit = units.first value = abs units.each_with_index do |candidate, idx| threshold = step**idx next_threshold = step**(idx + 1) next unless abs < next_threshold || idx == units.length - 1 unit = candidate value = abs / threshold break end self.format(sign * value, unit, precision: precision) end |
.parse(string) ⇒ Array(Float, Symbol)
Parse a string like “5 km”, “3.14kg”, or “72°F” into [value, unit_symbol].
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/philiprehberger/metric_units.rb', line 281 def self.parse(string) raise Error, 'value must be a string' unless string.is_a?(String) trimmed = string.strip raise Error, 'cannot parse empty string' if trimmed.empty? match = trimmed.match(/\A(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\s*(.+?)\z/) raise Error, "cannot parse: #{string.inspect}" unless match value = Float(match[1]) token = match[2].strip.downcase unit = ALIASES[token] raise Error, "unknown unit: #{match[2]}" unless unit [value, unit] end |
.units_for(category) ⇒ Array<Symbol>
Return all units for a given category
232 233 234 235 236 237 238 239 |
# File 'lib/philiprehberger/metric_units.rb', line 232 def self.units_for(category) category = category.to_sym raise Error, "unknown category: #{category}" unless CATEGORY_MAP.key?(category) return TEMPERATURE_UNITS if category == :temperature CATEGORY_MAP[category].keys end |