diff options
Diffstat (limited to 'browser/components/urlbar/unitconverters')
4 files changed, 524 insertions, 0 deletions
diff --git a/browser/components/urlbar/unitconverters/UnitConverterSimple.sys.mjs b/browser/components/urlbar/unitconverters/UnitConverterSimple.sys.mjs new file mode 100644 index 0000000000..47ba0cc856 --- /dev/null +++ b/browser/components/urlbar/unitconverters/UnitConverterSimple.sys.mjs @@ -0,0 +1,243 @@ +/* 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/. */ + +// NOTE: This units table need to be localized upon supporting multi locales +// since it supports en-US only. +// e.g. Should support plugada or funty as well for pound. +const UNITS_GROUPS = [ + { + // Angle + degree: 1, + deg: "degree", + d: "degree", + radian: Math.PI / 180.0, + rad: "radian", + r: "radian", + gradian: 1 / 0.9, + grad: "gradian", + g: "gradian", + minute: 60, + min: "minute", + m: "minute", + second: 3600, + sec: "second", + s: "second", + sign: 1 / 30.0, + mil: 1 / 0.05625, + revolution: 1 / 360.0, + circle: 1 / 360.0, + turn: 1 / 360.0, + quadrant: 1 / 90.0, + rightangle: 1 / 90.0, + sextant: 1 / 60.0, + }, + { + // Force + newton: 1, + n: "newton", + kilonewton: 0.001, + kn: "kilonewton", + "gram-force": 101.9716213, + gf: "gram-force", + "kilogram-force": 0.1019716213, + kgf: "kilogram-force", + "ton-force": 0.0001019716213, + tf: "ton-force", + exanewton: 1.0e-18, + en: "exanewton", + petanewton: 1.0e-15, + PN: "petanewton", + Pn: "petanewton", + teranewton: 1.0e-12, + tn: "teranewton", + giganewton: 1.0e-9, + gn: "giganewton", + meganewton: 0.000001, + MN: "meganewton", + Mn: "meganewton", + hectonewton: 0.01, + hn: "hectonewton", + dekanewton: 0.1, + dan: "dekanewton", + decinewton: 10, + dn: "decinewton", + centinewton: 100, + cn: "centinewton", + millinewton: 1000, + mn: "millinewton", + micronewton: 1000000, + µn: "micronewton", + nanonewton: 1000000000, + nn: "nanonewton", + piconewton: 1000000000000, + pn: "piconewton", + femtonewton: 1000000000000000, + fn: "femtonewton", + attonewton: 1000000000000000000, + an: "attonewton", + dyne: 100000, + dyn: "dyne", + "joule/meter": 1, + "j/m": "joule/meter", + "joule/centimeter": 100, + "j/cm": "joule/centimeter", + "ton-force-short": 0.0001124045, + short: "ton-force-short", + "ton-force-long": 0.0001003611, + tonf: "ton-force-long", + "kip-force": 0.0002248089, + kipf: "kip-force", + "pound-force": 0.2248089431, + lbf: "pound-force", + "ounce-force": 3.5969430896, + ozf: "ounce-force", + poundal: 7.2330138512, + pdl: "poundal", + pond: 101.9716213, + p: "pond", + kilopond: 0.1019716213, + kp: "kilopond", + }, + { + // Length + meter: 1, + m: "meter", + nanometer: 1000000000, + micrometer: 1000000, + millimeter: 1000, + mm: "millimeter", + centimeter: 100, + cm: "centimeter", + kilometer: 0.001, + km: "kilometer", + mile: 0.0006213689, + mi: "mile", + yard: 1.0936132983, + yd: "yard", + foot: 3.280839895, + feet: "foot", + ft: "foot", + inch: 39.37007874, + inches: "inch", + in: "inch", + }, + { + // Mass + kilogram: 1, + kg: "kilogram", + gram: 1000, + g: "gram", + milligram: 1000000, + mg: "milligram", + ton: 0.001, + t: "ton", + longton: 0.0009842073, + "l.t.": "longton", + "l/t": "longton", + shortton: 0.0011023122, + "s.t.": "shortton", + "s/t": "shortton", + pound: 2.2046244202, + lbs: "pound", + lb: "pound", + ounce: 35.273990723, + oz: "ounce", + carat: 5000, + ffd: 5000, + }, +]; + +// There are some units that will be same in lower case in same unit group. +// e.g. Mn: meganewton and mn: millinewton on force group. +// Handle them as case-sensitive. +const CASE_SENSITIVE_UNITS = ["PN", "Pn", "MN", "Mn"]; + +const NUMBER_REGEX = "-?\\d+(?:\\.\\d+)?\\s*"; +const UNIT_REGEX = "[A-Za-zµ0-9_./-]+"; + +// 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 simple unit such as angle and length. + */ +export class UnitConverterSimple { + /** + * 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 = findUnitGroup(regexResult[2], regexResult[3]); + + if (!target) { + return null; + } + + const { group, inputUnit, outputUnit } = target; + const inputNumber = Number(regexResult[1]); + const outputNumber = parseFloat( + ((inputNumber / group[inputUnit]) * group[outputUnit]).toPrecision( + DECIMAL_PRECISION + ) + ); + + try { + return new Intl.NumberFormat("en-US", { + style: "unit", + unit: outputUnit, + maximumFractionDigits: DECIMAL_PRECISION, + }).format(outputNumber); + } catch (e) {} + + return `${outputNumber} ${outputUnit}`; + } +} + +/** + * 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 findUnitGroup(inputUnit, outputUnit) { + inputUnit = toSuitableUnit(inputUnit); + outputUnit = toSuitableUnit(outputUnit); + + const group = UNITS_GROUPS.find(ug => ug[inputUnit] && ug[outputUnit]); + + if (!group) { + return null; + } + + const inputValue = group[inputUnit]; + const outputValue = group[outputUnit]; + + return { + group, + inputUnit: typeof inputValue === "string" ? inputValue : inputUnit, + outputUnit: typeof outputValue === "string" ? outputValue : outputUnit, + }; +} + +function toSuitableUnit(unit) { + return CASE_SENSITIVE_UNITS.includes(unit) ? unit : unit.toLowerCase(); +} diff --git a/browser/components/urlbar/unitconverters/UnitConverterTemperature.sys.mjs b/browser/components/urlbar/unitconverters/UnitConverterTemperature.sys.mjs new file mode 100644 index 0000000000..5a78d20577 --- /dev/null +++ b/browser/components/urlbar/unitconverters/UnitConverterTemperature.sys.mjs @@ -0,0 +1,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)); +} diff --git a/browser/components/urlbar/unitconverters/UnitConverterTimezone.sys.mjs b/browser/components/urlbar/unitconverters/UnitConverterTimezone.sys.mjs new file mode 100644 index 0000000000..50ba924bed --- /dev/null +++ b/browser/components/urlbar/unitconverters/UnitConverterTimezone.sys.mjs @@ -0,0 +1,148 @@ +/* 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 TIMEZONES = { + IDLW: -12, + NT: -11, + HST: -10, + AKST: -9, + PST: -8, + AKDT: -8, + MST: -7, + PDT: -7, + CST: -6, + MDT: -6, + EST: -5, + CDT: -5, + EDT: -4, + AST: -4, + GUY: -3, + ADT: -3, + AT: -2, + UTC: 0, + GMT: 0, + Z: 0, + WET: 0, + WEST: 1, + CET: 1, + BST: 1, + IST: 1, + CEST: 2, + EET: 2, + EEST: 3, + MSK: 3, + MSD: 4, + ZP4: 4, + ZP5: 5, + ZP6: 6, + WAST: 7, + AWST: 8, + WST: 8, + JST: 9, + ACST: 9.5, + ACDT: 10.5, + AEST: 10, + AEDT: 11, + NZST: 12, + IDLE: 12, + NZD: 13, +}; + +const TIME_REGEX = "([0-2]?[0-9])(:([0-5][0-9]))?\\s*([ap]m)?"; +const TIMEZONE_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( + `^(${TIME_REGEX}|now)\\s*(${TIMEZONE_REGEX})?(?:\\s+in\\s+|\\s+to\\s+|\\s*=\\s*)(${TIMEZONE_REGEX}|here)`, + "i" +); + +const KEYWORD_HERE = "HERE"; +const KEYWORD_NOW = "NOW"; + +/** + * This module converts timezone. + */ +export class UnitConverterTimezone { + /** + * 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 inputTime = regexResult[1].toUpperCase(); + const inputTimezone = regexResult[6]?.toUpperCase(); + let outputTimezone = regexResult[7].toUpperCase(); + + if ( + (inputTimezone && + inputTimezone !== KEYWORD_NOW && + !(inputTimezone in TIMEZONES)) || + (outputTimezone !== KEYWORD_HERE && !(outputTimezone in TIMEZONES)) + ) { + return null; + } + + const inputDate = new Date(); + let isMeridiemNeeded = false; + if (inputTime === KEYWORD_NOW) { + inputDate.setUTCHours(inputDate.getHours()); + inputDate.setUTCMinutes(inputDate.getMinutes()); + } else { + // If the input was given as AM/PM, we need to convert it to 24h. + // 12AM is converted to 00, and for PM times we add 12 to the hour value except for 12PM. + // If the input is for example 23PM, we use 23 as the hour - we don't add 12 as this would result in a date increment. + const inputAMPM = regexResult[5]?.toLowerCase() || ""; + const inputHours = + regexResult[2] === "12" && inputAMPM === "am" + ? 0 + : Number(regexResult[2]); + const inputMinutes = regexResult[4] ? Number(regexResult[4]) : 0; + const inputMeridianHourShift = + inputAMPM === "pm" && inputHours < 12 ? 12 : 0; + inputDate.setUTCHours(inputHours + inputMeridianHourShift); + inputDate.setUTCMinutes(inputMinutes); + isMeridiemNeeded = !!inputAMPM; + } + + const inputOffset = inputTimezone + ? TIMEZONES[inputTimezone] * 60 + : -inputDate.getTimezoneOffset(); + let outputOffset; + if (outputTimezone === KEYWORD_HERE) { + outputOffset = -inputDate.getTimezoneOffset(); + const sign = -inputDate.getTimezoneOffset() > 0 ? "+" : "-"; + const hours = parseInt(Math.abs(outputOffset) / 60); + const minutes = formatMinutes((outputOffset % 60) * 60); + outputTimezone = `UTC${sign}${hours}${minutes}`; + } else { + outputOffset = TIMEZONES[outputTimezone] * 60; + } + + const outputDate = new Date(inputDate.getTime()); + outputDate.setUTCMinutes( + outputDate.getUTCMinutes() - inputOffset + outputOffset + ); + + const time = new Intl.DateTimeFormat("en-US", { + timeStyle: "short", + hour12: isMeridiemNeeded, + timeZone: "UTC", + }).format(outputDate); + + return `${time} ${outputTimezone}`; + } +} + +function formatMinutes(minutes) { + return minutes.toString().padStart(2, "0"); +} diff --git a/browser/components/urlbar/unitconverters/moz.build b/browser/components/urlbar/unitconverters/moz.build new file mode 100644 index 0000000000..649522658f --- /dev/null +++ b/browser/components/urlbar/unitconverters/moz.build @@ -0,0 +1,9 @@ +# 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/. + +EXTRA_JS_MODULES += [ + "UnitConverterSimple.sys.mjs", + "UnitConverterTemperature.sys.mjs", + "UnitConverterTimezone.sys.mjs", +] |