summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/unitconverters/UnitConverterTimezone.sys.mjs
blob: 50ba924bed90513b75280f007ce5eceecc87ffff (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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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");
}