UnitConverterTimezone.sys.mjs (3927B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const TIMEZONES = { 6 IDLW: -12, 7 NT: -11, 8 HST: -10, 9 AKST: -9, 10 PST: -8, 11 AKDT: -8, 12 MST: -7, 13 PDT: -7, 14 CST: -6, 15 MDT: -6, 16 EST: -5, 17 CDT: -5, 18 EDT: -4, 19 AST: -4, 20 GUY: -3, 21 ADT: -3, 22 AT: -2, 23 UTC: 0, 24 GMT: 0, 25 Z: 0, 26 WET: 0, 27 WEST: 1, 28 CET: 1, 29 BST: 1, 30 IST: 1, 31 CEST: 2, 32 EET: 2, 33 EEST: 3, 34 MSK: 3, 35 MSD: 4, 36 ZP4: 4, 37 ZP5: 5, 38 ZP6: 6, 39 WAST: 7, 40 AWST: 8, 41 WST: 8, 42 JST: 9, 43 ACST: 9.5, 44 ACDT: 10.5, 45 AEST: 10, 46 AEDT: 11, 47 NZST: 12, 48 IDLE: 12, 49 NZD: 13, 50 }; 51 52 const TIME_REGEX = "([0-2]?[0-9])(:([0-5][0-9]))?\\s*([ap]m)?"; 53 const TIMEZONE_REGEX = "\\w+"; 54 55 // NOTE: This regex need to be localized upon supporting multi locales 56 // since it supports en-US input format only. 57 const QUERY_REGEX = new RegExp( 58 `^(${TIME_REGEX}|now)\\s*(${TIMEZONE_REGEX})?(?:\\s+in\\s+|\\s+to\\s+|\\s*=\\s*)(${TIMEZONE_REGEX}|here)`, 59 "i" 60 ); 61 62 const KEYWORD_HERE = "HERE"; 63 const KEYWORD_NOW = "NOW"; 64 65 /** 66 * This module converts timezone. 67 */ 68 export class UnitConverterTimezone { 69 /** 70 * Convert the given search string. 71 * 72 * @param {string} searchString 73 * The string to be converted 74 * @returns {string} conversion result. 75 */ 76 convert(searchString) { 77 const regexResult = QUERY_REGEX.exec(searchString); 78 if (!regexResult) { 79 return null; 80 } 81 82 const inputTime = regexResult[1].toUpperCase(); 83 const inputTimezone = regexResult[6]?.toUpperCase(); 84 let outputTimezone = regexResult[7].toUpperCase(); 85 86 if ( 87 (inputTimezone && 88 inputTimezone !== KEYWORD_NOW && 89 !(inputTimezone in TIMEZONES)) || 90 (outputTimezone !== KEYWORD_HERE && !(outputTimezone in TIMEZONES)) 91 ) { 92 return null; 93 } 94 95 const inputDate = new Date(); 96 let isMeridiemNeeded = false; 97 if (inputTime === KEYWORD_NOW) { 98 inputDate.setUTCHours(inputDate.getHours()); 99 inputDate.setUTCMinutes(inputDate.getMinutes()); 100 } else { 101 // If the input was given as AM/PM, we need to convert it to 24h. 102 // 12AM is converted to 00, and for PM times we add 12 to the hour value except for 12PM. 103 // 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. 104 const inputAMPM = regexResult[5]?.toLowerCase() || ""; 105 const inputHours = 106 regexResult[2] === "12" && inputAMPM === "am" 107 ? 0 108 : Number(regexResult[2]); 109 const inputMinutes = regexResult[4] ? Number(regexResult[4]) : 0; 110 const inputMeridianHourShift = 111 inputAMPM === "pm" && inputHours < 12 ? 12 : 0; 112 inputDate.setUTCHours(inputHours + inputMeridianHourShift); 113 inputDate.setUTCMinutes(inputMinutes); 114 isMeridiemNeeded = !!inputAMPM; 115 } 116 117 const inputOffset = inputTimezone 118 ? TIMEZONES[inputTimezone] * 60 119 : -inputDate.getTimezoneOffset(); 120 let outputOffset; 121 if (outputTimezone === KEYWORD_HERE) { 122 outputOffset = -inputDate.getTimezoneOffset(); 123 const sign = -inputDate.getTimezoneOffset() > 0 ? "+" : "-"; 124 const hours = Math.floor(Math.abs(outputOffset) / 60); 125 const minutes = formatMinutes((outputOffset % 60) * 60); 126 outputTimezone = `UTC${sign}${hours}${minutes}`; 127 } else { 128 outputOffset = TIMEZONES[outputTimezone] * 60; 129 } 130 131 const outputDate = new Date(inputDate.getTime()); 132 outputDate.setUTCMinutes( 133 outputDate.getUTCMinutes() - inputOffset + outputOffset 134 ); 135 136 const time = new Intl.DateTimeFormat("en-US", { 137 timeStyle: "short", 138 hour12: isMeridiemNeeded, 139 timeZone: "UTC", 140 }).format(outputDate); 141 142 return `${time} ${outputTimezone}`; 143 } 144 } 145 146 function formatMinutes(minutes) { 147 return minutes.toString().padStart(2, "0"); 148 }