├── .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 | --------------------------------------------------------------------------------