├── .gitignore
├── 8A920179-B636-4465-A1F1-66C544A085CD.png
├── README.md
├── icon.png
├── index.js
├── info.plist
├── package.json
├── test.js
├── timezone-expander.alfredworkflow
├── timezone_names.js
└── tz_expander.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | package-lock.json
3 | .DS_Store
--------------------------------------------------------------------------------
/8A920179-B636-4465-A1F1-66C544A085CD.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devonzuegel/timezone-expander.alfredworkflow/2ca6fd45e56e4bdff25082baf78b977463084c1c/8A920179-B636-4465-A1F1-66C544A085CD.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | An [Alfred workflow](https://www.alfredapp.com/workflows) that helps you write the same time in multiple timezones.
2 |
3 | ---
4 |
5 | ### Example
6 |
7 | If your system time is in Miami (ET) and you enter `tz 5pm PT Shanghai Zurich` in your Alfred launcher, this result will be pasted into your topmost app: `2pm PT / 5am Shanghai / 11pm Zurich`.
8 |
9 | Here's a breakdown of how the input works:
10 | - **`tz`** – This first argument is the command that tells to Alfred what workflow to run.
11 | - **`5pm`** – This second argument is your local time. In this example, that means 5pm in Miami, which is in the Eastern Timezone.
12 | - **`PT Shanghai Zurich`** – All of the remaining arguments are a list of cities and/or timezones you want to display.
13 | - Note: You can also put your local timezone in this list in order to include it in the results (e.g. input: `tz 5pm PT ET` → output: `2pm PT / 5pm ET`).
14 |
15 | The input can handle either spaces and slashes as delimiters. This means that all of the following inputs give the same output:
16 | - `tz 5pm PT Shanghai Zurich`
17 | - `tz 5pm/PT/Shanghai/Zurich`
18 | - `tz 5pm / PT / Shanghai / Zurich`
19 |
20 | You can find more examples in [`test.js`](./test.js).
21 |
22 | ---
23 |
24 | ### Installation
25 |
26 | 1. Find your `Alfred.alfredpreferences`, right-click on it, and select "Show package contents". You should now see a list of directories that includes one called `workflows`.
27 | 2. Download this directory, and drag it into the `workflows` directory.
28 | 3. Open this directory in your terminal and install the dependencies with `npm install`.
29 | 4. Now, when you open your Alfred Preferences from the Alfred launcher, you should see a new workflow called `Timezone expander` in the left-hand sidebar. If so, you should be able to use this workflow from the launcher now! 🎉
30 |
31 | ---
32 |
33 | # Running the script from the command line
34 |
35 | Works something like this:
36 | (not exactly though, I was too lazy to fully figure it out — this is for future Devon to deal with)
37 |
38 | ```bash
39 | /usr/bin/osascript ~/Downloads/Multi\ Timezones\ \(2\)/timezone-expander.js 3pm PT
40 | ```
41 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devonzuegel/timezone-expander.alfredworkflow/2ca6fd45e56e4bdff25082baf78b977463084c1c/icon.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import alfy from 'alfy'
2 | import {tz_expander} from './tz_expander.js'
3 |
4 | // No newline at the end
5 | process.stdout.write(tz_expander(alfy.input))
6 |
--------------------------------------------------------------------------------
/info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | bundleid
6 | timezone-expander
7 | category
8 | Productivity
9 | connections
10 |
11 | 4A009174-DD5C-40F8-9CA7-3C9985E9D292
12 |
13 |
14 | destinationuid
15 | 7CAE867E-8A3A-4A4C-B5B0-7E2BEB324369
16 | modifiers
17 | 0
18 | modifiersubtext
19 |
20 | vitoclose
21 |
22 |
23 |
24 | 8A920179-B636-4465-A1F1-66C544A085CD
25 |
26 |
27 | destinationuid
28 | 4A009174-DD5C-40F8-9CA7-3C9985E9D292
29 | modifiers
30 | 0
31 | modifiersubtext
32 |
33 | vitoclose
34 |
35 |
36 |
37 |
38 | createdby
39 | Devon Zuegel
40 | description
41 |
42 | disabled
43 |
44 | name
45 | Timezone expander
46 | objects
47 |
48 |
49 | config
50 |
51 | autopaste
52 |
53 | clipboardtext
54 | {query}
55 | ignoredynamicplaceholders
56 |
57 | transient
58 |
59 |
60 | type
61 | alfred.workflow.output.clipboard
62 | uid
63 | 7CAE867E-8A3A-4A4C-B5B0-7E2BEB324369
64 | version
65 | 3
66 |
67 |
68 | config
69 |
70 | argumenttype
71 | 0
72 | keyword
73 | tz
74 | subtext
75 |
76 | text
77 | Timezone expander
78 | withspace
79 |
80 |
81 | type
82 | alfred.workflow.input.keyword
83 | uid
84 | 8A920179-B636-4465-A1F1-66C544A085CD
85 | version
86 | 1
87 |
88 |
89 | config
90 |
91 | concurrently
92 |
93 | escaping
94 | 68
95 | script
96 | ./node_modules/.bin/run-node index.js "$1"
97 | scriptargtype
98 | 1
99 | scriptfile
100 |
101 | type
102 | 0
103 |
104 | type
105 | alfred.workflow.action.script
106 | uid
107 | 4A009174-DD5C-40F8-9CA7-3C9985E9D292
108 | version
109 | 2
110 |
111 |
112 | readme
113 | This Snippet Trigger example workflow contains 4 types of actions:
114 |
115 | 1. Triggering a system command
116 | 2. Running a script and pasting the output text
117 | 3. Requiring you enter free text and pasting the output
118 | 4. Choosing from a list and pasting the output
119 |
120 | You'll find out more about this feature in the Snippet Trigger object documentation on our website.
121 |
122 | Need help with workflows? Pop by the forum (alfredforum.com) if you have any further questions :)
123 | uidata
124 |
125 | 4A009174-DD5C-40F8-9CA7-3C9985E9D292
126 |
127 | xpos
128 | 245
129 | ypos
130 | 135
131 |
132 | 7CAE867E-8A3A-4A4C-B5B0-7E2BEB324369
133 |
134 | xpos
135 | 485
136 | ypos
137 | 10
138 |
139 | 8A920179-B636-4465-A1F1-66C544A085CD
140 |
141 | xpos
142 | 45
143 | ypos
144 | 100
145 |
146 |
147 | version
148 | 1.0
149 | webaddress
150 | devonzuegel.com
151 |
152 |
153 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "timezone-expander",
3 | "version": "1.0.0",
4 | "description": "",
5 | "type": "module",
6 | "main": "index.js",
7 | "author": "",
8 | "license": "ISC",
9 | "dependencies": {
10 | "alfy": "^0.12.1",
11 | "date-fns": "^2.23.0",
12 | "date-fns-tz": "^1.1.6",
13 | "string-similarity": "^4.0.4"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | import {tz_expander} from './tz_expander.js'
2 |
3 | /**
4 | * Note to future self: These tests will likely break once we are in a different
5 | * time of year, because they are a function of the current timezone offsets,
6 | * which change throughout the year. This is why the variable is test_sept4, to
7 | * emphasize that they are only guaranteed to pass on Sept 4, 2021 (the day they
8 | * were originally written).
9 | */
10 | const tests_sept4 = [
11 | ['10:15am PT ET', '7:15am PT / 10:15am ET'],
12 | ['9AM PT ET', '6am PT / 9am ET'],
13 | ['9AM / PT / ET', '6am PT / 9am ET'],
14 | ['9am/ PT /ET', '6am PT / 9am ET'],
15 | ['9am/PT/ET', '6am PT / 9am ET'],
16 | ['9am London', '2pm London'],
17 | ['9am UK', '2pm UK'],
18 | ['2pm/pt/et', '11am PT / 2pm ET'],
19 | ['2pm/SF/miami', '11am SF / 2pm Miami'],
20 | ['2pm/SF/miami ', '11am SF / 2pm Miami'],
21 | ]
22 |
23 | const results = tests_sept4.map(([input, expected]) => {
24 | const output = tz_expander(input, true)
25 | if (output == expected) {
26 | console.log()
27 | console.log('🟢 Test passed')
28 | } else {
29 | console.log()
30 | console.log('🔴 Test failed:')
31 | console.log(' input: ', input)
32 | console.log(' expected: ', expected)
33 | console.log(' output: ', output)
34 | }
35 | })
36 |
37 | console.log()
38 |
--------------------------------------------------------------------------------
/timezone-expander.alfredworkflow:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devonzuegel/timezone-expander.alfredworkflow/2ca6fd45e56e4bdff25082baf78b977463084c1c/timezone-expander.alfredworkflow
--------------------------------------------------------------------------------
/timezone_names.js:
--------------------------------------------------------------------------------
1 | export const tzNames = [
2 | 'Europe/Andorra',
3 | 'Asia/Dubai',
4 | 'Asia/Kabul',
5 | 'Europe/Tirane',
6 | 'Asia/Yerevan',
7 | 'Antarctica/Casey',
8 | 'Antarctica/Davis',
9 | 'Antarctica/DumontDUrville', // https://bugs.chromium.org/p/chromium/issues/detail?id=928068
10 | 'Antarctica/Mawson',
11 | 'Antarctica/Palmer',
12 | 'Antarctica/Rothera',
13 | 'Antarctica/Syowa',
14 | 'Antarctica/Troll',
15 | 'Antarctica/Vostok',
16 | 'America/Argentina/Buenos_Aires',
17 | 'America/Argentina/Cordoba',
18 | 'America/Argentina/Salta',
19 | 'America/Argentina/Jujuy',
20 | 'America/Argentina/Tucuman',
21 | 'America/Argentina/Catamarca',
22 | 'America/Argentina/La_Rioja',
23 | 'America/Argentina/San_Juan',
24 | 'America/Argentina/Mendoza',
25 | 'America/Argentina/San_Luis',
26 | 'America/Argentina/Rio_Gallegos',
27 | 'America/Argentina/Ushuaia',
28 | 'Pacific/Pago_Pago',
29 | 'Europe/Vienna',
30 | 'Australia/Lord_Howe',
31 | 'Antarctica/Macquarie',
32 | 'Australia/Hobart',
33 | 'Australia/Currie',
34 | 'Australia/Melbourne',
35 | 'Australia/Sydney',
36 | 'Australia/Broken_Hill',
37 | 'Australia/Brisbane',
38 | 'Australia/Lindeman',
39 | 'Australia/Adelaide',
40 | 'Australia/Darwin',
41 | 'Australia/Perth',
42 | 'Australia/Eucla',
43 | 'Asia/Baku',
44 | 'America/Barbados',
45 | 'Asia/Dhaka',
46 | 'Europe/Brussels',
47 | 'Europe/Sofia',
48 | 'Atlantic/Bermuda',
49 | 'Asia/Brunei',
50 | 'America/La_Paz',
51 | 'America/Noronha',
52 | 'America/Belem',
53 | 'America/Fortaleza',
54 | 'America/Recife',
55 | 'America/Araguaina',
56 | 'America/Maceio',
57 | 'America/Bahia',
58 | 'America/Sao_Paulo',
59 | 'America/Campo_Grande',
60 | 'America/Cuiaba',
61 | 'America/Santarem',
62 | 'America/Porto_Velho',
63 | 'America/Boa_Vista',
64 | 'America/Manaus',
65 | 'America/Eirunepe',
66 | 'America/Rio_Branco',
67 | 'America/Nassau',
68 | 'Asia/Thimphu',
69 | 'Europe/Minsk',
70 | 'America/Belize',
71 | 'America/St_Johns',
72 | 'America/Halifax',
73 | 'America/Glace_Bay',
74 | 'America/Moncton',
75 | 'America/Goose_Bay',
76 | 'America/Blanc-Sablon',
77 | 'America/Toronto',
78 | 'America/Nipigon',
79 | 'America/Thunder_Bay',
80 | 'America/Iqaluit',
81 | 'America/Pangnirtung',
82 | 'America/Atikokan',
83 | 'America/Winnipeg',
84 | 'America/Rainy_River',
85 | 'America/Resolute',
86 | 'America/Rankin_Inlet',
87 | 'America/Regina',
88 | 'America/Swift_Current',
89 | 'America/Edmonton',
90 | 'America/Cambridge_Bay',
91 | 'America/Yellowknife',
92 | 'America/Inuvik',
93 | 'America/Creston',
94 | 'America/Dawson_Creek',
95 | 'America/Fort_Nelson',
96 | 'America/Vancouver',
97 | 'America/Whitehorse',
98 | 'America/Dawson',
99 | 'Indian/Cocos',
100 | 'Europe/Zurich',
101 | 'Africa/Abidjan',
102 | 'Pacific/Rarotonga',
103 | 'America/Santiago',
104 | 'America/Punta_Arenas',
105 | 'Pacific/Easter',
106 | 'Asia/Shanghai',
107 | 'Asia/Urumqi',
108 | 'America/Bogota',
109 | 'America/Costa_Rica',
110 | 'America/Havana',
111 | 'Atlantic/Cape_Verde',
112 | 'America/Curacao',
113 | 'Indian/Christmas',
114 | 'Asia/Nicosia',
115 | 'Asia/Famagusta',
116 | 'Europe/Prague',
117 | 'Europe/Berlin',
118 | 'Europe/Copenhagen',
119 | 'America/Santo_Domingo',
120 | 'Africa/Algiers',
121 | 'America/Guayaquil',
122 | 'Pacific/Galapagos',
123 | 'Europe/Tallinn',
124 | 'Africa/Cairo',
125 | 'Africa/El_Aaiun',
126 | 'Europe/Madrid',
127 | 'Africa/Ceuta',
128 | 'Atlantic/Canary',
129 | 'Europe/Helsinki',
130 | 'Pacific/Fiji',
131 | 'Atlantic/Stanley',
132 | 'Pacific/Chuuk',
133 | 'Pacific/Pohnpei',
134 | 'Pacific/Kosrae',
135 | 'Atlantic/Faroe',
136 | 'Europe/Paris',
137 | 'Europe/London',
138 | 'Asia/Tbilisi',
139 | 'America/Cayenne',
140 | 'Africa/Accra',
141 | 'Europe/Gibraltar',
142 | 'America/Godthab',
143 | 'America/Danmarkshavn',
144 | 'America/Scoresbysund',
145 | 'America/Thule',
146 | 'Europe/Athens',
147 | 'Atlantic/South_Georgia',
148 | 'America/Guatemala',
149 | 'Pacific/Guam',
150 | 'Africa/Bissau',
151 | 'America/Guyana',
152 | 'Asia/Hong_Kong',
153 | 'America/Tegucigalpa',
154 | 'America/Port-au-Prince',
155 | 'Europe/Budapest',
156 | 'Asia/Jakarta',
157 | 'Asia/Pontianak',
158 | 'Asia/Makassar',
159 | 'Asia/Jayapura',
160 | 'Europe/Dublin',
161 | 'Asia/Jerusalem',
162 | 'Asia/Kolkata',
163 | 'Indian/Chagos',
164 | 'Asia/Baghdad',
165 | 'Asia/Tehran',
166 | 'Atlantic/Reykjavik',
167 | 'Europe/Rome',
168 | 'America/Jamaica',
169 | 'Asia/Amman',
170 | 'Asia/Tokyo',
171 | 'Africa/Nairobi',
172 | 'Asia/Bishkek',
173 | 'Pacific/Tarawa',
174 | 'Pacific/Enderbury',
175 | 'Pacific/Kiritimati',
176 | 'Asia/Pyongyang',
177 | 'Asia/Seoul',
178 | 'Asia/Almaty',
179 | 'Asia/Qyzylorda',
180 | 'Asia/Qostanay', // https://bugs.chromium.org/p/chromium/issues/detail?id=928068
181 | 'Asia/Aqtobe',
182 | 'Asia/Aqtau',
183 | 'Asia/Atyrau',
184 | 'Asia/Oral',
185 | 'Asia/Beirut',
186 | 'Asia/Colombo',
187 | 'Africa/Monrovia',
188 | 'Europe/Vilnius',
189 | 'Europe/Luxembourg',
190 | 'Europe/Riga',
191 | 'Africa/Tripoli',
192 | 'Africa/Casablanca',
193 | 'Europe/Monaco',
194 | 'Europe/Chisinau',
195 | 'Pacific/Majuro',
196 | 'Pacific/Kwajalein',
197 | 'Asia/Yangon',
198 | 'Asia/Ulaanbaatar',
199 | 'Asia/Hovd',
200 | 'Asia/Choibalsan',
201 | 'Asia/Macau',
202 | 'America/Martinique',
203 | 'Europe/Malta',
204 | 'Indian/Mauritius',
205 | 'Indian/Maldives',
206 | 'America/Mexico_City',
207 | 'America/Cancun',
208 | 'America/Merida',
209 | 'America/Monterrey',
210 | 'America/Matamoros',
211 | 'America/Mazatlan',
212 | 'America/Chihuahua',
213 | 'America/Ojinaga',
214 | 'America/Hermosillo',
215 | 'America/Tijuana',
216 | 'America/Bahia_Banderas',
217 | 'Asia/Kuala_Lumpur',
218 | 'Asia/Kuching',
219 | 'Africa/Maputo',
220 | 'Africa/Windhoek',
221 | 'Pacific/Noumea',
222 | 'Pacific/Norfolk',
223 | 'Africa/Lagos',
224 | 'America/Managua',
225 | 'Europe/Amsterdam',
226 | 'Europe/Oslo',
227 | 'Asia/Kathmandu',
228 | 'Pacific/Nauru',
229 | 'Pacific/Niue',
230 | 'Pacific/Auckland',
231 | 'Pacific/Chatham',
232 | 'America/Panama',
233 | 'America/Lima',
234 | 'Pacific/Tahiti',
235 | 'Pacific/Marquesas',
236 | 'Pacific/Gambier',
237 | 'Pacific/Port_Moresby',
238 | 'Pacific/Bougainville',
239 | 'Asia/Manila',
240 | 'Asia/Karachi',
241 | 'Europe/Warsaw',
242 | 'America/Miquelon',
243 | 'Pacific/Pitcairn',
244 | 'America/Puerto_Rico',
245 | 'Asia/Gaza',
246 | 'Asia/Hebron',
247 | 'Europe/Lisbon',
248 | 'Atlantic/Madeira',
249 | 'Atlantic/Azores',
250 | 'Pacific/Palau',
251 | 'America/Asuncion',
252 | 'Asia/Qatar',
253 | 'Indian/Reunion',
254 | 'Europe/Bucharest',
255 | 'Europe/Belgrade',
256 | 'Europe/Kaliningrad',
257 | 'Europe/Moscow',
258 | 'Europe/Simferopol',
259 | 'Europe/Kirov',
260 | 'Europe/Astrakhan',
261 | 'Europe/Volgograd',
262 | 'Europe/Saratov',
263 | 'Europe/Ulyanovsk',
264 | 'Europe/Samara',
265 | 'Asia/Yekaterinburg',
266 | 'Asia/Omsk',
267 | 'Asia/Novosibirsk',
268 | 'Asia/Barnaul',
269 | 'Asia/Tomsk',
270 | 'Asia/Novokuznetsk',
271 | 'Asia/Krasnoyarsk',
272 | 'Asia/Irkutsk',
273 | 'Asia/Chita',
274 | 'Asia/Yakutsk',
275 | 'Asia/Khandyga',
276 | 'Asia/Vladivostok',
277 | 'Asia/Ust-Nera',
278 | 'Asia/Magadan',
279 | 'Asia/Sakhalin',
280 | 'Asia/Srednekolymsk',
281 | 'Asia/Kamchatka',
282 | 'Asia/Anadyr',
283 | 'Asia/Riyadh',
284 | 'Pacific/Guadalcanal',
285 | 'Indian/Mahe',
286 | 'Africa/Khartoum',
287 | 'Europe/Stockholm',
288 | 'Asia/Singapore',
289 | 'America/Paramaribo',
290 | 'Africa/Juba',
291 | 'Africa/Sao_Tome',
292 | 'America/El_Salvador',
293 | 'Asia/Damascus',
294 | 'America/Grand_Turk',
295 | 'Africa/Ndjamena',
296 | 'Indian/Kerguelen',
297 | 'Asia/Bangkok',
298 | 'Asia/Dushanbe',
299 | 'Pacific/Fakaofo',
300 | 'Asia/Dili',
301 | 'Asia/Ashgabat',
302 | 'Africa/Tunis',
303 | 'Pacific/Tongatapu',
304 | 'Europe/Istanbul',
305 | 'America/Port_of_Spain',
306 | 'Pacific/Funafuti',
307 | 'Asia/Taipei',
308 | 'Europe/Kiev',
309 | 'Europe/Uzhgorod',
310 | 'Europe/Zaporozhye',
311 | 'Pacific/Wake',
312 | 'America/New_York',
313 | 'America/Detroit',
314 | 'America/Kentucky/Louisville',
315 | 'America/Kentucky/Monticello',
316 | 'America/Indiana/Indianapolis',
317 | 'America/Indiana/Vincennes',
318 | 'America/Indiana/Winamac',
319 | 'America/Indiana/Marengo',
320 | 'America/Indiana/Petersburg',
321 | 'America/Indiana/Vevay',
322 | 'America/Chicago',
323 | 'America/Indiana/Tell_City',
324 | 'America/Indiana/Knox',
325 | 'America/Menominee',
326 | 'America/North_Dakota/Center',
327 | 'America/North_Dakota/New_Salem',
328 | 'America/North_Dakota/Beulah',
329 | 'America/Denver',
330 | 'America/Boise',
331 | 'America/Phoenix',
332 | 'America/Los_Angeles',
333 | 'America/Anchorage',
334 | 'America/Juneau',
335 | 'America/Sitka',
336 | 'America/Metlakatla',
337 | 'America/Yakutat',
338 | 'America/Nome',
339 | 'America/Adak',
340 | 'Pacific/Honolulu',
341 | 'America/Montevideo',
342 | 'Asia/Samarkand',
343 | 'Asia/Tashkent',
344 | 'America/Caracas',
345 | 'Asia/Ho_Chi_Minh',
346 | 'Pacific/Efate',
347 | 'Pacific/Wallis',
348 | 'Pacific/Apia',
349 | 'Africa/Johannesburg',
350 | ]
351 |
--------------------------------------------------------------------------------
/tz_expander.js:
--------------------------------------------------------------------------------
1 | import strSimilarity from 'string-similarity'
2 | import date_fns from 'date-fns'
3 | import timezone_fns from 'date-fns-tz'
4 | import {tzNames} from './timezone_names.js'
5 |
6 | const city_to_tz = (city) => {
7 | switch (city.toLowerCase()) {
8 | case 'sf':
9 | case 'san francisco':
10 | case 'la':
11 | case 'pt':
12 | case 'pst':
13 | case 'pdt':
14 | case 'los angeles':
15 | return 'America/Los_Angeles'
16 |
17 | case 'uk':
18 | case 'london':
19 | case 'manchester':
20 | return 'Europe/London'
21 |
22 | case 'nyc':
23 | case 'boston':
24 | case 'miami':
25 | case 'est':
26 | case 'edt':
27 | case 'et':
28 | return 'America/New_York'
29 |
30 | case 'sofia':
31 | return 'Europe/Sofia'
32 |
33 | case 'bsas':
34 | return 'America/Argentina/Buenos_Aires'
35 |
36 | case 'philippines':
37 | 'Asia/Manila'
38 |
39 | case 'berlin':
40 | return 'Europe/Berlin'
41 |
42 | case 'new zealand':
43 | case 'nz':
44 | return 'Pacific/Auckland'
45 |
46 | default:
47 | return strSimilarity.findBestMatch(city, tzNames).bestMatch.target
48 | }
49 | }
50 |
51 | const pprint = (obj) => {
52 | console.log(JSON.stringify(obj, null, 2))
53 | }
54 |
55 | /**
56 | * local_time: fuzzy date with case-insensitive am/pm ("10am", "11:30pm", "7:43AM")
57 | * returns: a parsed date that can be manipulated by the datefn libraries
58 | */
59 | const get_local_time = (local_time) => {
60 | if (local_time.includes(':')) {
61 | return date_fns.parse(local_time, 'hh:mmaaa', new Date())
62 | } else {
63 | return date_fns.parse(local_time, 'hhaaa', new Date())
64 | }
65 | }
66 |
67 | /**
68 | * date: a date that can be manipulateby the datefn libraries
69 | * returns: a fmt time with minutes when they are non-zero
70 | */
71 | const format_time = (date) => {
72 | const format = date_fns.format
73 | if (format(date, 'mm') == '00') {
74 | return format(date, 'haaa')
75 | } else {
76 | return format(date, 'h:mmaaa')
77 | }
78 | }
79 |
80 | const toSentenceCase = (str) => {
81 | return str
82 | .split(' ')
83 | .map((w) => w[0].toUpperCase() + w.substr(1).toLowerCase())
84 | .join(' ')
85 | }
86 |
87 | export const tz_expander = (input, test) => {
88 | const [local_time, ...cities] = input
89 | .trim()
90 | .split(input.includes('/') ? '/' : ' ')
91 | .map((s) => s.trim())
92 |
93 | const times = cities.map((city) => {
94 | const tz = city_to_tz(city)
95 | const parsed_local_time = get_local_time(local_time)
96 | const zoned_date = timezone_fns.utcToZonedTime(parsed_local_time, tz)
97 |
98 | const fmt_date = format_time(zoned_date)
99 | const fmt_city = city.length == 2 ? city.toUpperCase() : toSentenceCase(city)
100 | return fmt_date + ' ' + fmt_city
101 | })
102 |
103 | return times.join(' / ')
104 | }
105 |
--------------------------------------------------------------------------------