summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/unitconverters/UnitConverterTemperature.sys.mjs
blob: 5a78d205777975dc49b2f3dc5ef3ba824886ed5e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const ABSOLUTE = ["celsius", "kelvin", "fahrenheit"];
const ALIAS = ["c", "k", "f"];
const UNITS = [...ABSOLUTE, ...ALIAS];

const NUMBER_REGEX = "-?\\d+(?:\\.\\d+)?\\s*";
const UNIT_REGEX = "\\w+";

// NOTE: This regex need to be localized upon supporting multi locales
//       since it supports en-US input format only.
const QUERY_REGEX = new RegExp(
  `^(${NUMBER_REGEX})(${UNIT_REGEX})(?:\\s+in\\s+|\\s+to\\s+|\\s*=\\s*)(${UNIT_REGEX})`,
  "i"
);

const DECIMAL_PRECISION = 10;

/**
 * This module converts temperature unit.
 */
export class UnitConverterTemperature {
  /**
   * Convert the given search string.
   *
   * @param {string} searchString
   *   The string to be converted
   * @returns {string} conversion result.
   */
  convert(searchString) {
    const regexResult = QUERY_REGEX.exec(searchString);
    if (!regexResult) {
      return null;
    }

    const target = findUnits(regexResult[2], regexResult[3]);

    if (!target) {
      return null;
    }

    const { inputUnit, outputUnit } = target;
    const inputNumber = Number(regexResult[1]);
    const inputChar = inputUnit.charAt(0);
    const outputChar = outputUnit.charAt(0);

    let outputNumber;
    if (inputChar === outputChar) {
      outputNumber = inputNumber;
    } else {
      outputNumber = this[`${inputChar}2${outputChar}`](inputNumber);
    }

    outputNumber = parseFloat(outputNumber.toPrecision(DECIMAL_PRECISION));

    try {
      return new Intl.NumberFormat("en-US", {
        style: "unit",
        unit: outputUnit,
        maximumFractionDigits: DECIMAL_PRECISION,
      }).format(outputNumber);
    } catch (e) {}

    return `${outputNumber} ${outputUnit}`;
  }

  c2k(t) {
    return t + 273.15;
  }

  c2f(t) {
    return t * 1.8 + 32;
  }

  k2c(t) {
    return t - 273.15;
  }

  k2f(t) {
    return this.c2f(this.k2c(t));
  }

  f2c(t) {
    return (t - 32) / 1.8;
  }

  f2k(t) {
    return this.c2k(this.f2c(t));
  }
}

/**
 * Returns the suitable units for the given two values.
 * If could not found suitable unit, returns null.
 *
 * @param {string} inputUnit
 *    A set of units to convert, mapped to the `inputUnit` value on the return
 * @param {string} outputUnit
 *    A set of units to convert, mapped to the `outputUnit` value on the return
 * @returns {{ inputUnit: string, outputUnit: string }} The suitable units.
 */
function findUnits(inputUnit, outputUnit) {
  inputUnit = inputUnit.toLowerCase();
  outputUnit = outputUnit.toLowerCase();

  if (!UNITS.includes(inputUnit) || !UNITS.includes(outputUnit)) {
    return null;
  }

  return {
    inputUnit: toAbsoluteUnit(inputUnit),
    outputUnit: toAbsoluteUnit(outputUnit),
  };
}

function toAbsoluteUnit(unit) {
  if (unit.length !== 1) {
    return unit;
  }

  return ABSOLUTE.find(a => a.startsWith(unit));
}