Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
   4  
   5  use PhpOffice\PhpSpreadsheet\Calculation\Exception;
   6  use PhpOffice\PhpSpreadsheet\Calculation\Functions;
   7  
   8  class ConvertUOM
   9  {
  10      public const CATEGORY_WEIGHT_AND_MASS = 'Weight and Mass';
  11      public const CATEGORY_DISTANCE = 'Distance';
  12      public const CATEGORY_TIME = 'Time';
  13      public const CATEGORY_PRESSURE = 'Pressure';
  14      public const CATEGORY_FORCE = 'Force';
  15      public const CATEGORY_ENERGY = 'Energy';
  16      public const CATEGORY_POWER = 'Power';
  17      public const CATEGORY_MAGNETISM = 'Magnetism';
  18      public const CATEGORY_TEMPERATURE = 'Temperature';
  19      public const CATEGORY_VOLUME = 'Volume and Liquid Measure';
  20      public const CATEGORY_AREA = 'Area';
  21      public const CATEGORY_INFORMATION = 'Information';
  22      public const CATEGORY_SPEED = 'Speed';
  23  
  24      /**
  25       * Details of the Units of measure that can be used in CONVERTUOM().
  26       *
  27       * @var mixed[]
  28       */
  29      private static $conversionUnits = [
  30          // Weight and Mass
  31          'g' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Gram', 'AllowPrefix' => true],
  32          'sg' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Slug', 'AllowPrefix' => false],
  33          'lbm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Pound mass (avoirdupois)', 'AllowPrefix' => false],
  34          'u' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U (atomic mass unit)', 'AllowPrefix' => true],
  35          'ozm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ounce mass (avoirdupois)', 'AllowPrefix' => false],
  36          'grain' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Grain', 'AllowPrefix' => false],
  37          'cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false],
  38          'shweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false],
  39          'uk_cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false],
  40          'lcwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false],
  41          'hweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false],
  42          'stone' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Stone', 'AllowPrefix' => false],
  43          'ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ton', 'AllowPrefix' => false],
  44          'uk_ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false],
  45          'LTON' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false],
  46          'brton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false],
  47          // Distance
  48          'm' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Meter', 'AllowPrefix' => true],
  49          'mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Statute mile', 'AllowPrefix' => false],
  50          'Nmi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Nautical mile', 'AllowPrefix' => false],
  51          'in' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Inch', 'AllowPrefix' => false],
  52          'ft' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Foot', 'AllowPrefix' => false],
  53          'yd' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Yard', 'AllowPrefix' => false],
  54          'ang' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Angstrom', 'AllowPrefix' => true],
  55          'ell' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Ell', 'AllowPrefix' => false],
  56          'ly' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Light Year', 'AllowPrefix' => false],
  57          'parsec' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false],
  58          'pc' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false],
  59          'Pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false],
  60          'Picapt' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false],
  61          'pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/6 in)', 'AllowPrefix' => false],
  62          'survey_mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'U.S survey mile (statute mile)', 'AllowPrefix' => false],
  63          // Time
  64          'yr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Year', 'AllowPrefix' => false],
  65          'day' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false],
  66          'd' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false],
  67          'hr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Hour', 'AllowPrefix' => false],
  68          'mn' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false],
  69          'min' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false],
  70          'sec' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true],
  71          's' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true],
  72          // Pressure
  73          'Pa' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true],
  74          'p' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true],
  75          'atm' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true],
  76          'at' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true],
  77          'mmHg' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'mm of Mercury', 'AllowPrefix' => true],
  78          'psi' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'PSI', 'AllowPrefix' => true],
  79          'Torr' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Torr', 'AllowPrefix' => true],
  80          // Force
  81          'N' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Newton', 'AllowPrefix' => true],
  82          'dyn' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true],
  83          'dy' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true],
  84          'lbf' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pound force', 'AllowPrefix' => false],
  85          'pond' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pond', 'AllowPrefix' => true],
  86          // Energy
  87          'J' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Joule', 'AllowPrefix' => true],
  88          'e' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Erg', 'AllowPrefix' => true],
  89          'c' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Thermodynamic calorie', 'AllowPrefix' => true],
  90          'cal' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'IT calorie', 'AllowPrefix' => true],
  91          'eV' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true],
  92          'ev' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true],
  93          'HPh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false],
  94          'hh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false],
  95          'Wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true],
  96          'wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true],
  97          'flb' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Foot-pound', 'AllowPrefix' => false],
  98          'BTU' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false],
  99          'btu' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false],
 100          // Power
 101          'HP' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false],
 102          'h' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false],
 103          'W' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true],
 104          'w' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true],
 105          'PS' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Pferdestärke', 'AllowPrefix' => false],
 106          'T' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Tesla', 'AllowPrefix' => true],
 107          'ga' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Gauss', 'AllowPrefix' => true],
 108          // Temperature
 109          'C' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false],
 110          'cel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false],
 111          'F' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false],
 112          'fah' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false],
 113          'K' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false],
 114          'kel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false],
 115          'Rank' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Rankine', 'AllowPrefix' => false],
 116          'Reau' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Réaumur', 'AllowPrefix' => false],
 117          // Volume
 118          'l' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true],
 119          'L' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true],
 120          'lt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true],
 121          'tsp' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Teaspoon', 'AllowPrefix' => false],
 122          'tspm' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Modern Teaspoon', 'AllowPrefix' => false],
 123          'tbs' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Tablespoon', 'AllowPrefix' => false],
 124          'oz' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Fluid Ounce', 'AllowPrefix' => false],
 125          'cup' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cup', 'AllowPrefix' => false],
 126          'pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false],
 127          'us_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false],
 128          'uk_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.K. Pint', 'AllowPrefix' => false],
 129          'qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Quart', 'AllowPrefix' => false],
 130          'uk_qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Quart (UK)', 'AllowPrefix' => false],
 131          'gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gallon', 'AllowPrefix' => false],
 132          'uk_gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Gallon (UK)', 'AllowPrefix' => false],
 133          'ang3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true],
 134          'ang^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true],
 135          'barrel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Oil Barrel', 'AllowPrefix' => false],
 136          'bushel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Bushel', 'AllowPrefix' => false],
 137          'in3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false],
 138          'in^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false],
 139          'ft3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false],
 140          'ft^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false],
 141          'ly3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false],
 142          'ly^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false],
 143          'm3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true],
 144          'm^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true],
 145          'mi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false],
 146          'mi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false],
 147          'yd3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false],
 148          'yd^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false],
 149          'Nmi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false],
 150          'Nmi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false],
 151          'Pica3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false],
 152          'Pica^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false],
 153          'Picapt3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false],
 154          'Picapt^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false],
 155          'GRT' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false],
 156          'regton' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false],
 157          'MTON' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Measurement Ton (Freight Ton)', 'AllowPrefix' => false],
 158          // Area
 159          'ha' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Hectare', 'AllowPrefix' => true],
 160          'uk_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'International Acre', 'AllowPrefix' => false],
 161          'us_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'US Survey/Statute Acre', 'AllowPrefix' => false],
 162          'ang2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true],
 163          'ang^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true],
 164          'ar' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Are', 'AllowPrefix' => true],
 165          'ft2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false],
 166          'ft^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false],
 167          'in2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false],
 168          'in^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false],
 169          'ly2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false],
 170          'ly^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false],
 171          'm2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true],
 172          'm^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true],
 173          'Morgen' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Morgen', 'AllowPrefix' => false],
 174          'mi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false],
 175          'mi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false],
 176          'Nmi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false],
 177          'Nmi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false],
 178          'Pica2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false],
 179          'Pica^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false],
 180          'Picapt2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false],
 181          'Picapt^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false],
 182          'yd2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false],
 183          'yd^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false],
 184          // Information
 185          'byte' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Byte', 'AllowPrefix' => true],
 186          'bit' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Bit', 'AllowPrefix' => true],
 187          // Speed
 188          'm/s' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true],
 189          'm/sec' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true],
 190          'm/h' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true],
 191          'm/hr' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true],
 192          'mph' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Miles per hour', 'AllowPrefix' => false],
 193          'admkn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Admiralty Knot', 'AllowPrefix' => false],
 194          'kn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Knot', 'AllowPrefix' => false],
 195      ];
 196  
 197      /**
 198       * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
 199       *
 200       * @var mixed[]
 201       */
 202      private static $conversionMultipliers = [
 203          'Y' => ['multiplier' => 1E24, 'name' => 'yotta'],
 204          'Z' => ['multiplier' => 1E21, 'name' => 'zetta'],
 205          'E' => ['multiplier' => 1E18, 'name' => 'exa'],
 206          'P' => ['multiplier' => 1E15, 'name' => 'peta'],
 207          'T' => ['multiplier' => 1E12, 'name' => 'tera'],
 208          'G' => ['multiplier' => 1E9, 'name' => 'giga'],
 209          'M' => ['multiplier' => 1E6, 'name' => 'mega'],
 210          'k' => ['multiplier' => 1E3, 'name' => 'kilo'],
 211          'h' => ['multiplier' => 1E2, 'name' => 'hecto'],
 212          'e' => ['multiplier' => 1E1, 'name' => 'dekao'],
 213          'da' => ['multiplier' => 1E1, 'name' => 'dekao'],
 214          'd' => ['multiplier' => 1E-1, 'name' => 'deci'],
 215          'c' => ['multiplier' => 1E-2, 'name' => 'centi'],
 216          'm' => ['multiplier' => 1E-3, 'name' => 'milli'],
 217          'u' => ['multiplier' => 1E-6, 'name' => 'micro'],
 218          'n' => ['multiplier' => 1E-9, 'name' => 'nano'],
 219          'p' => ['multiplier' => 1E-12, 'name' => 'pico'],
 220          'f' => ['multiplier' => 1E-15, 'name' => 'femto'],
 221          'a' => ['multiplier' => 1E-18, 'name' => 'atto'],
 222          'z' => ['multiplier' => 1E-21, 'name' => 'zepto'],
 223          'y' => ['multiplier' => 1E-24, 'name' => 'yocto'],
 224      ];
 225  
 226      /**
 227       * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
 228       *
 229       * @var mixed[]
 230       */
 231      private static $binaryConversionMultipliers = [
 232          'Yi' => ['multiplier' => 2 ** 80, 'name' => 'yobi'],
 233          'Zi' => ['multiplier' => 2 ** 70, 'name' => 'zebi'],
 234          'Ei' => ['multiplier' => 2 ** 60, 'name' => 'exbi'],
 235          'Pi' => ['multiplier' => 2 ** 50, 'name' => 'pebi'],
 236          'Ti' => ['multiplier' => 2 ** 40, 'name' => 'tebi'],
 237          'Gi' => ['multiplier' => 2 ** 30, 'name' => 'gibi'],
 238          'Mi' => ['multiplier' => 2 ** 20, 'name' => 'mebi'],
 239          'ki' => ['multiplier' => 2 ** 10, 'name' => 'kibi'],
 240      ];
 241  
 242      /**
 243       * Details of the Units of measure conversion factors, organised by group.
 244       *
 245       * @var mixed[]
 246       */
 247      private static $unitConversions = [
 248          // Conversion uses gram (g) as an intermediate unit
 249          self::CATEGORY_WEIGHT_AND_MASS => [
 250              'g' => 1.0,
 251              'sg' => 6.85217658567918E-05,
 252              'lbm' => 2.20462262184878E-03,
 253              'u' => 6.02214179421676E+23,
 254              'ozm' => 3.52739619495804E-02,
 255              'grain' => 1.54323583529414E+01,
 256              'cwt' => 2.20462262184878E-05,
 257              'shweight' => 2.20462262184878E-05,
 258              'uk_cwt' => 1.96841305522212E-05,
 259              'lcwt' => 1.96841305522212E-05,
 260              'hweight' => 1.96841305522212E-05,
 261              'stone' => 1.57473044417770E-04,
 262              'ton' => 1.10231131092439E-06,
 263              'uk_ton' => 9.84206527611061E-07,
 264              'LTON' => 9.84206527611061E-07,
 265              'brton' => 9.84206527611061E-07,
 266          ],
 267          // Conversion uses meter (m) as an intermediate unit
 268          self::CATEGORY_DISTANCE => [
 269              'm' => 1.0,
 270              'mi' => 6.21371192237334E-04,
 271              'Nmi' => 5.39956803455724E-04,
 272              'in' => 3.93700787401575E+01,
 273              'ft' => 3.28083989501312E+00,
 274              'yd' => 1.09361329833771E+00,
 275              'ang' => 1.0E+10,
 276              'ell' => 8.74890638670166E-01,
 277              'ly' => 1.05700083402462E-16,
 278              'parsec' => 3.24077928966473E-17,
 279              'pc' => 3.24077928966473E-17,
 280              'Pica' => 2.83464566929134E+03,
 281              'Picapt' => 2.83464566929134E+03,
 282              'pica' => 2.36220472440945E+02,
 283              'survey_mi' => 6.21369949494950E-04,
 284          ],
 285          // Conversion uses second (s) as an intermediate unit
 286          self::CATEGORY_TIME => [
 287              'yr' => 3.16880878140289E-08,
 288              'day' => 1.15740740740741E-05,
 289              'd' => 1.15740740740741E-05,
 290              'hr' => 2.77777777777778E-04,
 291              'mn' => 1.66666666666667E-02,
 292              'min' => 1.66666666666667E-02,
 293              'sec' => 1.0,
 294              's' => 1.0,
 295          ],
 296          // Conversion uses Pascal (Pa) as an intermediate unit
 297          self::CATEGORY_PRESSURE => [
 298              'Pa' => 1.0,
 299              'p' => 1.0,
 300              'atm' => 9.86923266716013E-06,
 301              'at' => 9.86923266716013E-06,
 302              'mmHg' => 7.50063755419211E-03,
 303              'psi' => 1.45037737730209E-04,
 304              'Torr' => 7.50061682704170E-03,
 305          ],
 306          // Conversion uses Newton (N) as an intermediate unit
 307          self::CATEGORY_FORCE => [
 308              'N' => 1.0,
 309              'dyn' => 1.0E+5,
 310              'dy' => 1.0E+5,
 311              'lbf' => 2.24808923655339E-01,
 312              'pond' => 1.01971621297793E+02,
 313          ],
 314          // Conversion uses Joule (J) as an intermediate unit
 315          self::CATEGORY_ENERGY => [
 316              'J' => 1.0,
 317              'e' => 9.99999519343231E+06,
 318              'c' => 2.39006249473467E-01,
 319              'cal' => 2.38846190642017E-01,
 320              'eV' => 6.24145700000000E+18,
 321              'ev' => 6.24145700000000E+18,
 322              'HPh' => 3.72506430801000E-07,
 323              'hh' => 3.72506430801000E-07,
 324              'Wh' => 2.77777916238711E-04,
 325              'wh' => 2.77777916238711E-04,
 326              'flb' => 2.37304222192651E+01,
 327              'BTU' => 9.47815067349015E-04,
 328              'btu' => 9.47815067349015E-04,
 329          ],
 330          // Conversion uses Horsepower (HP) as an intermediate unit
 331          self::CATEGORY_POWER => [
 332              'HP' => 1.0,
 333              'h' => 1.0,
 334              'W' => 7.45699871582270E+02,
 335              'w' => 7.45699871582270E+02,
 336              'PS' => 1.01386966542400E+00,
 337          ],
 338          // Conversion uses Tesla (T) as an intermediate unit
 339          self::CATEGORY_MAGNETISM => [
 340              'T' => 1.0,
 341              'ga' => 10000.0,
 342          ],
 343          // Conversion uses litre (l) as an intermediate unit
 344          self::CATEGORY_VOLUME => [
 345              'l' => 1.0,
 346              'L' => 1.0,
 347              'lt' => 1.0,
 348              'tsp' => 2.02884136211058E+02,
 349              'tspm' => 2.0E+02,
 350              'tbs' => 6.76280454036860E+01,
 351              'oz' => 3.38140227018430E+01,
 352              'cup' => 4.22675283773038E+00,
 353              'pt' => 2.11337641886519E+00,
 354              'us_pt' => 2.11337641886519E+00,
 355              'uk_pt' => 1.75975398639270E+00,
 356              'qt' => 1.05668820943259E+00,
 357              'uk_qt' => 8.79876993196351E-01,
 358              'gal' => 2.64172052358148E-01,
 359              'uk_gal' => 2.19969248299088E-01,
 360              'ang3' => 1.0E+27,
 361              'ang^3' => 1.0E+27,
 362              'barrel' => 6.28981077043211E-03,
 363              'bushel' => 2.83775932584017E-02,
 364              'in3' => 6.10237440947323E+01,
 365              'in^3' => 6.10237440947323E+01,
 366              'ft3' => 3.53146667214886E-02,
 367              'ft^3' => 3.53146667214886E-02,
 368              'ly3' => 1.18093498844171E-51,
 369              'ly^3' => 1.18093498844171E-51,
 370              'm3' => 1.0E-03,
 371              'm^3' => 1.0E-03,
 372              'mi3' => 2.39912758578928E-13,
 373              'mi^3' => 2.39912758578928E-13,
 374              'yd3' => 1.30795061931439E-03,
 375              'yd^3' => 1.30795061931439E-03,
 376              'Nmi3' => 1.57426214685811E-13,
 377              'Nmi^3' => 1.57426214685811E-13,
 378              'Pica3' => 2.27769904358706E+07,
 379              'Pica^3' => 2.27769904358706E+07,
 380              'Picapt3' => 2.27769904358706E+07,
 381              'Picapt^3' => 2.27769904358706E+07,
 382              'GRT' => 3.53146667214886E-04,
 383              'regton' => 3.53146667214886E-04,
 384              'MTON' => 8.82866668037215E-04,
 385          ],
 386          // Conversion uses hectare (ha) as an intermediate unit
 387          self::CATEGORY_AREA => [
 388              'ha' => 1.0,
 389              'uk_acre' => 2.47105381467165E+00,
 390              'us_acre' => 2.47104393046628E+00,
 391              'ang2' => 1.0E+24,
 392              'ang^2' => 1.0E+24,
 393              'ar' => 1.0E+02,
 394              'ft2' => 1.07639104167097E+05,
 395              'ft^2' => 1.07639104167097E+05,
 396              'in2' => 1.55000310000620E+07,
 397              'in^2' => 1.55000310000620E+07,
 398              'ly2' => 1.11725076312873E-28,
 399              'ly^2' => 1.11725076312873E-28,
 400              'm2' => 1.0E+04,
 401              'm^2' => 1.0E+04,
 402              'Morgen' => 4.0E+00,
 403              'mi2' => 3.86102158542446E-03,
 404              'mi^2' => 3.86102158542446E-03,
 405              'Nmi2' => 2.91553349598123E-03,
 406              'Nmi^2' => 2.91553349598123E-03,
 407              'Pica2' => 8.03521607043214E+10,
 408              'Pica^2' => 8.03521607043214E+10,
 409              'Picapt2' => 8.03521607043214E+10,
 410              'Picapt^2' => 8.03521607043214E+10,
 411              'yd2' => 1.19599004630108E+04,
 412              'yd^2' => 1.19599004630108E+04,
 413          ],
 414          // Conversion uses bit (bit) as an intermediate unit
 415          self::CATEGORY_INFORMATION => [
 416              'bit' => 1.0,
 417              'byte' => 0.125,
 418          ],
 419          // Conversion uses Meters per Second (m/s) as an intermediate unit
 420          self::CATEGORY_SPEED => [
 421              'm/s' => 1.0,
 422              'm/sec' => 1.0,
 423              'm/h' => 3.60E+03,
 424              'm/hr' => 3.60E+03,
 425              'mph' => 2.23693629205440E+00,
 426              'admkn' => 1.94260256941567E+00,
 427              'kn' => 1.94384449244060E+00,
 428          ],
 429      ];
 430  
 431      /**
 432       *    getConversionGroups
 433       * Returns a list of the different conversion groups for UOM conversions.
 434       *
 435       * @return array
 436       */
 437      public static function getConversionCategories()
 438      {
 439          $conversionGroups = [];
 440          foreach (self::$conversionUnits as $conversionUnit) {
 441              $conversionGroups[] = $conversionUnit['Group'];
 442          }
 443  
 444          return array_merge(array_unique($conversionGroups));
 445      }
 446  
 447      /**
 448       *    getConversionGroupUnits
 449       * Returns an array of units of measure, for a specified conversion group, or for all groups.
 450       *
 451       * @param string $category The group whose units of measure you want to retrieve
 452       *
 453       * @return array
 454       */
 455      public static function getConversionCategoryUnits($category = null)
 456      {
 457          $conversionGroups = [];
 458          foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) {
 459              if (($category === null) || ($conversionGroup['Group'] == $category)) {
 460                  $conversionGroups[$conversionGroup['Group']][] = $conversionUnit;
 461              }
 462          }
 463  
 464          return $conversionGroups;
 465      }
 466  
 467      /**
 468       * getConversionGroupUnitDetails.
 469       *
 470       * @param string $category The group whose units of measure you want to retrieve
 471       *
 472       * @return array
 473       */
 474      public static function getConversionCategoryUnitDetails($category = null)
 475      {
 476          $conversionGroups = [];
 477          foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) {
 478              if (($category === null) || ($conversionGroup['Group'] == $category)) {
 479                  $conversionGroups[$conversionGroup['Group']][] = [
 480                      'unit' => $conversionUnit,
 481                      'description' => $conversionGroup['Unit Name'],
 482                  ];
 483              }
 484          }
 485  
 486          return $conversionGroups;
 487      }
 488  
 489      /**
 490       *    getConversionMultipliers
 491       * Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
 492       *
 493       * @return array of mixed
 494       */
 495      public static function getConversionMultipliers()
 496      {
 497          return self::$conversionMultipliers;
 498      }
 499  
 500      /**
 501       *    getBinaryConversionMultipliers
 502       * Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure in CONVERTUOM().
 503       *
 504       * @return array of mixed
 505       */
 506      public static function getBinaryConversionMultipliers()
 507      {
 508          return self::$binaryConversionMultipliers;
 509      }
 510  
 511      /**
 512       * CONVERT.
 513       *
 514       * Converts a number from one measurement system to another.
 515       *    For example, CONVERT can translate a table of distances in miles to a table of distances
 516       * in kilometers.
 517       *
 518       *    Excel Function:
 519       *        CONVERT(value,fromUOM,toUOM)
 520       *
 521       * @param float|int $value the value in fromUOM to convert
 522       * @param string $fromUOM the units for value
 523       * @param string $toUOM the units for the result
 524       *
 525       * @return float|string
 526       */
 527      public static function CONVERT($value, $fromUOM, $toUOM)
 528      {
 529          $value = Functions::flattenSingleValue($value);
 530          $fromUOM = Functions::flattenSingleValue($fromUOM);
 531          $toUOM = Functions::flattenSingleValue($toUOM);
 532  
 533          if (!is_numeric($value)) {
 534              return Functions::VALUE();
 535          }
 536  
 537          try {
 538              [$fromUOM, $fromCategory, $fromMultiplier] = self::getUOMDetails($fromUOM);
 539              [$toUOM, $toCategory, $toMultiplier] = self::getUOMDetails($toUOM);
 540          } catch (Exception $e) {
 541              return Functions::NA();
 542          }
 543  
 544          if ($fromCategory !== $toCategory) {
 545              return Functions::NA();
 546          }
 547  
 548          $value *= $fromMultiplier;
 549  
 550          if (($fromUOM === $toUOM) && ($fromMultiplier === $toMultiplier)) {
 551              //    We've already factored $fromMultiplier into the value, so we need
 552              //        to reverse it again
 553              return $value / $fromMultiplier;
 554          } elseif ($fromUOM === $toUOM) {
 555              return $value / $toMultiplier;
 556          } elseif ($fromCategory === self::CATEGORY_TEMPERATURE) {
 557              return self::convertTemperature($fromUOM, $toUOM, $value);
 558          }
 559  
 560          $baseValue = $value * (1.0 / self::$unitConversions[$fromCategory][$fromUOM]);
 561  
 562          return ($baseValue * self::$unitConversions[$fromCategory][$toUOM]) / $toMultiplier;
 563      }
 564  
 565      private static function getUOMDetails(string $uom)
 566      {
 567          if (isset(self::$conversionUnits[$uom])) {
 568              $unitCategory = self::$conversionUnits[$uom]['Group'];
 569  
 570              return [$uom, $unitCategory, 1.0];
 571          }
 572  
 573          // Check 1-character standard metric multiplier prefixes
 574          $multiplierType = substr($uom, 0, 1);
 575          $uom = substr($uom, 1);
 576          if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) {
 577              if (self::$conversionUnits[$uom]['AllowPrefix'] === false) {
 578                  throw new Exception('Prefix not allowed for UoM');
 579              }
 580              $unitCategory = self::$conversionUnits[$uom]['Group'];
 581  
 582              return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']];
 583          }
 584  
 585          $multiplierType .= substr($uom, 0, 1);
 586          $uom = substr($uom, 1);
 587  
 588          // Check 2-character standard metric multiplier prefixes
 589          if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) {
 590              if (self::$conversionUnits[$uom]['AllowPrefix'] === false) {
 591                  throw new Exception('Prefix not allowed for UoM');
 592              }
 593              $unitCategory = self::$conversionUnits[$uom]['Group'];
 594  
 595              return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']];
 596          }
 597  
 598          // Check 2-character binary multiplier prefixes
 599          if (isset(self::$conversionUnits[$uom], self::$binaryConversionMultipliers[$multiplierType])) {
 600              if (self::$conversionUnits[$uom]['AllowPrefix'] === false) {
 601                  throw new Exception('Prefix not allowed for UoM');
 602              }
 603              $unitCategory = self::$conversionUnits[$uom]['Group'];
 604              if ($unitCategory !== 'Information') {
 605                  throw new Exception('Binary Prefix is only allowed for Information UoM');
 606              }
 607  
 608              return [$uom, $unitCategory, self::$binaryConversionMultipliers[$multiplierType]['multiplier']];
 609          }
 610  
 611          throw new Exception('UoM Not Found');
 612      }
 613  
 614      /**
 615       * @param float|int $value
 616       *
 617       * @return float|int
 618       */
 619      protected static function convertTemperature(string $fromUOM, string $toUOM, $value)
 620      {
 621          $fromUOM = self::resolveTemperatureSynonyms($fromUOM);
 622          $toUOM = self::resolveTemperatureSynonyms($toUOM);
 623  
 624          if ($fromUOM === $toUOM) {
 625              return $value;
 626          }
 627  
 628          // Convert to Kelvin
 629          switch ($fromUOM) {
 630              case 'F':
 631                  $value = ($value - 32) / 1.8 + 273.15;
 632  
 633                  break;
 634              case 'C':
 635                  $value += 273.15;
 636  
 637                  break;
 638              case 'Rank':
 639                  $value /= 1.8;
 640  
 641                  break;
 642              case 'Reau':
 643                  $value = $value * 1.25 + 273.15;
 644  
 645                  break;
 646          }
 647  
 648          // Convert from Kelvin
 649          switch ($toUOM) {
 650              case 'F':
 651                  $value = ($value - 273.15) * 1.8 + 32.00;
 652  
 653                  break;
 654              case 'C':
 655                  $value -= 273.15;
 656  
 657                  break;
 658              case 'Rank':
 659                  $value *= 1.8;
 660  
 661                  break;
 662              case 'Reau':
 663                  $value = ($value - 273.15) * 0.80000;
 664  
 665                  break;
 666          }
 667  
 668          return $value;
 669      }
 670  
 671      private static function resolveTemperatureSynonyms(string $uom)
 672      {
 673          switch ($uom) {
 674              case 'fah':
 675                  return 'F';
 676              case 'cel':
 677                  return 'C';
 678              case 'kel':
 679                  return 'K';
 680          }
 681  
 682          return $uom;
 683      }
 684  }