1/** 2 * ++++++++++++++++++++++++++++++++++++++ 3 * Add to Calendar TimeZones iCal Library 4 * ++++++++++++++++++++++++++++++++++++++ 5 */ 6const tzlibVersion = '1.3.2'; 7/* Creator: Jens Kuerschner (https://jenskuerschner.de) 8 * Project: https://github.com/add2cal/timezones-ical-library 9 * License: Apache-2.0 10 * 11 */ 12 13// DEFINING THE DB DATA - WILL GET RE-WRITTEN WITH THE ACTUAL DATA ON BUILD 14const tzlibZonesDB = { 15 Africa: { 16 Abidjan: ['', 0], 17 Accra: ['Africa/Abidjan', 0], 18 Addis_Ababa: ['Africa/Nairobi', 1], 19 Algiers: ['', 2], 20 Asmara: ['Africa/Nairobi', 1], 21 Asmera: ['Africa/Nairobi', 1], 22 Bamako: ['Africa/Abidjan', 0], 23 Bangui: ['Africa/Lagos', 3], 24 Banjul: ['Africa/Abidjan', 0], 25 Bissau: ['', 0], 26 Blantyre: ['Africa/Maputo', 4], 27 Brazzaville: ['Africa/Lagos', 3], 28 Bujumbura: ['Africa/Maputo', 4], 29 Cairo: ['', 5], 30 Casablanca: ['', 6], 31 Ceuta: ['', 7], 32 Conakry: ['Africa/Abidjan', 0], 33 Dakar: ['Africa/Abidjan', 0], 34 Dar_es_Salaam: ['Africa/Nairobi', 1], 35 Djibouti: ['Africa/Nairobi', 1], 36 Douala: ['Africa/Lagos', 3], 37 El_Aaiun: ['', 6], 38 Freetown: ['Africa/Abidjan', 0], 39 Gaborone: ['Africa/Maputo', 4], 40 Harare: ['Africa/Maputo', 4], 41 Johannesburg: ['', 8], 42 Juba: ['', 4], 43 Kampala: ['Africa/Nairobi', 1], 44 Khartoum: ['', 4], 45 Kigali: ['Africa/Maputo', 4], 46 Kinshasa: ['Africa/Lagos', 3], 47 Lagos: ['', 3], 48 Libreville: ['Africa/Lagos', 3], 49 Lome: ['Africa/Abidjan', 0], 50 Luanda: ['Africa/Lagos', 3], 51 Lubumbashi: ['Africa/Maputo', 4], 52 Lusaka: ['Africa/Maputo', 4], 53 Malabo: ['Africa/Lagos', 3], 54 Maputo: ['', 4], 55 Maseru: ['Africa/Johannesburg', 8], 56 Mbabane: ['Africa/Johannesburg', 8], 57 Mogadishu: ['Africa/Nairobi', 1], 58 Monrovia: ['', 0], 59 Nairobi: ['', 1], 60 Ndjamena: ['', 3], 61 Niamey: ['Africa/Lagos', 3], 62 Nouakchott: ['Africa/Abidjan', 0], 63 Ouagadougou: ['Africa/Abidjan', 0], 64 'Porto-Novo': ['Africa/Lagos', 3], 65 Sao_Tome: ['', 0], 66 Timbuktu: ['Africa/Abidjan', 0], 67 Tripoli: ['', 5], 68 Tunis: ['', 2], 69 Windhoek: ['', 4], 70 }, 71 America: { 72 Adak: ['', 9], 73 Anchorage: ['', 10], 74 Anguilla: ['America/Puerto_Rico', 11], 75 Antigua: ['America/Puerto_Rico', 11], 76 Araguaina: ['', 12], 77 Argentina: { 78 Buenos_Aires: ['', 12], 79 Catamarca: ['', 12], 80 ComodRivadavia: ['America/Argentina/Catamarca', 12], 81 Cordoba: ['', 12], 82 Jujuy: ['', 12], 83 La_Rioja: ['', 12], 84 Mendoza: ['', 12], 85 Rio_Gallegos: ['', 12], 86 Salta: ['', 12], 87 San_Juan: ['', 12], 88 San_Luis: ['', 12], 89 Tucuman: ['', 12], 90 Ushuaia: ['', 12], 91 }, 92 Aruba: ['America/Puerto_Rico', 11], 93 Asuncion: ['', 13], 94 Atikokan: ['America/Panama', 14], 95 Atka: ['America/Adak', 9], 96 Bahia_Banderas: ['', 15], 97 Bahia: ['', 12], 98 Barbados: ['', 11], 99 Belem: ['', 12], 100 Belize: ['', 16], 101 'Blanc-Sablon': ['America/Puerto_Rico', 11], 102 Boa_Vista: ['', 17], 103 Bogota: ['', 18], 104 Boise: ['', 19], 105 Buenos_Aires: ['America/Argentina/Buenos_Aires', 12], 106 Cambridge_Bay: ['', 19], 107 Campo_Grande: ['', 17], 108 Cancun: ['', 14], 109 Caracas: ['', 17], 110 Catamarca: ['America/Argentina/Catamarca', 12], 111 Cayenne: ['', 12], 112 Cayman: ['America/Panama', 14], 113 Chicago: ['', 20], 114 Chihuahua: ['', 21], 115 Coral_Harbour: ['America/Panama', 14], 116 Cordoba: ['America/Argentina/Cordoba', 12], 117 Costa_Rica: ['', 16], 118 Creston: ['America/Phoenix', 22], 119 Cuiaba: ['', 17], 120 Curacao: ['America/Puerto_Rico', 11], 121 Danmarkshavn: ['', 0], 122 Dawson_Creek: ['', 22], 123 Dawson: ['', 22], 124 Denver: ['', 19], 125 Detroit: ['', 23], 126 Dominica: ['America/Puerto_Rico', 11], 127 Edmonton: ['', 19], 128 Eirunepe: ['', 18], 129 El_Salvador: ['', 16], 130 Ensenada: ['America/Tijuana', 24], 131 Fort_Nelson: ['', 22], 132 Fort_Wayne: ['America/Indiana/Indianapolis', 23], 133 Fortaleza: ['', 12], 134 Glace_Bay: ['', 25], 135 Godthab: ['America/Nuuk', 26], 136 Goose_Bay: ['', 27], 137 Grand_Turk: ['', 28], 138 Grenada: ['America/Puerto_Rico', 11], 139 Guadeloupe: ['America/Puerto_Rico', 11], 140 Guatemala: ['', 16], 141 Guayaquil: ['', 18], 142 Guyana: ['', 17], 143 Halifax: ['', 25], 144 Havana: ['', 29], 145 Hermosillo: ['', 22], 146 Indiana: { 147 Indianapolis: ['', 23], 148 Knox: ['', 20], 149 Marengo: ['', 23], 150 Petersburg: ['', 23], 151 Tell_City: ['', 20], 152 Vevay: ['', 23], 153 Vincennes: ['', 23], 154 Winamac: ['', 28], 155 }, 156 Indianapolis: ['America/Indiana/Indianapolis', 23], 157 Inuvik: ['', 19], 158 Iqaluit: ['', 23], 159 Jamaica: ['', 14], 160 Jujuy: ['America/Argentina/Jujuy', 12], 161 Juneau: ['', 10], 162 Kentucky: { Louisville: ['', 23], Monticello: ['', 23] }, 163 Knox_IN: ['America/Indiana/Knox', 20], 164 Kralendijk: ['America/Puerto_Rico', 11], 165 La_Paz: ['', 17], 166 Lima: ['', 18], 167 Los_Angeles: ['', 24], 168 Louisville: ['America/Kentucky/Louisville', 23], 169 Lower_Princes: ['America/Puerto_Rico', 11], 170 Maceio: ['', 12], 171 Managua: ['', 16], 172 Manaus: ['', 17], 173 Marigot: ['America/Puerto_Rico', 11], 174 Martinique: ['', 11], 175 Matamoros: ['', 20], 176 Mazatlan: ['', 21], 177 Mendoza: ['America/Argentina/Mendoza', 12], 178 Menominee: ['', 20], 179 Merida: ['', 30], 180 Metlakatla: ['', 10], 181 Mexico_City: ['', 30], 182 Miquelon: ['', 31], 183 Moncton: ['', 25], 184 Monterrey: ['', 30], 185 Montevideo: ['', 12], 186 Montreal: ['America/Toronto', 23], 187 Montserrat: ['America/Puerto_Rico', 11], 188 Nassau: ['America/Toronto', 23], 189 New_York: ['', 23], 190 Nipigon: ['', 23], 191 Nome: ['', 10], 192 Noronha: ['', 32], 193 North_Dakota: { Beulah: ['', 20], Center: ['', 20], New_Salem: ['', 20] }, 194 Nuuk: ['', 26], 195 Ojinaga: ['', 19], 196 Panama: ['', 14], 197 Pangnirtung: ['', 23], 198 Paramaribo: ['', 12], 199 Phoenix: ['', 22], 200 Port_of_Spain: ['America/Puerto_Rico', 11], 201 'Port-au-Prince': ['', 23], 202 Porto_Acre: ['America/Rio_Branco', 18], 203 Porto_Velho: ['', 17], 204 Puerto_Rico: ['', 11], 205 Punta_Arenas: ['', 12], 206 Rainy_River: ['', 20], 207 Rankin_Inlet: ['', 20], 208 Recife: ['', 12], 209 Regina: ['', 16], 210 Resolute: ['', 33], 211 Rio_Branco: ['', 18], 212 Rosario: ['America/Argentina/Cordoba', 12], 213 Santa_Isabel: ['America/Tijuana', 24],
214 Santarem: ['', 12], 215 Santiago: ['', 34], 216 Santo_Domingo: ['', 11], 217 Sao_Paulo: ['', 12], 218 Scoresbysund: ['', 35], 219 Shiprock: ['America/Denver', 19], 220 Sitka: ['', 10], 221 St_Barthelemy: ['America/Puerto_Rico', 11], 222 St_Johns: ['', 36], 223 St_Kitts: ['America/Puerto_Rico', 11], 224 St_Lucia: ['America/Puerto_Rico', 11], 225 St_Thomas: ['America/Puerto_Rico', 11], 226 St_Vincent: ['America/Puerto_Rico', 11], 227 Swift_Current: ['', 16], 228 Tegucigalpa: ['', 16], 229 Thule: ['', 25], 230 Thunder_Bay: ['', 23], 231 Tijuana: ['', 24], 232 Toronto: ['', 23], 233 Tortola: ['America/Puerto_Rico', 11], 234 Vancouver: ['', 24], 235 Virgin: ['America/Puerto_Rico', 11], 236 Whitehorse: ['', 22], 237 Winnipeg: ['', 20], 238 Yakutat: ['', 10], 239 Yellowknife: ['', 19], 240 }, 241 Antarctica: { 242 Casey: ['', 37], 243 Davis: ['', 38], 244 DumontDUrville: ['Pacific/Port_Moresby', 39], 245 Macquarie: ['', 40], 246 Mawson: ['', 41], 247 McMurdo: ['Pacific/Auckland', 42], 248 Palmer: ['', 12], 249 Rothera: ['', 12], 250 South_Pole: ['Pacific/Auckland', 42], 251 Syowa: ['Asia/Riyadh', 43], 252 Troll: ['', 44], 253 Vostok: ['Asia/Urumqi', 45], 254 }, 255 Arctic: { Longyearbyen: ['Europe/Berlin', 7] }, 256 Asia: { 257 Aden: ['Asia/Riyadh', 43], 258 Almaty: ['', 45], 259 Amman: ['', 46], 260 Anadyr: ['', 47], 261 Aqtau: ['', 41], 262 Aqtobe: ['', 41], 263 Ashgabat: ['', 41], 264 Ashkhabad: ['Asia/Ashgabat', 41], 265 Atyrau: ['', 41], 266 Baghdad: ['', 43], 267 Bahrain: ['Asia/Qatar', 43], 268 Baku: ['', 48], 269 Bangkok: ['', 38], 270 Barnaul: ['', 38], 271 Beirut: ['', 49], 272 Bishkek: ['', 45], 273 Brunei: ['Asia/Kuching', 50], 274 Calcutta: ['Asia/Kolkata', 51], 275 Chita: ['', 52], 276 Choibalsan: ['', 50], 277 Chongqing: ['Asia/Shanghai', 53], 278 Chungking: ['Asia/Shanghai', 53], 279 Colombo: ['', 54], 280 Dacca: ['Asia/Dhaka', 45], 281 Damascus: ['', 55], 282 Dhaka: ['', 45], 283 Dili: ['', 52], 284 Dubai: ['', 48], 285 Dushanbe: ['', 41], 286 Famagusta: ['', 56], 287 Gaza: ['', 57], 288 Harbin: ['Asia/Shanghai', 53], 289 Hebron: ['', 57], 290 Ho_Chi_Minh: ['', 38], 291 Hong_Kong: ['', 58], 292 Hovd: ['', 38], 293 Irkutsk: ['', 50], 294 Istanbul: ['Europe/Istanbul', 43], 295 Jakarta: ['', 59], 296 Jayapura: ['', 60], 297 Jerusalem: ['', 61], 298 Kabul: ['', 62], 299 Kamchatka: ['', 47], 300 Karachi: ['', 63], 301 Kashgar: ['Asia/Urumqi', 45], 302 Kathmandu: ['', 64], 303 Katmandu: ['Asia/Kathmandu', 64], 304 Khandyga: ['', 52], 305 Kolkata: ['', 51], 306 Krasnoyarsk: ['', 38], 307 Kuala_Lumpur: ['Asia/Singapore', 50], 308 Kuching: ['', 50], 309 Kuwait: ['Asia/Riyadh', 43], 310 Macao: ['Asia/Macau', 53], 311 Macau: ['', 53], 312 Magadan: ['', 37], 313 Makassar: ['', 65], 314 Manila: ['', 66], 315 Muscat: ['Asia/Dubai', 48], 316 Nicosia: ['', 67], 317 Novokuznetsk: ['', 38], 318 Novosibirsk: ['', 38], 319 Omsk: ['', 45], 320 Oral: ['', 41], 321 Phnom_Penh: ['Asia/Bangkok', 38], 322 Pontianak: ['', 59], 323 Pyongyang: ['', 68], 324 Qatar: ['', 43], 325 Qostanay: ['', 45], 326 Qyzylorda: ['', 41], 327 Rangoon: ['Asia/Yangon', 69], 328 Riyadh: ['', 43], 329 Saigon: ['Asia/Ho_Chi_Minh', 38], 330 Sakhalin: ['', 37], 331 Samarkand: ['', 41], 332 Seoul: ['', 68], 333 Shanghai: ['', 53], 334 Singapore: ['', 50], 335 Srednekolymsk: ['', 37], 336 Taipei: ['', 53], 337 Tashkent: ['', 41], 338 Tbilisi: ['', 48], 339 Tehran: ['', 70], 340 Tel_Aviv: ['Asia/Jerusalem', 61], 341 Thimbu: ['Asia/Thimphu', 45], 342 Thimphu: ['', 45], 343 Tokyo: ['', 71], 344 Tomsk: ['', 38], 345 Ujung_Pandang: ['Asia/Makassar', 65], 346 Ulaanbaatar: ['', 50], 347 Ulan_Bator: ['Asia/Ulaanbaatar', 50], 348 Urumqi: ['', 45], 349 'Ust-Nera': ['', 39], 350 Vientiane: ['Asia/Bangkok', 38], 351 Vladivostok: ['', 39], 352 Yakutsk: ['', 52], 353 Yangon: ['', 69], 354 Yekaterinburg: ['', 41], 355 Yerevan: ['', 48], 356 }, 357 Atlantic: { 358 Azores: ['', 35], 359 Bermuda: ['', 25], 360 Canary: ['', 72], 361 Cape_Verde: ['', 73], 362 Faeroe: ['Atlantic/Faroe', 72], 363 Faroe: ['', 72], 364 Jan_Mayen: ['Europe/Berlin', 7], 365 Madeira: ['', 72], 366 Reykjavik: ['Africa/Abidjan', 0], 367 South_Georgia: ['', 32], 368 St_Helena: ['Africa/Abidjan', 0], 369 Stanley: ['', 12], 370 }, 371 Australia: { 372 ACT: ['Australia/Sydney', 40], 373 Adelaide: ['', 74], 374 Brisbane: ['', 75], 375 Broken_Hill: ['', 74], 376 Canberra: ['Australia/Sydney', 40], 377 Currie: ['Australia/Hobart', 76], 378 Darwin: ['', 77], 379 Eucla: ['', 78], 380 Hobart: ['', 76], 381 LHI: ['Australia/Lord_Howe', 79], 382 Lindeman: ['', 75], 383 Lord_Howe: ['', 79], 384 Melbourne: ['', 40], 385 North: ['Australia/Darwin', 77], 386 NSW: ['Australia/Sydney', 40], 387 Perth: ['', 80], 388 Queensland: ['Australia/Brisbane', 75], 389 South: ['Australia/Adelaide', 74], 390 Sydney: ['', 40], 391 Tasmania: ['Australia/Hobart', 76], 392 Victoria: ['Australia/Melbourne', 40], 393 West: ['Australia/Perth', 80], 394 Yancowinna: ['Australia/Broken_Hill', 74], 395 }, 396 Brazil: { 397 Acre: ['America/Rio_Branco', 18], 398 DeNoronha: ['America/Noronha', 32],
399 East: ['America/Sao_Paulo', 12], 400 West: ['America/Manaus', 17], 401 }, 402 Canada: { 403 Atlantic: ['America/Halifax', 25], 404 Central: ['America/Winnipeg', 20], 405 Eastern: ['America/Toronto', 23], 406 Mountain: ['America/Edmonton', 19], 407 Newfoundland: ['America/St_Johns', 36], 408 Pacific: ['America/Vancouver', 24], 409 Saskatchewan: ['America/Regina', 16], 410 Yukon: ['America/Whitehorse', 22], 411 }, 412 CET: ['', 7], 413 Chile: { Continental: ['America/Santiago', 34], EasterIsland: ['Pacific/Easter', 81] }, 414 CST6CDT: ['', 20], 415 Cuba: ['America/Havana', 29], 416 EET: ['', 56], 417 Egypt: ['Africa/Cairo', 5], 418 Eire: ['Europe/Dublin', 82], 419 EST: ['', 14], 420 EST5EDT: ['', 23], 421 Etc: { 422 'GMT-0': ['Etc/GMT', 0], 423 'GMT-1': ['', 6], 424 'GMT-10': ['', 39], 425 'GMT-11': ['', 37], 426 'GMT-12': ['', 47], 427 'GMT-13': ['', 83], 428 'GMT-14': ['', 84], 429 'GMT-2': ['', 85], 430 'GMT-3': ['', 43], 431 'GMT-4': ['', 48], 432 'GMT-5': ['', 41], 433 'GMT-6': ['', 45], 434 'GMT-7': ['', 38], 435 'GMT-8': ['', 50], 436 'GMT-9': ['', 52], 437 GMT: ['', 0], 438 'GMT+0': ['Etc/GMT', 0], 439 'GMT+1': ['', 73], 440 'GMT+10': ['', 86], 441 'GMT+11': ['', 87], 442 'GMT+12': ['', 88], 443 'GMT+2': ['', 32], 444 'GMT+3': ['', 12], 445 'GMT+4': ['', 17], 446 'GMT+5': ['', 18], 447 'GMT+6': ['', 89], 448 'GMT+7': ['', 90], 449 'GMT+8': ['', 91], 450 'GMT+9': ['', 92], 451 GMT0: ['Etc/GMT', 0], 452 Greenwich: ['Etc/GMT', 0], 453 UCT: ['Etc/UTC', 93], 454 Universal: ['Etc/UTC', 93], 455 UTC: ['', 93], 456 Zulu: ['Etc/UTC', 93], 457 }, 458 Europe: { 459 Amsterdam: ['Europe/Brussels', 7], 460 Andorra: ['', 7], 461 Astrakhan: ['', 48], 462 Athens: ['', 56], 463 Belfast: ['Europe/London', 94], 464 Belgrade: ['', 7], 465 Berlin: ['', 7], 466 Bratislava: ['Europe/Prague', 7], 467 Brussels: ['', 7], 468 Bucharest: ['', 56], 469 Budapest: ['', 7], 470 Busingen: ['Europe/Zurich', 7], 471 Chisinau: ['', 95], 472 Copenhagen: ['Europe/Berlin', 7], 473 Dublin: ['', 82], 474 Gibraltar: ['', 7], 475 Guernsey: ['Europe/London', 94], 476 Helsinki: ['', 56],
477 Isle_of_Man: ['Europe/London', 94], 478 Istanbul: ['', 43], 479 Jersey: ['Europe/London', 94], 480 Kaliningrad: ['', 5], 481 Kiev: ['Europe/Kyiv', 67], 482 Kirov: ['', 43], 483 Kyiv: ['', 67], 484 Lisbon: ['', 96], 485 Ljubljana: ['Europe/Belgrade', 7], 486 London: ['', 94], 487 Luxembourg: ['Europe/Brussels', 7], 488 Madrid: ['', 7], 489 Malta: ['', 7], 490 Mariehamn: ['Europe/Helsinki', 56], 491 Minsk: ['', 43], 492 Monaco: ['Europe/Paris', 7], 493 Moscow: ['', 97], 494 Nicosia: ['Asia/Nicosia', 67], 495 Oslo: ['Europe/Berlin', 7], 496 Paris: ['', 7], 497 Podgorica: ['Europe/Belgrade', 7], 498 Prague: ['', 7], 499 Riga: ['', 56], 500 Rome: ['', 7], 501 Samara: ['', 48], 502 San_Marino: ['Europe/Rome', 7], 503 Sarajevo: ['Europe/Belgrade', 7], 504 Saratov: ['', 48], 505 Simferopol: ['', 97], 506 Skopje: ['Europe/Belgrade', 7], 507 Sofia: ['', 56], 508 Stockholm: ['Europe/Berlin', 7], 509 Tallinn: ['', 56], 510 Tirane: ['', 7], 511 Tiraspol: ['Europe/Chisinau', 95], 512 Ulyanovsk: ['', 48], 513 Uzhgorod: ['Europe/Kyiv', 67], 514 Vaduz: ['Europe/Zurich', 7], 515 Vatican: ['Europe/Rome', 7], 516 Vienna: ['', 7], 517 Vilnius: ['', 56], 518 Volgograd: ['', 43], 519 Warsaw: ['', 7], 520 Zagreb: ['Europe/Belgrade', 7], 521 Zaporozhye: ['Europe/Kyiv', 67], 522 Zurich: ['', 7], 523 }, 524 'GB-Eire': ['Europe/London', 94], 525 GB: ['Europe/London', 94], 526 'GMT-0': ['Etc/GMT', 0], 527 GMT: ['Etc/GMT', 0], 528 'GMT+0': ['Etc/GMT', 0], 529 GMT0: ['Etc/GMT', 0], 530 Greenwich: ['Etc/GMT', 0], 531 Hongkong: ['Asia/Hong_Kong', 58], 532 HST: ['', 98], 533 Iceland: ['Africa/Abidjan', 0], 534 Indian: { 535 Antananarivo: ['Africa/Nairobi', 1], 536 Chagos: ['', 45], 537 Christmas: ['Asia/Bangkok', 38], 538 Cocos: ['Asia/Yangon', 69], 539 Comoro: ['Africa/Nairobi', 1], 540 Kerguelen: ['Indian/Maldives', 41], 541 Mahe: ['Asia/Dubai', 48], 542 Maldives: ['', 41], 543 Mauritius: ['', 48], 544 Mayotte: ['Africa/Nairobi', 1], 545 Reunion: ['Asia/Dubai', 48], 546 }, 547 Iran: ['Asia/Tehran', 70], 548 Israel: ['Asia/Jerusalem', 61], 549 Jamaica: ['America/Jamaica', 14], 550 Japan: ['Asia/Tokyo', 71], 551 Kwajalein: ['Pacific/Kwajalein', 47], 552 Libya: ['Africa/Tripoli', 5], 553 MET: ['', 99], 554 Mexico: { 555 BajaNorte: ['America/Tijuana', 24], 556 BajaSur: ['America/Mazatlan', 21], 557 General: ['America/Mexico_City', 30], 558 }, 559 MST: ['', 22], 560 MST7MDT: ['', 19], 561 Navajo: ['America/Denver', 19], 562 'NZ-CHAT': ['Pacific/Chatham', 100], 563 NZ: ['Pacific/Auckland', 42], 564 Pacific: { 565 Apia: ['', 83], 566 Auckland: ['', 42], 567 Bougainville: ['', 37], 568 Chatham: ['', 100], 569 Chuuk: ['Pacific/Port_Moresby', 39], 570 Easter: ['', 81], 571 Efate: ['', 37], 572 Enderbury: ['Pacific/Kanton', 83], 573 Fakaofo: ['', 83], 574 Fiji: ['', 101], 575 Funafuti: ['Pacific/Tarawa', 47], 576 Galapagos: ['', 89], 577 Gambier: ['', 92], 578 Guadalcanal: ['', 37], 579 Guam: ['', 102], 580 Honolulu: ['', 98], 581 Johnston: ['Pacific/Honolulu', 98], 582 Kanton: ['', 83], 583 Kiritimati: ['', 84], 584 Kosrae: ['', 37], 585 Kwajalein: ['', 47], 586 Majuro: ['Pacific/Tarawa', 47], 587 Marquesas: ['', 103], 588 Midway: ['Pacific/Pago_Pago', 104], 589 Nauru: ['', 47], 590 Niue: ['', 87], 591 Norfolk: ['', 105], 592 Noumea: ['', 37], 593 Pago_Pago: ['', 104], 594 Palau: ['', 52], 595 Pitcairn: ['', 91], 596 Pohnpei: ['Pacific/Guadalcanal', 37], 597 Ponape: ['Pacific/Guadalcanal', 37], 598 Port_Moresby: ['', 39], 599 Rarotonga: ['', 86], 600 Saipan: ['Pacific/Guam', 102], 601 Samoa: ['Pacific/Pago_Pago', 104], 602 Tahiti: ['', 86], 603 Tarawa: ['', 47], 604 Tongatapu: ['', 83], 605 Truk: ['Pacific/Port_Moresby', 39], 606 Wake: ['Pacific/Tarawa', 47], 607 Wallis: ['Pacific/Tarawa', 47], 608 Yap: ['Pacific/Port_Moresby', 39], 609 }, 610 Poland: ['Europe/Warsaw', 7], 611 Portugal: ['Europe/Lisbon', 96], 612 PRC: ['Asia/Shanghai', 53], 613 PST8PDT: ['', 24], 614 ROC: ['Asia/Taipei', 53], 615 ROK: ['Asia/Seoul', 68], 616 Singapore: ['Asia/Singapore', 50], 617 Turkey: ['Europe/Istanbul', 43], 618 UCT: ['Etc/UTC', 93], 619 Universal: ['Etc/UTC', 93], 620 US: { 621 Alaska: ['America/Anchorage', 10], 622 Aleutian: ['America/Adak', 9], 623 Arizona: ['America/Phoenix', 22], 624 Central: ['America/Chicago', 20], 625 'East-Indiana': ['America/Indiana/Indianapolis', 23],
626 Eastern: ['America/New_York', 23], 627 Hawaii: ['Pacific/Honolulu', 98], 628 'Indiana-Starke': ['America/Indiana/Knox', 20], 629 Michigan: ['America/Detroit', 23], 630 Mountain: ['America/Denver', 19], 631 Pacific: ['America/Los_Angeles', 24], 632 Samoa: ['Pacific/Pago_Pago', 104], 633 }, 634 UTC: ['Etc/UTC', 93], 635 'W-SU': ['Europe/Moscow', 97], 636 WET: ['', 72], 637 Zulu: ['Etc/UTC', 93], 638}; 639 640const tzlibZonesDetailsDB = [ 641 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:GMT<br>TZOFFSETFROM:+0000<br>TZOFFSETTO:+0000<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 642 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:EAT<br>TZOFFSETFROM:+0300<br>TZOFFSETTO:+0300<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 643 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:CET<br>TZOFFSETFROM:+0100<br>TZOFFSETTO:+0100<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 644 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:WAT<br>TZOFFSETFROM:+0100<br>TZOFFSETTO:+0100<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 645 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:CAT<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0200<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 646 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:EET<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0200<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 647 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+01<br>TZOFFSETFROM:+0100<br>TZOFFSETTO:+0100<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 648 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:CEST<br>TZOFFSETFROM:+0100<br>TZOFFSETTO:+0200<br>DTSTART:19700329T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:CET<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0100<br>DTSTART:19701025T030000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>', 649 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:SAST<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0200<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 650 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:HDT<br>TZOFFSETFROM:-1000<br>TZOFFSETTO:-0900<br>DTSTART:19700308T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:HST<br>TZOFFSETFROM:-0900<br>TZOFFSETTO:-1000<br>DTSTART:19701101T020000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU<br>END:STANDARD<br>', 651 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:AKDT<br>TZOFFSETFROM:-0900<br>TZOFFSETTO:-0800<br>DTSTART:19700308T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:AKST<br>TZOFFSETFROM:-0800<br>TZOFFSETTO:-0900<br>DTSTART:19701101T020000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU<br>END:STANDARD<br>', 652 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:AST<br>TZOFFSETFROM:-0400<br>TZOFFSETTO:-0400<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 653 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-03<br>TZOFFSETFROM:-0300<br>TZOFFSETTO:-0300<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 654 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:-03<br>TZOFFSETFROM:-0400<br>TZOFFSETTO:-0300<br>DTSTART:19701004T000000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:-04<br>TZOFFSETFROM:-0300<br>TZOFFSETTO:-0400<br>DTSTART:19700322T000000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=4SU<br>END:STANDARD<br>', 655 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:EST<br>TZOFFSETFROM:-0500<br>TZOFFSETTO:-0500<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 656 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:CST<br>TZOFFSETFROM:-0500<br>TZOFFSETTO:-0600<br>DTSTART:19701025T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:CDT<br>TZOFFSETFROM:-0600<br>TZOFFSETTO:-0500<br>DTSTART:19700405T020000<br>RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU<br>END:DAYLIGHT<br>', 657 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:CST<br>TZOFFSETFROM:-0600<br>TZOFFSETTO:-0600<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 658 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-04<br>TZOFFSETFROM:-0400<br>TZOFFSETTO:-0400<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 659 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-05<br>TZOFFSETFROM:-0500<br>TZOFFSETTO:-0500<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 660 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:MDT<br>TZOFFSETFROM:-0700<br>TZOFFSETTO:-0600<br>DTSTART:19700308T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:MST<br>TZOFFSETFROM:-0600<br>TZOFFSETTO:-0700<br>DTSTART:19701101T020000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU<br>END:STANDARD<br>', 661 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:CDT<br>TZOFFSETFROM:-0600<br>TZOFFSETTO:-0500<br>DTSTART:19700308T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:CST<br>TZOFFSETFROM:-0500<br>TZOFFSETTO:-0600<br>DTSTART:19701101T020000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU<br>END:STANDARD<br>', 662 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:MDT<br>TZOFFSETFROM:-0700<br>TZOFFSETTO:-0600<br>DTSTART:19700405T020000<br>RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:MST<br>TZOFFSETFROM:-0600<br>TZOFFSETTO:-0700<br>DTSTART:19701025T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>', 663 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:MST<br>TZOFFSETFROM:-0700<br>TZOFFSETTO:-0700<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 664 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:EDT<br>TZOFFSETFROM:-0500<br>TZOFFSETTO:-0400<br>DTSTART:19700308T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:EST<br>TZOFFSETFROM:-0400<br>TZOFFSETTO:-0500<br>DTSTART:19701101T020000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU<br>END:STANDARD<br>', 665 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:PDT<br>TZOFFSETFROM:-0800<br>TZOFFSETTO:-0700<br>DTSTART:19700308T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:PST<br>TZOFFSETFROM:-0700<br>TZOFFSETTO:-0800<br>DTSTART:19701101T020000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU<br>END:STANDARD<br>', 666 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:ADT<br>TZOFFSETFROM:-0400<br>TZOFFSETTO:-0300<br>DTSTART:19700308T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:AST<br>TZOFFSETFROM:-0300<br>TZOFFSETTO:-0400<br>DTSTART:19701101T020000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU<br>END:STANDARD<br>', 667 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:-02<br>TZOFFSETFROM:-0300<br>TZOFFSETTO:-0200<br>DTSTART:19700328T220000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SA<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:-03<br>TZOFFSETFROM:-0200<br>TZOFFSETTO:-0300<br>DTSTART:19701024T230000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SA<br>END:STANDARD<br>', 668 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:AST<br>TZOFFSETFROM:-0300<br>TZOFFSETTO:-0400<br>DTSTART:19701101T020000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:ADT<br>TZOFFSETFROM:-0400<br>TZOFFSETTO:-0300<br>DTSTART:19700308T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU<br>END:DAYLIGHT<br>', 669 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:EST<br>TZOFFSETFROM:-0400<br>TZOFFSETTO:-0500<br>DTSTART:19701101T020000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:EDT<br>TZOFFSETFROM:-0500<br>TZOFFSETTO:-0400<br>DTSTART:19700308T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU<br>END:DAYLIGHT<br>', 670 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:CST<br>TZOFFSETFROM:-0400<br>TZOFFSETTO:-0500<br>DTSTART:19701101T010000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:CDT<br>TZOFFSETFROM:-0500<br>TZOFFSETTO:-0400<br>DTSTART:19700308T000000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU<br>END:DAYLIGHT<br>', 671 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:CDT<br>TZOFFSETFROM:-0600<br>TZOFFSETTO:-0500<br>DTSTART:19700405T020000<br>RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:CST<br>TZOFFSETFROM:-0500<br>TZOFFSETTO:-0600<br>DTSTART:19701025T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>', 672 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:-02<br>TZOFFSETFROM:-0300<br>TZOFFSETTO:-0200<br>DTSTART:19700308T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:-03<br>TZOFFSETFROM:-0200<br>TZOFFSETTO:-0300<br>DTSTART:19701101T020000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU<br>END:STANDARD<br>', 673 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-02<br>TZOFFSETFROM:-0200<br>TZOFFSETTO:-0200<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 674 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:CST<br>TZOFFSETFROM:-0500<br>TZOFFSETTO:-0600<br>DTSTART:19701101T020000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:CDT<br>TZOFFSETFROM:-0600<br>TZOFFSETTO:-0500<br>DTSTART:19700308T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU<br>END:DAYLIGHT<br>', 675 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-04<br>TZOFFSETFROM:-0300<br>TZOFFSETTO:-0400<br>DTSTART:19700405T000000<br>RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:-03<br>TZOFFSETFROM:-0400<br>TZOFFSETTO:-0300<br>DTSTART:19700906T000000<br>RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=1SU<br>END:DAYLIGHT<br>', 676 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:+00<br>TZOFFSETFROM:-0100<br>TZOFFSETTO:+0000<br>DTSTART:19700329T000000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:-01<br>TZOFFSETFROM:+0000<br>TZOFFSETTO:-0100<br>DTSTART:19701025T010000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>', 677 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:NST<br>TZOFFSETFROM:-0230<br>TZOFFSETTO:-0330<br>DTSTART:19701101T020000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:NDT<br>TZOFFSETFROM:-0330<br>TZOFFSETTO:-0230<br>DTSTART:19700308T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU<br>END:DAYLIGHT<br>', 678 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+11<br>TZOFFSETFROM:+1100<br>TZOFFSETTO:+1100<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 679 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+07<br>TZOFFSETFROM:+0700<br>TZOFFSETTO:+0700<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 680 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+10<br>TZOFFSETFROM:+1000<br>TZOFFSETTO:+1000<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 681 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:AEST<br>TZOFFSETFROM:+1100<br>TZOFFSETTO:+1000<br>DTSTART:19700405T030000<br>RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:AEDT<br>TZOFFSETFROM:+1000<br>TZOFFSETTO:+1100<br>DTSTART:19701004T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU<br>END:DAYLIGHT<br>', 682 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+05<br>TZOFFSETFROM:+0500<br>TZOFFSETTO:+0500<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 683 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:NZDT<br>TZOFFSETFROM:+1200<br>TZOFFSETTO:+1300<br>DTSTART:19700927T020000<br>RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:NZST<br>TZOFFSETFROM:+1300<br>TZOFFSETTO:+1200<br>DTSTART:19700405T030000<br>RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU<br>END:STANDARD<br>', 684 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+03<br>TZOFFSETFROM:+0300<br>TZOFFSETTO:+0300<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 685 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:+02<br>TZOFFSETFROM:+0000<br>TZOFFSETTO:+0200<br>DTSTART:19700329T010000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:+00<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0000<br>DTSTART:19701025T030000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>', 686 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+06<br>TZOFFSETFROM:+0600<br>TZOFFSETTO:+0600<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 687 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:EET<br>TZOFFSETFROM:+0300<br>TZOFFSETTO:+0200<br>DTSTART:19701030T010000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1FR<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:EEST<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0300<br>DTSTART:19700227T000000<br>RRULE:FREQ=YEARLY;BYMONTH=2;BYDAY=-1FR<br>END:DAYLIGHT<br>', 688 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+12<br>TZOFFSETFROM:+1200<br>TZOFFSETTO:+1200<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 689 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+04<br>TZOFFSETFROM:+0400<br>TZOFFSETTO:+0400<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 690 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:EEST<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0300<br>DTSTART:19700329T000000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:EET<br>TZOFFSETFROM:+0300<br>TZOFFSETTO:+0200<br>DTSTART:19701025T000000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>', 691 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+08<br>TZOFFSETFROM:+0800<br>TZOFFSETTO:+0800<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 692 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:IST<br>TZOFFSETFROM:+0530<br>TZOFFSETTO:+0530<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 693 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+09<br>TZOFFSETFROM:+0900<br>TZOFFSETTO:+0900<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 694 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:CST<br>TZOFFSETFROM:+0800<br>TZOFFSETTO:+0800<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 695 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+0530<br>TZOFFSETFROM:+0530<br>TZOFFSETTO:+0530<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 696 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:EET<br>TZOFFSETFROM:+0300<br>TZOFFSETTO:+0200<br>DTSTART:19701030T000000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1FR<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:EEST<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0300<br>DTSTART:19700327T000000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1FR<br>END:DAYLIGHT<br>', 697 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:EEST<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0300<br>DTSTART:19700329T030000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:EET<br>TZOFFSETFROM:+0300<br>TZOFFSETTO:+0200<br>DTSTART:19701025T040000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>', 698 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:EET<br>TZOFFSETFROM:+0300<br>TZOFFSETTO:+0200<br>DTSTART:19701024T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SA<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:EEST<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0300<br>DTSTART:19700328T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SA<br>END:DAYLIGHT<br>', 699 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:HKT<br>TZOFFSETFROM:+0800<br>TZOFFSETTO:+0800<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 700 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:WIB<br>TZOFFSETFROM:+0700<br>TZOFFSETTO:+0700<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 701 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:WIT<br>TZOFFSETFROM:+0900<br>TZOFFSETTO:+0900<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 702 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:IDT<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0300<br>DTSTART:19700327T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1FR<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:IST<br>TZOFFSETFROM:+0300<br>TZOFFSETTO:+0200<br>DTSTART:19701025T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>', 703 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+0430<br>TZOFFSETFROM:+0430<br>TZOFFSETTO:+0430<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 704 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:PKT<br>TZOFFSETFROM:+0500<br>TZOFFSETTO:+0500<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 705 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+0545<br>TZOFFSETFROM:+0545<br>TZOFFSETTO:+0545<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 706 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:WITA<br>TZOFFSETFROM:+0800<br>TZOFFSETTO:+0800<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 707 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:PST<br>TZOFFSETFROM:+0800<br>TZOFFSETTO:+0800<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 708 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:EET<br>TZOFFSETFROM:+0300<br>TZOFFSETTO:+0200<br>DTSTART:19701025T040000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:EEST<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0300<br>DTSTART:19700329T030000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU<br>END:DAYLI
708GHT<br>', 709 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:KST<br>TZOFFSETFROM:+0900<br>TZOFFSETTO:+0900<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 710 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+0630<br>TZOFFSETFROM:+0630<br>TZOFFSETTO:+0630<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 711 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+0330<br>TZOFFSETFROM:+0330<br>TZOFFSETTO:+0330<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 712 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:JST<br>TZOFFSETFROM:+0900<br>TZOFFSETTO:+0900<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 713 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:WEST<br>TZOFFSETFROM:+0000<br>TZOFFSETTO:+0100<br>DTSTART:19700329T010000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:WET<br>TZOFFSETFROM:+0100<br>TZOFFSETTO:+0000<br>DTSTART:19701025T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>', 714 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-01<br>TZOFFSETFROM:-0100<br>TZOFFSETTO:-0100<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 715 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:ACST<br>TZOFFSETFROM:+1030<br>TZOFFSETTO:+0930<br>DTSTART:19700405T030000<br>RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:ACDT<br>TZOFFSETFROM:+0930<br>TZOFFSETTO:+1030<br>DTSTART:19701004T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU<br>END:DAYLIGHT<br>', 716 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:AEST<br>TZOFFSETFROM:+1000<br>TZOFFSETTO:+1000<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 717 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:AEDT<br>TZOFFSETFROM:+1000<br>TZOFFSETTO:+1100<br>DTSTART:19701004T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:AEST<br>TZOFFSETFROM:+1100<br>TZOFFSETTO:+1000<br>DTSTART:19700405T030000<br>RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU<br>END:STANDARD<br>', 718 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:ACST<br>TZOFFSETFROM:+0930<br>TZOFFSETTO:+0930<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 719 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+0845<br>TZOFFSETFROM:+0845<br>TZOFFSETTO:+0845<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 720 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+1030<br>TZOFFSETFROM:+1100<br>TZOFFSETTO:+1030<br>DTSTART:19700405T020000<br>RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:+11<br>TZOFFSETFROM:+1030<br>TZOFFSETTO:+1100<br>DTSTART:19701004T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU<br>END:DAYLIGHT<br>', 721 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:AWST<br>TZOFFSETFROM:+0800<br>TZOFFSETTO:+0800<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 722 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-06<br>TZOFFSETFROM:-0500<br>TZOFFSETTO:-0600<br>DTSTART:19700404T220000<br>RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SA<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:-05<br>TZOFFSETFROM:-0600<br>TZOFFSETTO:-0500<br>DTSTART:19700905T220000<br>RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=1SA<br>END:DAYLIGHT<br>', 723 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:IST<br>TZOFFSETFROM:+0000<br>TZOFFSETTO:+0100<br>DTSTART:19700329T010000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:GMT<br>TZOFFSETFROM:+0100<br>TZOFFSETTO:+0000<br>DTSTART:19701025T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:DAYLIGHT<br>', 724 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+13<br>TZOFFSETFROM:+1300<br>TZOFFSETTO:+1300<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 725 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+14<br>TZOFFSETFROM:+1400<br>TZOFFSETTO:+1400<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 726 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:+02<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0200<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 727 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-10<br>TZOFFSETFROM:-1000<br>TZOFFSETTO:-1000<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 728 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-11<br>TZOFFSETFROM:-1100<br>TZOFFSETTO:-1100<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 729 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-12<br>TZOFFSETFROM:-1200<br>TZOFFSETTO:-1200<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 730 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-06<br>TZOFFSETFROM:-0600<br>TZOFFSETTO:-0600<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 731 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-07<br>TZOFFSETFROM:-0700<br>TZOFFSETTO:-0700<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 732 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-08<br>TZOFFSETFROM:-0800<br>TZOFFSETTO:-0800<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 733 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-09<br>TZOFFSETFROM:-0900<br>TZOFFSETTO:-0900<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 734 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:UTC<br>TZOFFSETFROM:+0000<br>TZOFFSETTO:+0000<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 735 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:BST<br>TZOFFSETFROM:+0000<br>TZOFFSETTO:+0100<br>DTSTART:19700329T010000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:GMT<br>TZOFFSETFROM:+0100<br>TZOFFSETTO:+0000<br>DTSTART:19701025T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>', 736 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:EEST<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0300<br>DTSTART:19700329T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:EET<br>TZOFFSETFROM:+0300<br>TZOFFSETTO:+0200<br>DTSTART:19701025T030000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>', 737 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:WET<br>TZOFFSETFROM:+0100<br>TZOFFSETTO:+0000<br>DTSTART:19701025T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>BEGIN:DAYLIGHT<br>TZNAME:WEST<br>TZOFFSETFROM:+0000<br>TZOFFSETTO:+0100<br>DTSTART:19700329T010000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU<br>END:DAYLIGHT<br>', 738 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:MSK<br>TZOFFSETFROM:+0300<br>TZOFFSETTO:+0300<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 739 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:HST<br>TZOFFSETFROM:-1000<br>TZOFFSETTO:-1000<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 740 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:MEST<br>TZOFFSETFROM:+0100<br>TZOFFSETTO:+0200<br>DTSTART:19700329T020000<br>RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:MET<br>TZOFFSETFROM:+0200<br>TZOFFSETTO:+0100<br>DTSTART:19701025T030000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU<br>END:STANDARD<br>', 741 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:+1345<br>TZOFFSETFROM:+1245<br>TZOFFSETTO:+1345<br>DTSTART:19700927T024500<br>RRULE:FREQ=YEARLY;BYMONTH=9;BYDAY=-1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:+1245<br>TZOFFSETFROM:+1345<br>TZOFFSETTO:+1245<br>DTSTART:19700405T034500<br>RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU<br>END:STANDARD<br>', 742 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:+13<br>TZOFFSETFROM:+1200<br>TZOFFSETTO:+1300<br>DTSTART:19701108T020000<br>RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=2SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:+12<br>TZOFFSETFROM:+1300<br>TZOFFSETTO:+1200<br>DTSTART:19700118T030000<br>RRULE:FREQ=YEARLY;BYMONTH=1;BYDAY=-2SU<br>END:STANDARD<br>', 743 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:ChST<br>TZOFFSETFROM:+1000<br>TZOFFSETTO:+1000<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 744 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:-0930<br>TZOFFSETFROM:-0930<br>TZOFFSETTO:-0930<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 745 '20220929T150625Z<br>BEGIN:STANDARD<br>TZNAME:SST<br>TZOFFSETFROM:-1100<br>TZOFFSETTO:-1100<br>DTSTART:19700101T000000<br>END:STANDARD<br>', 746 '20220929T150625Z<br>BEGIN:DAYLIGHT<br>TZNAME:+12<br>TZOFFSETFROM:+1100<br>TZOFFSETTO:+1200<br>DTSTART:19701004T020000<br>RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU<br>END:DAYLIGHT<br>BEGIN:STANDARD<br>TZNAME:+11<br>TZOFFSETFROM:+1200<br>TZOFFSETTO:+1100<br>DTSTART:19700405T030000<br>RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU<br>END:STANDARD<br>', 747]; 748 749 750// SHARED FUNCTION TO GET THE TZ CONTENT FROM THE INTERNAL DATABASE 751function tzlib_get_content(tzName) { 752 // get timezone parts 753 const nameParts = tzName.split('/'); 754 // validate timezone 755 // TODO: Make this a little bit smarter (depending on the future db structure) 756 if ( 757 (nameParts.length === 3 && 758 (!tzlibZonesDB[`${nameParts[0]}`] || 759 !tzlibZonesDB[`${nameParts[0]}`][`${nameParts[1]}`] || 760 !tzlibZonesDB[`${nameParts[0]}`][`${nameParts[1]}`][`${nameParts[2]}`])) || 761 (nameParts.length === 2 && 762 (!tzlibZonesDB[`${nameParts[0]}`] || !tzlibZonesDB[`${nameParts[0]}`][`${nameParts[1]}`])) || 763 (nameParts.length === 1 && !tzlibZonesDB[`${nameParts[0]}`]) 764 ) { 765 console.error('Given timezone not valid.'); 766 return ''; 767 } 768 // create the output 769 if (nameParts.length === 3) { 770 return [ 771 tzlibZonesDB[`${nameParts[0]}`][`${nameParts[1]}`][`${nameParts[2]}`][0], 772 tzlibZonesDetailsDB[tzlibZonesDB[`${nameParts[0]}`][`${nameParts[1]}`][`${nameParts[2]}`][1]], 773 ]; 774 } 775 if (nameParts.length === 2) { 776 return [ 777 tzlibZonesDB[`${nameParts[0]}`][`${nameParts[1]}`][0], 778 tzlibZonesDetailsDB[tzlibZonesDB[`${nameParts[0]}`][`${nameParts[1]}`][1]], 779 ]; 780 } 781 return [tzlibZonesDB[`${nameParts[0]}`][0], tzlibZonesDetailsDB[tzlibZonesDB[`${nameParts[0]}`][1]]]; 782} 783 784// LOADING THE RIGHT CODE BLOCK 785// eslint-disable-next-line @typescript-eslint/no-unused-vars 786function tzlib_get_ical_block(tzName, jsonType = false) { 787 const tzBlock = tzlib_get_content(tzName); 788 if (tzBlock[1] == null || tzBlock[1] == '') { 789 return ''; 790 } 791 // create the output 792 const location = (function () { 793 if (tzBlock[0] == '') { 794 return tzName; 795 } else { 796 return tzBlock[0]; 797 } 798 })(); 799 const tzidLine = 'TZID=' + location; 800 const output = [
801 'BEGIN:VTIMEZONE\r\nTZID:' + 802 location + 803 '\r\nX-LIC-LOCATION:' + 804 location + 805 '\r\nLAST-MODIFIED:' + 806 tzBlock[1].replace(/[^\w_\-:,;=+/<br>]/g, '').replace(/<br>/g, '\r\n') + 807 'END:VTIMEZONE', 808 tzidLine, 809 ]; 810 // return 811 if (jsonType) { 812 return JSON.stringify(output); 813 } 814 return output; 815} 816 817// PROVIDING THE OFFSET BASED ON A GIVEN DATE AND TIME (YYYY-MM-DD and hh:mm as per ISO-8601). 818// eslint-disable-next-line @typescript-eslint/no-unused-vars 819function tzlib_get_offset(tzName, isoDate, isoTime) { 820 const tzBlock = tzlib_get_content(tzName); 821 if (tzBlock[1] == null || tzBlock[1] == '') { 822 return ''; 823 } 824 // validate date 825 if (!isoDate.match(/^\d{4}-\d{2}-\d{2}$/)) { 826 console.error('offset calculation failed: date misspelled [-> YYYY-MM-DD]'); 827 return ''; 828 } 829 // validate time 830 if (!isoTime.match(/^\d{2}:\d{2}$/)) { 831 console.error('offset calculation failed: time misspelled [-> hh:mm]'); 832 return ''; 833 } 834 // return early if there are no daylight changes 835 if (!tzBlock[1].match(/BEGIN:DAYLIGHT/i)) { 836 return tzBlock[1].match(/TZOFFSETTO:([+|-]\d{4})/i)[1]; 837 } 838 // otherwise, calculate offset 839 // creating a JS date from the input 840 const dateString = isoDate + 'T' + isoTime + ':00'; 841 const date = new Date(dateString); 842 const dateYear = date.getFullYear(); 843 const dateMonth = date.getMonth() + 1; 844 const dateDay = date.getDate(); 845 const dateHour = date.getHours(); 846 // preparing the tz data 847 const timezoneData = tzBlock[1].replace(/[^\w_\-:,;=+/<br>]/g, '').split('<br>'); 848 // collect timezone breakpoints (exactly 2) 849 const tzBreakpoints = { 1: {}, 2: {} }; 850 let breakpointCount = 0; 851 for (let i = 0; i < timezoneData.length; i++) { 852 // always first and therefore drives the counter 853 if (timezoneData[`${i}`].startsWith('TZOFFSETTO')) { 854 breakpointCount++; 855 tzBreakpoints[`${breakpointCount}`].offset = timezoneData[`${i}`].split(':')[1]; 856 } 857 // only required for the critical hour 858 if (timezoneData[`${i}`].startsWith('DTSTART')) { 859 tzBreakpoints[`${breakpointCount}`].hour = parseInt(timezoneData[`${i}`].substr(17, 2)); 860 } 861 // the RRULE is deciding when the switch happens (excluding the hour information from DTSTART) 862 if (timezoneData[`${i}`].startsWith('RRULE')) { 863 let rruleParts = timezoneData[`${i}`].split(';'); 864 let rruleMonth = parseInt(rruleParts[1].split('=')[1]); 865 tzBreakpoints[`${breakpointCount}`].month = parseInt(rruleMonth); 866 tzBreakpoints[`${breakpointCount}`].day = rruleParts[2].split('=')[1]; 867 } 868 } 869 // swap objects, if larger one comes first 870 if (tzBreakpoints[1].month > tzBreakpoints[2].month) { 871 [tzBreakpoints[1], tzBreakpoints[2]] = [tzBreakpoints[2], tzBreakpoints[1]]; 872 } 873 // check for easy cases where the month is cleary in between 874 if (dateMonth != tzBreakpoints[1].month && dateMonth != tzBreakpoints[2].month) { 875 if (dateMonth < tzBreakpoints[1].month || dateMonth > tzBreakpoints[2].month) { 876 return tzBreakpoints[2].offset; 877 } else { 878 return tzBreakpoints[1].offset; 879 } 880 } 881 // in other cases, validate where we are exactly and pick the right offset 882 // defining the critical case, we need to evaluate (the breakpoint we are matching by month) 883 const theCase = (function () { 884 return Object.keys(tzBreakpoints).find((key) => tzBreakpoints[`${key}`].month == dateMonth); 885 })(); 886 // determining the actual day 887 const helperArrayWeekdays = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; 888 const numberDays = new Date(dateYear, dateMonth, 0).getDate(); 889 let weekdayCount = new Date(dateYear, dateMonth - 1, 1).getDay(); 890 const weekdays = { SU: {}, MO: {}, TU: {}, WE: {}, TH: {}, FR: {}, SA: {} }; 891 for (let d = 1; d <= numberDays; d++) { 892 const occurence = Object.keys(weekdays[helperArrayWeekdays[`${weekdayCount}`]]).length + 1; 893 weekdays[helperArrayWeekdays[`${weekdayCount}`]][`${occurence}`] = d; 894 weekdayCount++; 895 if (weekdayCount == 7) { 896 weekdayCount = 0; 897 } 898 } 899 const actualDay = (function () { 900 if (tzBreakpoints[`${theCase}`].day[0] == '-') {
901 const breakpointWeekday = tzBreakpoints[`${theCase}`].day.substr(2, 2); 902 const dayIndex = 903 Object.keys(weekdays[`${breakpointWeekday}`]).length + 904 1 - 905 parseInt(tzBreakpoints[`${theCase}`].day[1]); 906 return weekdays[`${breakpointWeekday}`][`${dayIndex}`]; 907 } else { 908 const breakpointWeekday = tzBreakpoints[`${theCase}`].day.substr(1, 2); 909 return weekdays[`${breakpointWeekday}`][tzBreakpoints[`${theCase}`].day[0]]; 910 } 911 })(); 912 // finally identifying the right offset 913 if (dateDay > actualDay || (dateDay == actualDay && dateHour >= tzBreakpoints[`${theCase}`].hour)) { 914 return tzBreakpoints[`${theCase}`].offset; 915 } 916 const fallbackCase = (function () { 917 if (theCase == 1) { 918 return 2; 919 } else { 920 return 1; 921 } 922 })(); 923 return tzBreakpoints[`${fallbackCase}`].offset; 924} 925 926// PROVIDE ALL TIMEZONES 927// eslint-disable-next-line @typescript-eslint/no-unused-vars 928function tzlib_get_timezones(jsonType = false) { 929 const tzlibZoneNames = [ 930 "Africa/Abidjan", 931 "Africa/Accra", 932 "Africa/Addis_Ababa", 933 "Africa/Algiers", 934 "Africa/Asmara", 935 "Africa/Asmera", 936 "Africa/Bamako", 937 "Africa/Bangui", 938 "Africa/Banjul", 939 "Africa/Bissau", 940 "Africa/Blantyre", 941 "Africa/Brazzaville", 942 "Africa/Bujumbura", 943 "Africa/Cairo", 944 "Africa/Casablanca", 945 "Africa/Ceuta", 946 "Africa/Conakry", 947 "Africa/Dakar", 948 "Africa/Dar_es_Salaam", 949 "Africa/Djibouti", 950 "Africa/Douala", 951 "Africa/El_Aaiun", 952 "Africa/Freetown", 953 "Africa/Gaborone", 954 "Africa/Harare", 955 "Africa/Johannesburg", 956 "Africa/Juba", 957 "Africa/Kampala", 958 "Africa/Khartoum", 959 "Africa/Kigali", 960 "Africa/Kinshasa", 961 "Africa/Lagos", 962 "Africa/Libreville", 963 "Africa/Lome", 964 "Africa/Luanda", 965 "Africa/Lubumbashi", 966 "Africa/Lusaka", 967 "Africa/Malabo", 968 "Africa/Maputo", 969 "Africa/Maseru", 970 "Africa/Mbabane", 971 "Africa/Mogadishu", 972 "Africa/Monrovia", 973 "Africa/Nairobi", 974 "Africa/Ndjamena", 975 "Africa/Niamey", 976 "Africa/Nouakchott", 977 "Africa/Ouagadougou", 978 "Africa/Porto-Novo", 979 "Africa/Sao_Tome", 980 "Africa/Timbuktu", 981 "Africa/Tripoli", 982 "Africa/Tunis", 983 "Africa/Windhoek", 984 "America/Adak", 985 "America/Anchorage", 986 "America/Anguilla", 987 "America/Antigua", 988 "America/Araguaina", 989 "America/Argentina/Buenos_Aires", 990 "America/Argentina/Catamarca", 991 "America/Argentina/ComodRivadavia", 992 "America/Argentina/Cordoba", 993 "America/Argentina/Jujuy", 994 "America/Argentina/La_Rioja", 995 "America/Argentina/Mendoza", 996 "America/Argentina/Rio_Gallegos", 997 "America/Argentina/Salta", 998 "America/Argentina/San_Juan", 999 "America/Argentina/San_Luis", 1000 "America/Argentina/Tucuman", 1001 "America/Argentina/Ushuaia", 1002 "America/Aruba", 1003 "America/Asuncion", 1004 "America/Atikokan", 1005 "America/Atka", 1006 "America/Bahia_Banderas", 1007 "America/Bahia", 1008 "America/Barbados", 1009 "America/Belem", 1010 "America/Belize", 1011 "America/Blanc-Sablon", 1012 "America/Boa_Vista", 1013 "America/Bogota", 1014 "America/Boise", 1015 "America/Buenos_Aires", 1016 "America/Cambridge_Bay", 1017 "America/Campo_Grande", 1018 "America/Cancun", 1019 "America/Caracas", 1020 "America/Catamarca", 1021 "America/Cayenne", 1022 "America/Cayman", 1023 "America/Chicago", 1024 "America/Chihuahua", 1025 "America/Coral_Harbour", 1026 "America/Cordoba", 1027 "America/Costa_Rica", 1028 "America/Creston", 1029 "America/Cuiaba", 1030 "America/Curacao", 1031 "America/Danmarkshavn", 1032 "America/Dawson_Creek", 1033 "America/Dawson", 1034 "America/Denver", 1035 "America/Detroit", 1036 "America/Dominica", 1037 "America/Edmonton", 1038 "America/Eirunepe", 1039 "America/El_Salvador", 1040 "America/Ensenada", 1041 "America/Fort_Nelson", 1042 "America/Fort_Wayne", 1043 "America/Fortaleza", 1044 "America/Glace_Bay", 1045 "America/Godthab", 1046 "America/Goose_Bay", 1047 "America/Grand_Turk", 1048 "America/Grenada", 1049 "America/Guadeloupe", 1050 "America/Guatemala", 1051 "America/Guayaquil", 1052 "America/Guyana", 1053 "America/Halifax", 1054 "America/Havana", 1055 "America/Hermosillo", 1056 "America/Indiana/Indianapolis", 1057 "America/Indiana/Knox", 1058 "America/Indiana/Marengo", 1059 "America/Indiana/Petersburg", 1060 "America/Indiana/Tell_City", 1061 "America/Indiana/Vevay", 1062 "America/Indiana/Vincennes", 1063 "America/Indiana/Winamac", 1064 "America/Indianapolis", 1065 "America/Inuvik", 1066 "America/Iqaluit", 1067 "America/Jamaica", 1068 "America/Jujuy", 1069 "America/Juneau", 1070 "America/Kentucky/Louisville", 1071 "America/Kentucky/Monticello", 1072 "America/Knox_IN", 1073 "America/Kralendijk", 1074 "America/La_Paz", 1075 "America/Lima", 1076 "America/Los_Angeles", 1077 "America/Louisville", 1078 "America/Lower_Princes", 1079 "America/Maceio", 1080 "America/Managua", 1081 "America/Manaus", 1082 "America/Marigot", 1083 "America/Martinique", 1084 "America/Matamoros", 1085 "America/Mazatlan", 1086 "America/Mendoza", 1087 "America/Menominee", 1088 "America/Merida", 1089 "America/Metlakatla", 1090 "America/Mexico_City", 1091 "America/Miquelon", 1092 "America/Moncton", 1093 "America/Monterrey", 1094 "America/Montevideo", 1095 "America/Montreal", 1096 "America/Montserrat", 1097 "America/Nassau",
1098 "America/New_York", 1099 "America/Nipigon", 1100 "America/Nome", 1101 "America/Noronha", 1102 "America/North_Dakota/Beulah", 1103 "America/North_Dakota/Center", 1104 "America/North_Dakota/New_Salem", 1105 "America/Nuuk", 1106 "America/Ojinaga", 1107 "America/Panama", 1108 "America/Pangnirtung", 1109 "America/Paramaribo", 1110 "America/Phoenix", 1111 "America/Port_of_Spain", 1112 "America/Port-au-Prince", 1113 "America/Porto_Acre", 1114 "America/Porto_Velho", 1115 "America/Puerto_Rico", 1116 "America/Punta_Arenas", 1117 "America/Rainy_River", 1118 "America/Rankin_Inlet", 1119 "America/Recife", 1120 "America/Regina", 1121 "America/Resolute", 1122 "America/Rio_Branco", 1123 "America/Rosario", 1124 "America/Santa_Isabel", 1125 "America/Santarem", 1126 "America/Santiago", 1127 "America/Santo_Domingo", 1128 "America/Sao_Paulo", 1129 "America/Scoresbysund", 1130 "America/Shiprock", 1131 "America/Sitka", 1132 "America/St_Barthelemy", 1133 "America/St_Johns", 1134 "America/St_Kitts", 1135 "America/St_Lucia", 1136 "America/St_Thomas", 1137 "America/St_Vincent", 1138 "America/Swift_Current", 1139 "America/Tegucigalpa", 1140 "America/Thule", 1141 "America/Thunder_Bay", 1142 "America/Tijuana", 1143 "America/Toronto", 1144 "America/Tortola", 1145 "America/Vancouver", 1146 "America/Virgin", 1147 "America/Whitehorse", 1148 "America/Winnipeg", 1149 "America/Yakutat", 1150 "America/Yellowknife", 1151 "Antarctica/Casey", 1152 "Antarctica/Davis", 1153 "Antarctica/DumontDUrville", 1154 "Antarctica/Macquarie", 1155 "Antarctica/Mawson", 1156 "Antarctica/McMurdo", 1157 "Antarctica/Palmer", 1158 "Antarctica/Rothera", 1159 "Antarctica/South_Pole", 1160 "Antarctica/Syowa", 1161 "Antarctica/Troll", 1162 "Antarctica/Vostok", 1163 "Arctic/Longyearbyen", 1164 "Asia/Aden", 1165 "Asia/Almaty", 1166 "Asia/Amman", 1167 "Asia/Anadyr", 1168 "Asia/Aqtau", 1169 "Asia/Aqtobe", 1170 "Asia/Ashgabat", 1171 "Asia/Ashkhabad", 1172 "Asia/Atyrau", 1173 "Asia/Baghdad", 1174 "Asia/Bahrain", 1175 "Asia/Baku", 1176 "Asia/Bangkok", 1177 "Asia/Barnaul", 1178 "Asia/Beirut", 1179 "Asia/Bishkek", 1180 "Asia/Brunei", 1181 "Asia/Calcutta", 1182 "Asia/Chita", 1183 "Asia/Choibalsan", 1184 "Asia/Chongqing", 1185 "Asia/Chungking", 1186 "Asia/Colombo", 1187 "Asia/Dacca", 1188 "Asia/Damascus", 1189 "Asia/Dhaka", 1190 "Asia/Dili", 1191 "Asia/Dubai", 1192 "Asia/Dushanbe", 1193 "Asia/Famagusta", 1194 "Asia/Gaza", 1195 "Asia/Harbin", 1196 "Asia/Hebron", 1197 "Asia/Ho_Chi_Minh", 1198 "Asia/Hong_Kong", 1199 "Asia/Hovd", 1200 "Asia/Irkutsk", 1201 "Asia/Istanbul", 1202 "Asia/Jakarta", 1203 "Asia/Jayapura", 1204 "Asia/Jerusalem", 1205 "Asia/Kabul", 1206 "Asia/Kamchatka", 1207 "Asia/Karachi", 1208 "Asia/Kashgar", 1209 "Asia/Kathmandu", 1210 "Asia/Katmandu", 1211 "Asia/Khandyga", 1212 "Asia/Kolkata", 1213 "Asia/Krasnoyarsk", 1214 "Asia/Kuala_Lumpur", 1215 "Asia/Kuching", 1216 "Asia/Kuwait", 1217 "Asia/Macao", 1218 "Asia/Macau", 1219 "Asia/Magadan", 1220 "Asia/Makassar", 1221 "Asia/Manila", 1222 "Asia/Muscat", 1223 "Asia/Nicosia", 1224 "Asia/Novokuznetsk", 1225 "Asia/Novosibirsk", 1226 "Asia/Omsk", 1227 "Asia/Oral", 1228 "Asia/Phnom_Penh", 1229 "Asia/Pontianak", 1230 "Asia/Pyongyang", 1231 "Asia/Qatar", 1232 "Asia/Qostanay", 1233 "Asia/Qyzylorda", 1234 "Asia/Rangoon", 1235 "Asia/Riyadh", 1236 "Asia/Saigon", 1237 "Asia/Sakhalin", 1238 "Asia/Samarkand", 1239 "Asia/Seoul", 1240 "Asia/Shanghai", 1241 "Asia/Singapore", 1242 "Asia/Srednekolymsk", 1243 "Asia/Taipei", 1244 "Asia/Tashkent", 1245 "Asia/Tbilisi", 1246 "Asia/Tehran", 1247 "Asia/Tel_Aviv", 1248 "Asia/Thimbu", 1249 "Asia/Thimphu", 1250 "Asia/Tokyo", 1251 "Asia/Tomsk", 1252 "Asia/Ujung_Pandang", 1253 "Asia/Ulaanbaatar", 1254 "Asia/Ulan_Bator", 1255 "Asia/Urumqi", 1256 "Asia/Ust-Nera", 1257 "Asia/Vientiane", 1258 "Asia/Vladivostok", 1259 "Asia/Yakutsk", 1260 "Asia/Yangon", 1261 "Asia/Yekaterinburg", 1262 "Asia/Yerevan", 1263 "Atlantic/Azores", 1264 "Atlantic/Bermuda", 1265 "Atlantic/Canary", 1266 "Atlantic/Cape_Verde", 1267 "Atlantic/Faeroe", 1268 "Atlantic/Faroe", 1269 "Atlantic/Jan_Mayen", 1270 "Atlantic/Madeira", 1271 "Atlantic/Reykjavik", 1272 "Atlantic/South_Georgia", 1273 "Atlantic/St_Helena", 1274 "Atlantic/Stanley", 1275 "Australia/ACT", 1276 "Australia/Adelaide", 1277 "Australia/Brisbane", 1278 "Australia/Broken_Hill", 1279 "Australia/Canberra", 1280 "Australia/Currie", 1281 "Australia/Darwin", 1282 "Australia/Eucla", 1283 "Australia/Hobart", 1284 "Australia/LHI", 1285 "Australia/Lindeman", 1286 "Australia/Lord_Howe", 1287 "Australia/Melbourne", 1288 "Australia/North", 1289 "Australia/NSW", 1290 "Australia/Perth", 1291 "Australia/Queensland", 1292 "Australia/South", 1293 "Australia/Sydney", 1294 "Australia/Tasmania", 1295 "Australia/Victoria", 1296 "Australia/West", 1297 "Australia/Yancowinna", 1298 "Brazil/Acre", 1299 "Brazil/DeNoronha", 1300 "Brazil/East", 1301 "Brazil/West", 1302 "Canada/Atlantic", 1303 "Canada/Central", 1304 "Canada/Eastern", 1305 "Canada/Mountain", 1306 "Canada/Newfoundland", 1307 "Canada/Pacific", 1308 "Canada/Saskatchewan", 1309 "Canada/Yukon", 1310 "CET", 1311 "Chile/Continental", 1312 "Chile/EasterIsland", 1313 "CST6CDT", 1314 "Cuba", 1315 "EET", 1316 "Egypt", 1317 "Eire", 1318 "EST", 1319 "EST5EDT", 1320 "Etc/GMT-0", 1321 "Etc/GMT-1", 1322 "Etc/GMT-10", 1323 "Etc/GMT-11", 1324 "Etc/GMT-12", 1325 "Etc/GMT-13", 1326 "Etc/GMT-14", 1327 "Etc/GMT-2", 1328 "Etc/GMT-3", 1329 "Etc/GMT-4", 1330 "Etc/GMT-5", 1331 "Etc/GMT-6", 1332 "Etc/GMT-7", 1333 "Etc/GMT-8", 1334 "Etc/GMT-9", 1335 "Etc/GMT", 1336 "Etc/GMT+0", 1337 "Etc/GMT+1", 1338 "Etc/GMT+10", 1339 "Etc/GMT+11", 1340 "Etc/GMT+12", 1341 "Etc/GMT+2", 1342 "Etc/GMT+3", 1343 "Etc/GMT+4", 1344 "Etc/GMT+5", 1345 "Etc/GMT+6", 1346 "Etc/GMT+7", 1347 "Etc/GMT+8", 1348 "Etc/GMT+9", 1349 "Etc/GMT0", 1350 "Etc/Greenwich", 1351 "Etc/UCT", 1352 "Etc/Universal", 1353 "Etc/UTC", 1354 "Etc/Zulu", 1355 "Europe/Amsterdam", 1356 "Europe/Andorra", 1357 "Europe/Astrakhan", 1358 "Europe/Athens", 1359 "Europe/Belfast", 1360 "Europe/Belgrade", 1361 "Europe/Berlin", 1362 "Europe/Bratislava", 1363 "Europe/Brussels", 1364 "Europe/Bucharest", 1365 "Europe/Budapest", 1366 "Europe/Busingen", 1367 "Europe/Chisinau", 1368 "Europe/Copenhagen", 1369 "Europe/Dublin", 1370 "Europe/Gibraltar", 1371 "Europe/Guernsey", 1372 "Europe/Helsinki", 1373 "Europe/Isle_of_Man", 1374 "Europe/Istanbul", 1375 "Europe/Jersey", 1376 "Europe/Kaliningrad", 1377 "Europe/Kiev", 1378 "Europe/Kirov", 1379 "Europe/Kyiv", 1380 "Europe/Lisbon", 1381 "Europe/Ljubljana", 1382 "Europe/London", 1383 "Europe/Luxembourg", 1384 "Europe/Madrid", 1385 "Europe/Malta", 1386 "Europe/Mariehamn", 1387 "Europe/Minsk", 1388 "Europe/Monaco", 1389 "Europe/Moscow", 1390 "Europe/Nicosia", 1391 "Europe/Oslo", 1392 "Europe/Paris", 1393 "Europe/Podgorica", 1394 "Europe/Prague", 1395 "Europe/Riga", 1396 "Europe/Rome", 1397 "Europe/Samara", 1398 "Europe/San_Marino", 1399 "Europe/Sarajevo", 1400 "Europe/Saratov", 1401 "Europe/Simferopol", 1402 "Europe/Skopje", 1403 "Europe/Sofia", 1404 "Europe/Stockholm", 1405 "Europe/Tallinn", 1406 "Europe/Tirane", 1407 "Europe/Tiraspol", 1408 "Europe/Ulyanovsk", 1409 "Europe/Uzhgorod", 1410 "Europe/Vaduz", 1411 "Europe/Vatican", 1412 "Europe/Vienna", 1413 "Europe/Vilnius", 1414 "Europe/Volgograd", 1415 "Europe/Warsaw", 1416 "Europe/Zagreb", 1417 "Europe/Zaporozhye", 1418 "Europe/Zurich", 1419 "GB-Eire", 1420 "GB", 1421 "GMT-0", 1422 "GMT", 1423 "GMT+0", 1424 "GMT0", 1425 "Greenwich", 1426 "Hongkong", 1427 "HST", 1428 "Iceland", 1429 "Indian/Antananarivo", 1430 "Indian/Chagos", 1431 "Indian/Christmas", 1432 "Indian/Cocos", 1433 "Indian/Comoro", 1434 "Indian/Kerguelen", 1435 "Indian/Mahe", 1436 "Indian/Maldives", 1437 "Indian/Mauritius", 1438 "Indian/Mayotte", 1439 "Indian/Reunion", 1440 "Iran", 1441 "Israel", 1442 "Jamaica", 1443 "Japan", 1444 "Kwajalein", 1445 "Libya", 1446 "MET", 1447 "Mexico/BajaNorte", 1448 "Mexico/BajaSur", 1449 "Mexico/General", 1450 "MST", 1451 "MST7MDT", 1452 "Navajo", 1453 "NZ-CHAT", 1454 "NZ", 1455 "Pacific/Apia", 1456 "Pacific/Auckland", 1457 "Pacific/Bougainville", 1458 "Pacific/Chatham", 1459 "Pacific/Chuuk", 1460 "Pacific/Easter", 1461 "Pacific/Efate", 1462 "Pacific/Enderbury", 1463 "Pacific/Fakaofo", 1464 "Pacific/Fiji", 1465 "Pacific/Funafuti", 1466 "Pacific/Galapagos", 1467 "Pacific/Gambier", 1468 "Pacific/Guadalcanal", 1469 "Pacific/Guam", 1470 "Pacific/Honolulu", 1471 "Pacific/Johnston", 1472 "Pacific/Kanton", 1473 "Pacific/Kiritimati", 1474 "Pacific/Kosrae", 1475 "Pacific/Kwajalein", 1476 "Pacific/Majuro", 1477 "Pacific/Marquesas", 1478 "Pacific/Midway", 1479 "Pacific/Nauru", 1480 "Pacific/Niue", 1481 "Pacific/Norfolk", 1482 "Pacific/Noumea", 1483 "Pacific/Pago_Pago", 1484 "Pacific/Palau", 1485 "Pacific/Pitcairn", 1486 "Pacific/Pohnpei", 1487 "Pacific/Ponape", 1488 "Pacific/Port_Moresby", 1489 "Pacific/Rarotonga", 1490 "Pacific/Saipan", 1491 "Pacific/Samoa", 1492 "Pacific/Tahiti", 1493 "Pacific/Tarawa", 1494 "Pacific/Tongatapu", 1495 "Pacific/Truk", 1496 "Pacific/Wake", 1497 "Pacific/Wallis", 1498 "Pacific/Yap", 1499 "Poland", 1500 "Portugal", 1501 "PRC", 1502 "PST8PDT", 1503 "ROC", 1504 "ROK", 1505 "Singapore", 1506 "Turkey", 1507 "UCT", 1508 "Universal", 1509 "US/Alaska", 1510 "US/Aleutian", 1511 "US/Arizona", 1512 "US/Central", 1513 "US/East-Indiana", 1514 "US/Eastern", 1515 "US/Hawaii", 1516 "US/Indiana-Starke", 1517 "US/Michigan", 1518 "US/Mountain", 1519 "US/Pacific", 1520 "US/Samoa", 1521 "UTC", 1522 "W-SU", 1523 "WET", 1524 "Zulu" 1525] 1526; 1527 if (jsonType) { 1528 return JSON.stringify(tzlibZoneNames); 1529 } 1530 return tzlibZoneNames; 1531} 1532 1533 1534 1535 1536 1537/*! 1538 * @preserve 1539 * 1540 * ++++++++++++++++++++++ 1541 * Add to Calendar Button 1542 * ++++++++++++++++++++++ 1543 * 1544 * Creator: Jens Kuerschner (https://jenskuerschner.de) 1545 * Project: https://github.com/add2cal/add-to-calendar-button
1546 * License: Apache-2.0 with âCommons Clauseâ License Condition v1.0 1547 * Note: DO NOT REMOVE THE COPYRIGHT NOTICE ABOVE! 1548 * 1549 */ 1550const atcbVersion = '1.17.0'; 1551 1552 1553 1554// CHECKING FOR SPECIFIC DEVICED AND SYSTEMS 1555// browser 1556const isBrowser = () => { 1557 if (typeof window === 'undefined') { 1558 return false; 1559 } else { 1560 return true; 1561 } 1562}; 1563// iOS 1564const isiOS = isBrowser() 1565 ? () => { 1566 if ( 1567 (/iPad|iPhone|iPod/i.test(navigator.userAgent || navigator.vendor || window.opera) && 1568 !window.MSStream) || 1569 (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) 1570 ) { 1571 return true; 1572 } else { 1573 return false; 1574 } 1575 } 1576 : () => { 1577 return false; 1578 }; 1579// Android 1580const isAndroid = isBrowser() 1581 ? () => { 1582 if (/android/i.test(navigator.userAgent || navigator.vendor || window.opera) && !window.MSStream) { 1583 return true; 1584 } else { 1585 return false; 1586 } 1587 } 1588 : () => { 1589 return false; 1590 }; 1591// Chrome 1592const isChrome = isBrowser() 1593 ? () => { 1594 if (/chrome|chromium|crios/i.test(navigator.userAgent)) { 1595 return true; 1596 } else { 1597 return false; 1598 } 1599 } 1600 : () => { 1601 return false; 1602 }; 1603// Mobile 1604const isMobile = () => { 1605 if (isAndroid() || isiOS()) { 1606 return true; 1607 } else { 1608 return false; 1609 } 1610}; 1611// WebView (iOS and Android) 1612const isWebView = isBrowser() 1613 ? () => { 1614 if ( 1615 /(; ?wv|(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari))/i.test(navigator.userAgent || navigator.vendor) 1616 ) { 1617 return true; 1618 } else { 1619 return false; 1620 } 1621 } 1622 : () => { 1623 return false; 1624 }; 1625// checking for problematic apps 1626const isProblematicWebView = isBrowser() 1627 ? () => { 1628 if (/(Instagram)/i.test(navigator.userAgent || navigator.vendor || window.opera)) { 1629 return true; 1630 } else { 1631 return false; 1632 } 1633 } 1634 : () => { 1635 return false; 1636 }; 1637 1638// DEFINE GLOBAL VARIABLES 1639let atcbConsoleInit = false; 1640const atcbDefaultTarget = isWebView() ? '_system' : '_blank'; 1641const atcbOptions = ['apple', 'google', 'ical', 'ms365', 'outlookcom', 'msteams', 'yahoo']; 1642const atcbValidRecurrOptions = ['apple', 'google', 'ical']; 1643const atcbiOSInvalidOptions = ['ical']; 1644 1645// DEFINING GLOBAL ICONS 1646const atcbIcon = { 1647 trigger: 1648 '<span class="atcb-icon-trigger"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200.016"><path d="M132.829 7.699c0-4.248 4.199-7.699 9.391-7.699s9.391 3.451 9.391 7.699v33.724c0 4.248-4.199 7.699-9.391 7.699s-9.391-3.451-9.391-7.699zm-5.941 123.747c2.979 0 5.404 2.425 5.404 5.404s-2.425 5.404-5.404 5.404l-21.077-.065-.065 21.045c0 2.979-2.425 5.404-5.404 5.404s-5.404-2.425-5.404-5.404l.065-21.061-21.045-.081c-2.979 0-5.404-2.425-5.404-5.404s2.425-5.404 5.404-5.404l21.061.065.065-21.045c0-2.979 2.425-5.404 5.404-5.404s5.404 2.425 5.404 5.404l-.065 21.077 21.061.065zM48.193 7.699C48.193 3.451 52.393 0 57.585 0s9.391 3.451 9.391 7.699v33.724c0 4.248-4.199 7.699-9.391 7.699s-9.391-3.451-9.391-7.699zM10.417 73.763h179.167V34.945c0-1.302-.537-2.49-1.4-3.369-.863-.863-2.051-1.4-3.369-1.4h-17.171c-2.881 0-5.208-2.327-5.208-5.208s2.327-5.208 5.208-5.208h17.171c4.183 0 7.975 1.709 10.726 4.46S200 30.762 200 34.945v44.043 105.843c0 4.183-1.709 7.975-4.46 10.726s-6.543 4.46-10.726 4.46H15.186c-4.183 0-7.975-1.709-10.726-4.46C1.709 192.79 0 188.997 0 184.814V78.988 34.945c0-4.183 1.709-7.975 4.46-10.726s6.543-4.46 10.726-4.46h18.343c2.881 0 5.208 2.327 5.208 5.208s-2.327 5.208-5.208 5.208H15.186c-1.302 0-2.49.537-3.369 1.4-.863.863-1.4 2.051-1.4 3.369zm179.167 10.433H10.417v100.618c0 1.302.537 2.49 1.4 3.369.863.863 2.051 1.4 3.369 1.4h169.629c1.302 0 2.49-.537 3.369-1.4.863-.863 1.4-2.051 1.4-3.369zM82.08 30.176c-2.881 0-5.208-2.327-5.208-5.208s2.327-5.208 5.208-5.208h34.977c2.881 0 5.208 2.327 5.208 5.208s-2.327 5.208-5.208 5.208z"/></svg></span>', 1649 apple: 1650 '<span class="atcb-icon-apple"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 245.657"><path d="M167.084 130.514c-.308-31.099 25.364-46.022 26.511-46.761-14.429-21.107-36.91-24.008-44.921-24.335-19.13-1.931-37.323 11.27-47.042 11.27-9.692 0-24.67-10.98-40.532-10.689-20.849.308-40.07 12.126-50.818 30.799-21.661 37.581-5.54 93.281 15.572 123.754 10.313 14.923 22.612 31.688 38.764 31.089 15.549-.612 21.433-10.073 40.242-10.073s24.086 10.073 40.546 9.751c16.737-.308 27.34-15.214 37.585-30.187 11.855-17.318 16.714-34.064 17.009-34.925-.372-.168-32.635-12.525-32.962-49.68l.045-.013zm-30.917-91.287C144.735 28.832 150.524 14.402 148.942 0c-12.344.503-27.313 8.228-36.176 18.609-7.956 9.216-14.906 23.904-13.047 38.011 13.786 1.075 27.862-7.004 36.434-17.376z"/></svg></span>', 1651 google: 1652 '<span class="atcb-icon-google"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><path d="M152.637 47.363H47.363v105.273h105.273z" fill="#fff"/><path d="M152.637 200L200 152.637h-47.363z" fill="#f72a25"/><path d="M200 47.363h-47.363v105.273H200z" fill="#fbbc04"/><path d="M152.637 152.637H47.363V200h105.273z" fill="#34a853"/><path d="M0 152.637v31.576A15.788 15.788 0 0 0 15.788 200h31.576v-47.363z" fill="#188038"/><path d="M200 47.363V15.788A15.79 15.79 0 0 0 184.212 0h-31.575v47.363z" fill="#1967d2"/><path d="M15.788 0A15.79 15.79 0 0 0 0 15.788v136.849h47.363V47.363h105.274V0z" fill="#4285f4"/><path d="M68.962 129.02c-3.939-2.653-6.657-6.543-8.138-11.67l9.131-3.76c.83 3.158 2.279 5.599 4.346 7.341 2.051 1.742 4.557 2.588 7.471 2.588 2.995 0 5.55-.911 7.699-2.718 2.148-1.823 3.223-4.134 3.223-6.934 0-2.865-1.139-5.208-3.402-7.031s-5.111-2.718-8.496-2.718h-5.273v-9.033h4.736c2.913 0 5.387-.781 7.389-2.376 2.002-1.579 2.995-3.743 2.995-6.494 0-2.441-.895-4.395-2.686-5.859s-4.053-2.197-6.803-2.197c-2.686 0-4.818.716-6.396 2.148s-2.767 3.255-3.451 5.273l-9.033-3.76c1.204-3.402 3.402-6.396 6.624-8.984s7.34-3.89 12.337-3.89c3.695 0 7.031.716 9.977 2.148s5.257 3.418 6.934 5.941c1.676 2.539 2.507 5.387 2.507 8.545 0 3.223-.781 5.941-2.327 8.187-1.546 2.23-3.467 3.955-5.729 5.143v.537a17.39 17.39 0 0 1 7.34 5.729c1.904 2.572 2.865 5.632 2.865 9.212s-.911 6.771-2.718 9.57c-1.823 2.799-4.329 5.013-7.52 6.624s-6.787 2.425-10.775 2.425c-4.622 0-8.887-1.318-12.826-3.988zm56.087-45.312l-10.026 7.243-5.013-7.601 17.985-12.972h6.901v61.198h-9.847z" fill="#1a73e8"/></svg></span>', 1653 ical: '<span class="atcb-icon-ical"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200.016"><path d="M132.829 7.699c0-4.248 4.199-7.699 9.391-7.699s9.391 3.451 9.391 7.699v33.724c0 4.248-4.199 7.699-9.391 7.699s-9.391-3.451-9.391-7.699zm-25.228 161.263c-.553 0-.993-2.327-.993-5.208s.439-5.208.993-5.208h25.7c.553 0 .993 2.327.993 5.208s-.439 5.208-.993 5.208zm-81.803-59.766c-.553 0-.993-2.327-.993-5.208s.439-5.208.993-5.208h25.7c.553 0 .993 2.327.993 5.208s-.439 5.208-.993 5.208zm40.902 0c-.553 0-.993-2.327-.993-5.208s.439-5.208.993-5.208h25.7c.553 0 .993 2.327.993 5.208s-.439 5.208-.993 5.208zm40.902 0c-.553 0-.993-2.327-.993-5.208s.439-5.208.993-5.208h25.7c.553 0 .993 2.327.993 5.208s-.439 5.208-.993 5.208zm40.918 0c-.553 0-.993-2.327-.993-5.208s.439-5.208.993-5.208h25.7c.553 0 .993 2.327.993 5.208s-.439 5.208-.993 5.208zM25.798 139.079c-.553 0-.993-2.327-.993-5.208s.439-5.208.993-5.208h25.7c.553 0 .993 2.327.993 5.208s-.439 5.208-.993 5.208zm40.902 0c-.553 0-.993-2.327-.993-5.208s.439-5.208.993-5.208h25.7c.553 0 .993 2.327.993 5.208s-.439 5.208-.993 5.208zm40.902 0c-.553 0-.993-2.327-.993-5.208s.439-5.208.993-5.208h25.7c.553 0 .993 2.327.993 5.208s-.439 5.208-.993 5.208zm40.918 0c-.553 0-.993-2.327-.993-5.208s.439-5.208.993-5.208h25.7c.553 0 .993 2.327.993 5.208s-.439 5.208-.993 5.208zM25.798 168.962c-.553 0-.993-2.327-.993-5.208s.439-5.208.993-5.208h25.7c.553 0 .993 2.327.993 5.208s-.439 5.208-.993 5.208zm40.902 0c-.553 0-.993-2.327-.993-5.208s.439-5.208.993-5.208h25.7c.553 0 .993 2.327.993 5.208s-.439 5.208-.993 5.208zM48.193 7.699C48.193 3.451 52.393 0 57.585 0s9.391 3.451 9.391 7.699v33.724c0 4.248-4.199 7.699-9.391 7.699s-9.391-3.451-9.391-7.699zM10.417 73.763h179.15V34.945c0-1.302-.537-2.49-1.4-3.369-.863-.863-2.051-1.4-3.369-1.4h-17.155c-2.881 0-5.208-2.327-5.208-5.208s2.327-5.208 5.208-5.208h17.171c4.183 0 7.975 1.709 10.726 4.46S200 30.762 200 34.945v44.043 105.843c0 4.183-1.709 7.975-4.46 10.726s-6.543 4.46-10.726 4.46H15.186c-4.183 0-7.975-1.709-10.726-4.46C1.709 192.79 0 188.997 0 184.814V78.971 34.945c0-4.183 1.709-7.975 4.46-10.726s6.543-4.46 10.726-4.46h18.343c2.881 0 5.208 2.327 5.208 5.208s-2.327 5.208-5.208 5.208H15.186c-1.302 0-2.49.537-3.369 1.4-.863.863-1.4 2.051-1.4 3.3
165369zm179.167 10.433H10.417v100.618c0 1.302.537 2.49 1.4 3.369.863.863 2.051 1.4 3.369 1.4h169.629c1.302 0 2.49-.537 3.369-1.4.863-.863 1.4-2.051 1.4-3.369zM82.08 30.176c-2.881 0-5.208-2.327-5.208-5.208s2.327-5.208 5.208-5.208h34.977c2.881 0 5.208 2.327 5.208 5.208s-2.327 5.208-5.208 5.208z"/></svg></span>', 1654 msteams: 1655 '<span class="atcb-icon-msteams"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 200 186.047"><path d="M195.349 39.535a20.93 20.93 0 1 1-41.86 0 20.93 20.93 0 1 1 41.86 0zm-55.847 30.233h51.66A8.84 8.84 0 0 1 200 78.605v47.056c0 17.938-14.541 32.479-32.479 32.479h0-.154c-17.938.003-32.481-14.537-32.484-32.474v-.005-51.274a4.62 4.62 0 0 1 4.619-4.619z" fill="#5059c9"/><path d="M149.614 69.767H64.34c-4.823.119-8.637 4.122-8.526 8.944v53.67c-.673 28.941 22.223 52.957 51.163 53.665 28.94-.708 51.836-24.725 51.163-53.665v-53.67c.112-4.823-3.703-8.825-8.526-8.944zm-10.079-39.535a30.233 30.233 0 0 1-60.465 0 30.233 30.233 0 0 1 60.465 0z" fill="#7b83eb"/><path opacity=".1" d="M111.628 69.767v75.209c-.023 3.449-2.113 6.547-5.302 7.86-1.015.43-2.107.651-3.209.651H59.907l-1.628-4.651c-1.628-5.337-2.459-10.885-2.465-16.465V78.698c-.112-4.815 3.697-8.811 8.512-8.93z"/><path opacity=".2" d="M106.977 69.767v79.86a8.241 8.241 0 0 1-.651 3.209c-1.313 3.189-4.412 5.279-7.86 5.302H62.093l-2.186-4.651a46.13 46.13 0 0 1-1.628-4.651 56.647 56.647 0 0 1-2.465-16.465V78.698c-.112-4.815 3.697-8.811 8.512-8.93z"/><path opacity=".2" d="M102.326 69.767v70.558a8.58 8.58 0 0 1-8.512 8.512H58.279a56.647 56.647 0 0 1-2.465-16.465V78.698c-.112-4.815 3.697-8.811 8.512-8.93z"/><path opacity=".1" d="M111.628 45.721v14.651l-2.326.093c-.791 0-1.535-.046-2.326-.093-1.57-.104-3.127-.353-4.651-.744a30.233 30.233 0 0 1-20.93-17.767 25.845 25.845 0 0 1-1.488-4.651h23.209c4.693.018 8.494 3.818 8.512 8.512z"/><use xlink:href="#B" opacity=".2" transform="scale(.08973306)"/><path d="M106.977 50.372v10c-1.57-.104-3.127-.353-4.651-.744a30.233 30.233 0 0 1-20.93-17.767h17.07c4.693.018 8.494 3.818 8.512 8.512zm0 19.395v70.558a8.58 8.58 0 0 1-8.512 8.512H58.279a56.647 56.647 0 0 1-2.465-16.465V78.698c-.112-4.815 3.697-8.811 8.512-8.93z" opacity=".2"/><path opacity=".2" d="M102.326 50.372v9.256a30.233 30.233 0 0 1-20.93-17.767h12.419c4.693.018 8.494 3.818 8.512 8.512z"/><linearGradient id="A" gradientUnits="userSpaceOnUse" x1="17.776" y1="35.199" x2="84.55" y2="150.848"><stop offset="0" stop-color="#5a62c3"/><stop offset=".5" stop-color="#4d55bd"/><stop offset="1" stop-color="#3940ab"/></linearGradient><path fill="url(#A)" d="M8.526 41.86H93.8a8.53 8.53 0 0 1 8.526 8.526v85.274a8.53 8.53 0 0 1-8.526 8.526H8.526A8.53 8.53 0 0 1 0 135.66V
165550.386a8.53 8.53 0 0 1 8.526-8.526z"/><path fill="#fff" d="M73.6 74.316H56.553v46.419h-10.86V74.316H28.726v-9.005H73.6z"/><defs><path id="B" d="M1192.167 561.355v111.442c-17.496-1.161-34.848-3.937-51.833-8.293a336.92 336.92 0 0 1-233.25-198.003h190.228c52.304.198 94.656 42.55 94.855 94.854z"/></defs></svg></span>', 1656 ms365: 1657 '<span class="atcb-icon-ms365"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 239.766"><path d="M200 219.785l-.021-.012V20.591L128.615 0 .322 48.172 0 48.234.016 192.257l43.78-17.134V57.943l84.819-20.279-.012 172.285L.088 192.257l128.515 47.456v.053l71.376-19.753v-.227z"/></svg></span>', 1658 outlookcom: 1659 '<span class="atcb-icon-outlookcom"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 175"><path d="M178.725 0H71.275A8.775 8.775 0 0 0 62.5 8.775v9.975l60.563 18.75L187.5 18.75V8.775A8.775 8.775 0 0 0 178.725 0z" fill="#0364b8"/><path d="M197.813 96.281c.915-2.878 2.187-5.855 2.187-8.781-.002-1.485-.795-2.857-1.491-3.26l-68.434-38.99a9.37 9.37 0 0 0-9.244-.519c-.312.154-.614.325-.906.512l-67.737 38.6-.025.013-.075.044a4.16 4.16 0 0 0-2.088 3.6c.541 2.971 1.272 5.904 2.188 8.781l71.825 52.532z" fill="#0a2767"/><path d="M150 18.75h-43.75L93.619 37.5l12.631 18.75L150 93.75h37.5v-37.5z" fill="#28a8ea"/><path d="M150 18.75h37.5v37.5H150z" fill="#50d9ff"/><path d="M150 93.75l-43.75-37.5H62.5v37.5l43.75 37.5 67.7 11.05z" fill="#0364b8"/><path d="M106.25 56.25v37.5H150v-37.5zM150 93.75v37.5h37.5v-37.5zm-87.5-75h43.75v37.5H62.5z" fill="#0078d4"/><path d="M62.5 93.75h43.75v37.5H62.5z" fill="#064a8c"/><path d="M126.188 145.113l-73.706-53.75 3.094-5.438 68.181 38.825a3.3 3.3 0 0 0 2.625-.075l68.331-38.937 3.1 5.431z" fill="#0a2767" opacity=".5"/><path d="M197.919 91.106l-.088.05-.019.013-67.738 38.588c-2.736 1.764-6.192 1.979-9.125.569l23.588 31.631 51.588 11.257v-.001c2.434-1.761 3.876-4.583 3.875-7.587V87.5c.001 1.488-.793 2.862-2.081 3.606z" fill="#1490df"/><path d="M200 165.625v-4.613l-62.394-35.55-7.531 4.294a9.356 9.356 0 0 1-9.125.569l23.588 31.631 51.588 11.231v.025a9.362 9.362 0 0 0 3.875-7.588z" opacity=".05"/><path d="M199.688 168.019l-68.394-38.956-1.219.688c-2.734 1.766-6.19 1.984-9.125.575l23.588 31.631 51.587 11.256v.001a9.38 9.38 0 0 0 3.562-5.187z" opacity=".1"/><path d="M51.455 90.721c-.733-.467-1.468-1.795-1.455-3.221v78.125c-.007 5.181 4.194 9.382 9.375 9.375h131.25c1.395-.015 2.614-.366 3.813-.813.638-.258 1.252-.652 1.687-.974z" fill="#28a8ea"/><path d="M112.5 141.669V39.581a8.356 8.356 0 0 0-8.331-8.331H62.687v46.6l-10.5 5.987-.031.012-.075.044A4.162 4.162 0 0 0 50 87.5v.031-.031V150h54.169a8.356 8.356 0 0 0 8.331-8.331z" opacity=".1"/><path d="M106.25 147.919V45.831a8.356 8.356 0 0 0-8.331-8.331H62.687v40.35l-10.5 5.987-.031.012-.075.044A4.162 4.162 0 0 0 50 87.5v.031-.031 68.75h47.919a8.356 8.356 0 0 0 8.331-8.331z" opacity=".2"/><path d="M106.25 135.419V45.831a8.356 8.356 0 0 0-8.331-8.331H62.687v40.35l-10.5 5.987-.031.012-.075.044A4.162 4.162 0 0 0 50 87.5v.031-.031 56.25h47.919a8.356 8.356 0 0 0 8.331-8.331z" opacity=".2"/><path d="M100 135.419V45.831a8.356 8.356 0 0 0-8.331-8.331H62.687v40.35l-10.5 5.987-.031.012-.075.044A4.162 4.162 0 0 0 50 87.5v.031-.031 56.25h41.669a8.356 8.356 0 0 0 8.331-8.331z" opacity=".2"/><path d="M8.331 37.5h83.337A8.331 8.331 0 0 1 100 45.831v83.338a8.331 8.331 0 0 1-8.331 8.331H8.331A8.331 8.331 0 0 1 0 129.169V45.831A8.331 8.331 0 0 1 8.331 37.5z" fill="#0078d4"/><path d="M24.169 71.675a26.131 26.131 0 0 1 10.263-11.337 31.031 31.031 0 0 1 16.313-4.087 28.856 28.856 0 0 1 15.081 3.875 25.875 25.875 0 0 1 9.988 10.831 34.981 34.981 0 0 1 3.5 15.938 36.881 36.881 0 0 1-3.606 16.662 26.494 26.494 0 0 1-10.281 11.213 30 30 0 0 1-15.656 3.981 29.556 29.556 0 0 1-15.425-3.919 26.275 26.275 0 0 1-10.112-10.85 34.119 34.119 0 0 1-3.544-15.744 37.844 37.844 0 0 1 3.481-16.563zm10.938 26.613a16.975 16.975 0 0 0 5.769 7.463 15.069 15.069 0 0 0 9.019 2.719 15.831 15.831 0 0 0 9.631-2.806 16.269 16.269 0 0 0 5.606-7.481 28.913 28.913 0 0 0 1.787-10.406 31.644 31.644 0 0 0-1.687-10.538 16.681 16.681 0 0 0-5.413-7.75 14.919 14.919 0 0 0-9.544-2.956 15.581 15.581 0 0 0-9.231 2.744 17.131 17.131 0 0 0-5.9 7.519 29.85 29.85 0 0 0-.044 21.5z" fill="#fff"/></svg></span>', 1660 yahoo: 1661 '<span class="atcb-icon-yahoo"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 177.803"><path d="M0 43.284h38.144l22.211 56.822 22.5-56.822h37.135L64.071 177.803H26.694l15.308-35.645L.001 43.284zm163.235 45.403H121.64L158.558 0 200 .002zm-30.699 8.488c12.762 0 23.108 10.346 23.108 23.106s-10.345 23.106-23.108 23.106a23.11 23.11 0 0 1-23.104-23.106 23.11 23.11 0 0 1 23.104-23.106z"/></svg></span>', 1662 atcb: '<svg version="1.1" viewBox="0 0 150 8.5002" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g transform="matrix(1.3333 0 0 -1.3333 -2427.5 1757.9)"><g transform="matrix(.22189 0 0 -.22189 1822.6 1374.6)" fill="#9a9a9a" style="paint-order:stroke markers fill;shape-inside:url(#rect2441);white-space:pre" aria-label="Add-to-Calendar-PRO.com"><path d="m-1.2773 253.99h12.148l7.9688 27.5h-9.4141l-1.0547-5.2734h-7.1094l-1.1328 5.2734h-9.0234zm8.7109 17.305-2.6172-12.031-2.6953 12.031z" style="paint-order:stroke markers fill"/><path d="m29.66 261.16q2.2656 0 3.9062 0.9375t2.6562 3.1055v-10.078l8.4375-1.25v27.617h-8.4375v-3.7109q-0.9375 2.0117-2.5586 3.0273-1.6211 0.9961-4.043 0.9961-2.0898 0-3.8672-0.83985-1.7773-0.85937-3.0859-2.2852-1.2891-1.4258-2.0312-3.2812-0.74219-1.875-0.74219-3.9062 0-2.1875 0.78125-4.082 0.80078-1.8945 2.1484-3.2812 1.3477-1.3867 3.1055-2.168 1.7773-0.80078 3.7305-0.80078zm6.5625 10.176q-0.03906-0.78125-0.37109-1.4844-0.33203-0.70312-0.87891-1.2109-0.52734-0.52735-1.2305-0.82032-0.70312-0.3125-1.4648-0.3125-0.85938 0-1.6016 0.33203-0.72266 0.3125-1.25 0.85938-0.52734 0.54687-0.83984 1.2695-0.29297 0.72266-0.29297 1.5234 0 0.85937 0.3125 1.582 0.33203 0.72266 0.87891 1.2695 0.54688 0.52734 1.2695 0.83984 0.72266 0.29297 1.5234 0.29297 0.83984 0 1.5625-0.33203 0.72266-0.33203 1.25-0.8789 0.52734-0.54688 0.82031-1.2695 0.3125-0.72265 0.3125-1.5039z" style="paint-order:stroke markers fill"/><path d="m57.551 261.16q2.2656 0 3.9062 0.9375t2.6562 3.1055v-10.078l8.4375-1.25v27.617h-8.4375v-3.7109q-0.9375 2.0117-2.5586 3.0273-1.6211 0.9961-4.043 0.9961-2.0898 0-3.8672-0.83985-1.7773-0.85937-3.0859-2.2852-1.2891-1.4258-2.0312-3.2812-0.74219-1.875-0.74219-3.9062 0-2.1875 0.78125-4.082 0.80078-1.8945 2.1484-3.2812 1.3477-1.3867 3.1055-2.168 1.7773-0.80078 3.7305-0.80078zm6.5625 10.176q-0.03906-0.78125-0.37109-1.4844-0.33203-0.70312-0.87891-1.2109-0.52734-0.52735-1.2305-0.82032-0.70312-0.3125-1.4648-0.3125-0.85938 0-1.6016 0.33203-0.72266 0.3125-1.25 0.85938-0.52734 0.54687-0.83984 1.2695-0.29297 0.72266-0.29297 1.5234 0 0.85937 0.3125 1.582 0.33203 0.72266 0.87891 1.2695 0.54688 0.52734 1.2695 0.83984 0.72266 0.29297 1.5234 0.29297 0.83984 0 1.5625-0.33203 0.72266-0.33203 1.25-0.8789 0.52734-0.54688 0.82031-1.2695 0.3125-0.72265 0.3125-1.5039z" style="paint-order:stroke markers fill"/><path d="m76.496 268.8h10.742v4.7266h-10.742z" style="paint-order:stroke markers fill"/><path d="m104.8 280.44q-2.7148 1.3672-6.0156 1.3672-1.6992 0-3.0273-0.54688-1.3281-0.5664-2.2461-1.6016-0.89844-1.0547-1.3672-2.5195-0.46875-1.4844-0.46875-3.3398l0.03906-7.3828h-2.5391v-4.9609h3.1641l3.7109-7.5781h4.0625v7.5781h4.6094v4.9609h-4.6094v6.7969q0 1.0547 0.68359 1.582t1.8555 0.52734q0.91797 0 1.8359-0.46875z" style="paint-order:stroke markers fill"/><path d="m114.62 271.45q0 1.0938 0.3125 1.9141 0.33204 0.82031 0.85938 1.3672 0.52734 0.54687 1.2109 0.82031 0.68359 0.27344 1.3867 0.27344 0.70312 0 1.3672-0.27344 0.6836-0.27344 1.2109-0.82031 0.54688-0.54688 0.85938-1.3672 0.33203-0.82031 0.33203-1.9141t-0.33203-1.9141q-0.3125-0.82031-0.85938-1.3476-0.52734-0.54688-1.2109-0.82032-0.66406-0.27343-1.3672-0.27343-0.70313 0-1.3867 0.27343-0.6836 0.27344-1.2109 0.82032-0.52734 0.52734-0.85938 1.3476-0.3125 0.82032-0.3125 1.9141zm-7.8125 0q0.0977-2.5195 1.0352-4.4336 0.95703-1.9141 2.5195-3.2226 1.5625-1.3086 3.6133-1.9727 2.0703-0.66406 4.3945-0.66406 2.5391 0 4.6484 0.76172 2.1094 0.76171 3.6328 2.1289 1.5234 1.3477 2.3633 3.2422 0.83985 1.8945 0.83985 4.1602 0 1.8359-0.48828 3.3203-0.46875 1.4844-1.2891 2.6367-0.82031 1.1328-1.9336 1.9726-1.1133 0.83985-2.4023 1.3867-1.2891 0.52735-2.6758 0.78125-1.3672 0.25391-2.7344 0.25391-2.5781 0-4.707-0.74219-2.1094-0.76172-3.6328-2.1289-1.5234-1.3672-2.3633-3.2617-0.82032-1.9141-0.82032-4.2188z" style="paint-order:stroke markers fill"/><path d="m132.73 268.8h10.742v4.7266h-10.742z" style="paint-order:stroke markers fill"/><path d="m162.41 274.89q0.6836 0 1.2695-0.0586 0.58594-0.0781 1.1524-0.21484 0.58593-0.13672 1.1914-0.35156 0.60547-0.21485 1.3477-0.50782l1.0938 6.3477q-3.4766 2.0117-7.5781 2.0117-5.8008 0-9.9414-3.9062-4.3359-4.0625-4.3945-10.488 0-3.125 1.0742-5.7617 1.0742-2.6367 2.9688-4.5312 1.8945-1.9141 4.4726-2.9688 2.5781-1.0742 5.5859-1.0742 4.2773 0 7.7734 1.9922l-1.0547 6.2109q-2.8711-1.0938-5.1953-1.0938-3.2617 0-4.9609 1.8945-1.6797 1.875-1.6797 5.293 0 1.6992 0.44922 3.0469 0.46875 1.3281 1.3477 2.2656 0.8789 0.91797 2.1484 1.4062 1.2891 0.48829 2.9297 0.48829z" style="paint-order:stroke markers fill"/><path d="m186.52 277.78q-0.9375 1.9531-2.5391 2.9883-1.6016 1.0352-4.0234 1.0352-1.9531 0-3.7305-0.78125-1.7774-0.80078-3.125-2.1875-1.3477-1.4062-2.1484-3.3203-0.80078-1.9141-0.80078-4.1602 0-2.2266 0.80078-4.1016 0.82031-1.875 2.168-3.2227 1.3672-1.3672 3.125-2.1094 1.7
1662773-0.76172 3.7109-0.76172 0.9961 0 1.9531 0.27343 0.97656 0.25391 1.8359 0.83985 0.8789 0.5664 1.582 1.4453 0.72265 0.87891 1.1914 2.1094v-4.668h8.3984v20.332h-8.3984zm0-6.4453q-0.0391-0.78125-0.37109-1.4844-0.33203-0.70312-0.87891-1.2109-0.52734-0.52735-1.2305-0.82032-0.70313-0.3125-1.4648-0.3125-0.85937 0-1.6016 0.33203-0.72266 0.3125-1.2695 0.85938-0.52735 0.52734-0.83985 1.25t-0.3125 1.543q0 0.85937 0.33204 1.6016 0.33203 0.72266 0.8789 1.25 0.56641 0.52734 1.2891 0.83984 0.72266 0.29297 1.5234 0.29297 0.82031 0 1.5234-0.3125 0.70312-0.3125 1.2305-0.83984 0.52734-0.52734 0.83984-1.2109 0.3125-0.70312 0.35156-1.4648z" style="paint-order:stroke markers fill"/><path d="m212.02 280.44q-2.7148 1.3672-6.0156 1.3672-1.6992 0-3.0273-0.54688-1.3281-0.5664-2.2461-1.6016-0.89843-1.0547-1.3672-2.5195-0.46875-1.4844-0.46875-3.3398v-18.672l8.4375-1.25v19.336q0 1.0742 0.68359 1.6016 0.68359 0.50781 1.8555 0.50781 0.89844 0 1.8359-0.46875z" style="paint-order:stroke markers fill"/><path d="m225.71 276.2q2.7344 0 5.8984-1.4062l0.97656 5.3711q-3.6328 1.6406-8.1641 1.6406-2.5195 0-4.6094-0.76172-2.0703-0.78125-3.5742-2.1484-1.4844-1.3867-2.3242-3.2812-0.82031-1.8945-0.82031-4.1211 0-2.3438 0.85937-4.2383 0.87891-1.9141 2.3828-3.2617 1.5039-1.3672 3.5156-2.0898 2.0117-0.74218 4.2969-0.74218 2.3047 0 4.043 0.78125 1.7383 0.76172 2.8906 2.1094 1.1719 1.3281 1.7383 3.125 0.58594 1.7774 0.58594 3.8086 0 0.27343-0.0195 0.52734t-0.0586 0.48828l-11.992 1.5625q0.46875 1.4062 1.5625 2.0312 1.0938 0.60547 2.8125 0.60547zm1.4844-7.6758q-0.6836-2.5-3.0469-2.5-0.74218 0-1.3281 0.29297t-0.9961 0.82031q-0.39062 0.50781-0.60546 1.2305-0.21485 0.70312-0.23438 1.5234z" style="paint-order:stroke markers fill"/><path d="m236.3 261.75h8.0469v4.082q1.1719-2.3633 3.0078-3.5156 1.8555-1.1523 4.3359-1.1523 1.9922 0 3.5156 0.74218 1.5234 0.74219 2.5586 2.1094 1.0547 1.3477 1.582 3.2617 0.54687 1.8945 0.54687 4.2188v10h-8.3984v-10.82q0-0.78125-0.15625-1.4453-0.13672-0.66406-0.46875-1.1328-0.3125-0.48828-0.80078-0.74219-0.48828-0.27343-1.1914-0.27343-0.85938 0-1.582 0.42968-0.72265 0.41016-1.2695 1.0156-0.52734 0.60547-0.85937 1.2695-0.33203 0.64453-0.42969 1.1133v10.586h-8.4375z" style="paint-order:stroke markers fill"/><path d="m272.59 261.16q2.2656 0 3.9062 0.9375 1.6406 0.9375 2.6562 3.1055v-10.078l8.4375-1.25v27.617h-8.4375v-3.7109q-0.9375 2.0117-2.5586 3.0273-1.6211 0.9961-4.043 0.9961-2.0898 0-3.8672-0.83985-1.7773-0.85937-3.0859-2.2852-1.2891-1.4258-2.0312-3.2812-0.74219-1.875-0.74219-3.9062 0-2.1875 0.78125-4.082 0.80078-1.8945 2.1484-3.2812 1.3476-1.3867 3.1055-2.168 1.7774-0.80078 3.7305-0.80078zm6.5625 10.176q-0.0391-0.78125-0.37109-1.4844-0.33203-0.70312-0.87891-1.2109-0.52734-0.52735-1.2305-0.82032-0.70312-0.3125-1.4648-0.3125-0.85937 0-1.6016 0.33203-0.72266 0.3125-1.25 0.85938-0.52735 0.54687-0.83985 1.2695-0.29296 0.72266-0.29296 1.5234 0 0.85937 0.3125 1.582 0.33203 0.72266 0.8789 1.2695 0.54688 0.52734 1.2695 0.83984 0.72266 0.29297 1.5234 0.29297 0.83984 0 1.5625-0.33203t1.25-0.8789q0.52734-0.54688 0.82031-1.2695 0.3125-0.72265 0.3125-1.5039z" style="paint-order:stroke markers fill"/><path d="m307.06 277.78q-0.9375 1.9531-2.5391 2.9883-1.6016 1.0352-4.0234 1.0352-1.9531 0-3.7305-0.78125-1.7773-0.80078-3.125-2.1875-1.3477-1.4062-2.1484-3.3203-0.80078-1.9141-0.80078-4.1602 0-2.2266 0.80078-4.1016 0.82032-1.875 2.168-3.2227 1.3672-1.3672 3.125-2.1094 1.7774-0.76172 3.7109-0.76172 0.99609 0 1.9531 0.27343 0.97657 0.25391 1.8359 0.83985 0.87891 0.5664 1.582 1.4453 0.72266 0.87891 1.1914 2.1094v-4.668h8.3984v20.332h-8.3984zm0-6.4453q-0.0391-0.78125-0.37109-1.4844-0.33204-0.70312-0.87891-1.2109-0.52734-0.52735-1.2305-0.82032-0.70312-0.3125-1.4648-0.3125-0.85938 0-1.6016 0.33203-0.72265 0.3125-1.2695 0.85938-0.52734 0.52734-0.83984 1.25t-0.3125 1.543q0 0.85937 0.33203 1.6016 0.33203 0.72266 0.87891 1.25 0.5664 0.52734 1.2891 0.83984 0.72266 0.29297 1.5234 0.29297 0.82031 0 1.5234-0.3125 0.70313-0.3125 1.2305-0.83984 0.52735-0.52734 0.83985-1.2109 0.3125-0.70312 0.35156-1.4648z" style="paint-order:stroke markers fill"/><path d="m319.5 261.75h8.4375v4.082q0.95703-2.4219 2.5391-3.5352 1.6016-1.1328 3.6719-1.1328l1.1719 6.543q-3.8086 0-5.6055 0.82031-1.7773 0.800
166278-1.7773 2.5781v10.391h-8.4375z" style="paint-order:stroke markers fill"/><path d="m337.22 268.8h10.742v4.7266h-10.742z" style="paint-order:stroke markers fill"/><path d="m352.43 253.99h9.9219q3.0859 0 5.4883 0.60547t4.043 1.7773q1.6406 1.1719 2.5 2.8711 0.85937 1.6797 0.85937 3.8477 0 2.1094-0.97656 3.7695-0.97656 1.6602-2.6758 2.832-1.6992 1.1524-3.9844 1.7774-2.2656 0.60547-4.8633 0.60547h-1.6797v9.4141h-8.6328zm10.352 12.539q2.0703 0 3.2422-0.85937 1.1719-0.85938 1.1719-2.5781 0-0.83984-0.35156-1.4648-0.33204-0.625-0.9375-1.0352-0.58594-0.42969-1.3867-0.64453t-1.7383-0.21484h-1.7188v6.7188q0.27343 0.0391 0.70312 0.0586t1.0156 0.0195z" style="paint-order:stroke markers fill"/><path d="m402.92 281.41q-2.5586 0.70313-3.6914 0.70313-7.2461 0-9.1016-6.6016l-1.0938-4.4531h-1.875v10.43h-8.75v-27.5h12.227q2.3828 0 4.4726 0.54687 2.0898 0.52735 3.6328 1.5625 1.5625 1.0156 2.4414 2.5195 0.89844 1.5039 0.89844 3.457 0 1.4453-0.37109 2.5976-0.35157 1.1328-1.0352 2.0508-0.68359 0.89844-1.6797 1.6016-0.97656 0.70312-2.2266 1.25l0.17578 0.95703q0.0586 0.33203 0.11719 0.5664 0.0586 0.23438 0.0977 0.39063l0.21484 0.83984q0.23437 0.74219 0.52734 1.2695 0.3125 0.50782 0.78125 0.83985 0.46875 0.3125 1.1524 0.46875 0.70312 0.13672 1.6992 0.13672 0.15625 0 0.48828-0.0391 0.35156-0.0391 0.89843-0.11718zm-8.8476-18.945q0-0.76172-0.33203-1.25-0.3125-0.50781-0.87891-0.78125-0.56641-0.29297-1.3281-0.39062-0.76172-0.11719-1.6406-0.11719h-2.7344v5.5078h1.5234l1.2891-0.0586q0.76171-0.0586 1.4844-0.19532 0.74219-0.15625 1.3281-0.46875 0.58593-0.33203 0.9375-0.85937 0.35156-0.54688 0.35156-1.3867z" style="paint-order:stroke markers fill"/><path d="m404.76 267.78q0-5.957 3.7891-10.039 4.043-4.3555 10.938-4.3555 3.3594 0 6.0742 1.0938 2.7148 1.0742 4.6289 2.9883t2.9492 4.5703q1.0352 2.6367 1.0352 5.7422 0 1.875-0.42968 3.6719-0.42969 1.7969-1.2891 3.3984-0.83984 1.582-2.0898 2.9297-1.25 1.3281-2.8906 2.3047-1.6211 0.95703-3.6328 1.4844-1.9922 0.54688-4.3555 0.54688-2.4219 0-4.4531-0.54688-2.0117-0.54687-3.6523-1.5234-1.6406-0.97657-2.8906-2.3242-1.2305-1.3477-2.0703-2.9297-0.82031-1.6016-1.25-3.3789-0.41016-1.7773-0.41016-3.6328zm14.727 7.0703q1.543 0 2.6172-0.64453 1.0938-0.66406 1.7774-1.6797 0.70312-1.0352 1.0156-2.3047 0.33203-1.2695 0.33203-2.4805 0-1.582-0.41015-2.8906-0.39063-1.3281-1.1524-2.2852-0.74219-0.97657-1.8164-1.543-1.0547-0.56641-2.3633-0.625-1.5625 0.0391-2.6562 0.70312-1.0938 0.66407-1.7969 1.7188-0.68359 1.0352-1.0156 2.3438-0.3125 1.2891-0.3125 2.5781 0 1.5234 0.39063 2.832 0.41016 1.2891 1.1523 2.2461 0.76172 0.9375 1.8359 1.4844 1.0742 0.54687 2.4023 0.54687z" style="paint-order:stroke markers fill"/><path d="m436.18 279.2q0-0.58594 0.2474-1.0807 0.26042-0.49479 0.67708-0.84636 0.41667-0.35156 0.95053-0.54687 0.54687-0.20834 1.1198-0.20834 0.59896 0 1.1458 0.22136 0.54688 0.20833 0.95053 0.58594 0.41666 0.36458 0.65104 0.84635 0.2474 0.48177 0.2474 1.0286 0 0.61198-0.26042 1.1068-0.2474 0.49479-0.66407 0.85938-0.41666 0.35156-0.96354 0.54687-0.53385 0.19531-1.1068 0.19531-0.625 0-1.1719-0.20833-0.54687-0.20833-0.95052-0.57292-0.40365-0.36458-0.63802-0.85937-0.23438-0.49479-0.23438-1.0677z" style="paint-order:stroke markers fill"/><path d="m454.38 272.56q-1.4062-0.4427-2.6042-0.4427-0.61198 0-1.1068 0.19531-0.48177 0.19531-0.83333 0.54687-0.33855 0.35157-0.53386 0.85938-0.18229 0.49479-0.18229 1.1068t0.18229 1.1198q0.19531 0.49479 0.54688 0.85937 0.35156 0.36459 0.84635 0.5599 0.50782 0.19531 1.1328 0.19531 1.1849 0 2.5-0.44271l0.52083 3.724q-1.0026 0.52083-1.9661 0.6901-0.95053 0.16927-2.0703 0.16927-1.5755 0-2.9036-0.49479t-2.2917-1.3932-1.5104-2.1615q-0.53385-1.276-0.53385-2.8255t0.58594-2.8125q0.58593-1.276 1.5755-2.1745 1.0026-0.91146 2.3177-1.4062 1.3151-0.4948 2.7604-0.4948 0.63802 0 1.1458 0.0521 0.52084 0.0391 0.98959 0.16927 0.46875 0.11718 0.9375 0.32552 0.46875 0.20833 1.0156 0.53385z" style="paint-order:stroke markers fill"/><path d="m461.18 274.8q0 0.72917 0.20833 1.276 0.22136 0.54688 0.57292 0.91146 0.35157 0.36459 0.80729 0.54688 0.45573 0.18229 0.92449 0.18229 0.46875 0 0.91145-0.18229 0.45573-0.18229 0.8073-0.54688 0.36458-0.36458 0.57291-0.91146 0.22136-0.54687 0.22136-1.276t-0.22136-1.276q-0.20833-0.54688-0.57291-0.89844-0.35157-0.36458-0.8073-0.54688-0.4427-0.18229-0.91145-0.18229-0.46876 0-0.92449 0.18229-0.45572 0.1823-0.80729 0.54688-0.35156 0.35156-0.57292 0.89844-0.20833 0.54687-0.20833 1.276zm-5.2083 0q0.0651-1.6797 0.69011-2.9557 0.63802-1.276 1.6797-2.1484 1.0417-0.8724 2.4088-1.3151 1.3802-0.44271 2.9297-0.44271 1.6927 0 3.099 0.50782 1.4062 0.50781 2.4219 1.4193 1.0156 0.89844 1.5755 2.1615 0.55989 1.263 0.55989 2.7734 0 1.224-0.32552 2.2136-0.3125 0.98958-0.85937 1.7578-0.54688 0.75521-1.2891 1.3151-0.74219 0.55989-1.6016 0.92448-0.85938 0.35156-1.7839 0.52083-0.91146 0.16927-1.8229 0.16927-1.7188 0-3.138-0.49479-1.4062-0.50781-2.4219-1.4193t-1.5755-2.1745q-0.54688-1.276-0.54688-2.8125z" style="paint-order:stroke markers fill"/><path d="m488.16 271.26q0.54688-1.6536 1.7578-2.487 1.2109-0.83334 3.138-0.83334 1.0938 0 2.0182 0.50782 0.92448 0.50781 1.6016 1.4193 0.67708 0.91146 1.0547 2.1745 0.3776 1.263 0.3776 2.7865v6.6667h-5.625v-6.6667q0-0.55989-0.13021-1.0286-0.11718-0.48178-0.35156-0.83334-0.23437-0.35156-0.57292-0.54687-0.33854-0.19532-0.76823-0.19532-0.55989 0-0.96354 0.26042-0.40364 0.2474-0.66406 0.63802-0.26042 0.37761-0.39063 0.84636-0.11718 0.45573-0.11718 0.85937v6.6667h-5.5729v-6.6667q0-0.54687-0.13021-1.0156-0.11718-0.48178-0.36458-0.83334-0.23438-0.35156-0.58594-0.54687-0.35156-0.20834-0.79427-0.20834-0.48177 0-0.8724 0.19532-0.3776 0.19531-0.65104 0.54687-0.27344 0.33854-0.42969 0.79427-0.14323 0.45573-0.15625 0.96355v6.7708h-5.625v-13.164h5.625v2.7214q0.74219-1.6536 1.875-2.3828 1.1458-0.72917 2.6823-0.72917 0.74219 0 1.4323 0.19532 0.69011 0.18229 1.2891 0.58593 0.61198 0.40365 1.0938 1.0417 0.49479 0.625 0.82031 1.4974z" style="paint-order:stroke markers fill"/></g></g></svg>', 1663 close: 1664 '<span class="atcb-icon-close"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><path d="M2.321 13.529a7.927 7.927 0 0 1 0-11.208 7.927 7.927 0 0 1 11.208 0l86.471 86.471L186.47 2.321a7.927 7.927 0 0 1 11.209 0 7.927 7.927 0 0 1 0 11.208l-86.474 86.469 86.472 86.473a7.927 7.927 0 0 1-11.209 11.208l-86.471-86.471-86.469 86.471a7.927 7.927 0 0 1-11.208-11.208l86.471-86.473z"/></svg></span>', 1665 browser: 1666 '<span class="atcb-icon-browser"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 245.657"><path d="M117.011 163.676q-6.283 6.218-13.623 12.419l6.087-1.009a80.373 80.373 0 0 0 11.524-3.255l.7 1.042 1.628 2.067h0 0a26.693 26.693 0 0 0 3.467 3.25
16665 89.992 89.992 0 0 1-15.837 4.753 95.592 95.592 0 0 1-37.159 0 87.046 87.046 0 0 1-17.253-5.322.423.423 0 0 1-.228-.114 101.077 101.077 0 0 1-15.625-8.415 88.56 88.56 0 0 1-13.672-11.214 85.761 85.761 0 0 1-11.214-13.64 97.317 97.317 0 0 1-8.545-15.658 90.806 90.806 0 0 1-5.436-17.546 95.592 95.592 0 0 1 0-37.159 86.037 86.037 0 0 1 5.339-17.253.537.537 0 0 1 .098-.228 98.212 98.212 0 0 1 8.545-15.707 87.893 87.893 0 0 1 11.214-13.656 84.947 84.947 0 0 1 13.672-11.231A97.17 97.17 0 0 1 56.43 7.259a88.739 88.739 0 0 1 17.448-5.436 95.592 95.592 0 0 1 37.159 0 87.714 87.714 0 0 1 17.253 5.322.456.456 0 0 1 .212.114 100.507 100.507 0 0 1 15.756 8.545 88.56 88.56 0 0 1 13.623 11.198 85.077 85.077 0 0 1 11.214 13.688 94.713 94.713 0 0 1 8.545 15.739 88.739 88.739 0 0 1 5.436 17.481l.195.977-8.822-2.49a76.499 76.499 0 0 0-4.232-12.744 88.251 88.251 0 0 0-4.671-9.375H138.48a106.562 106.562 0 0 1 6.836 13.819l-10.026-2.702a106.985 106.985 0 0 0-6.283-11.117H96.454v5.55l-.993.358a21.941 21.941 0 0 0-7.097 4.362V50.245H55.812q-12.484 19.385-14.03 38.152H83.4q1.628 4.02 3.402 8.138H41.7c.505 12.81 4.883 25.505 12.826 38.152h33.888v-34.49l8.138 17.904v16.553h7.748l3.727 8.138H96.503v28.5a201.567 201.567 0 0 0 17.139-15.707q1.709 4.053 3.369 8.138zm69.761-4.167a7.552 7.552 0 0 1-1.904 1.286h-.13a6.738 6.738 0 0 1-7.097-.977l-18.881-16.016-6.511 15.902a21.045 21.045 0 0 1-1.937 3.662 14.812 14.812 0 0 1-2.458 2.865 7.78 7.78 0 0 1-12.207-1.335 15.105 15.105 0 0 1-1.497-2.653c-11.231-28.467-26.465-56.805-37.859-85.289a5.062 5.062 0 0 1 5.68-6.966c27.296 5.046 62.664 16.586 90.416 23.943 8.627 2.279 10.026 9.88 3.662 15.772a19.874 19.874 0 0 1-3.255 2.474c-4.883 2.767-9.766 5.973-14.649 8.903l18.799 16.114a6.917 6.917 0 0 1 1.628 2.051v.13a6.966 6.966 0 0 1 .635 2.393h0a6.934 6.934 0 0 1-.26 2.507 7.145 7.145 0 0 1-1.172 2.262 153.894 153.894 0 0 1-11.003 12.972zm-4.883-6.25l9.099-10.677c-4.004-3.434-21.159-16.748-22.933-19.955a3.923 3.923 0 0 1 1.351-5.29c5.957-3.255 13.607-7.91 19.255-11.67a13.64 13.64 0 0 0 1.986-1.449 7.194 7.194 0 0 0 1.221-1.416l.26-.488-.505-.293a6.38 6.38 0 0 0-1.237-.423l-84.589-22.494 35.531 79.982a7.813 7.813 0 0 0 .619 1.139l.358.472.456-.326a7.341 7.341 0 0 0 1.188-1.449 12.224 12.224 0 0 0 1.107-2.165c2.653-6.511 5.68-15.414 8.789-21.436l.374-.521a3.906 3.906 0 0 1 5.485-.439l22.201 18.832zM81.594 176.095a171.814 171.814 0 0 1-31.348-33.334h-25.57A83.824 83.824 0 0 0 45.2 162.292a85.956 85.956 0 0 0 14.47 7.813.22.22 0 0 0 .179.114 79.966 79.966 0 0 0 15.69 4.883 106.008 106.008 0 0 0 6.104 1.009zm-62.241-41.44h25.733a82.359 82.359 0 0 1-11.394-38.152H8.138a90.741 90.741 0 0 0 1.628 12.923 78.566 78.566 0 0 0 4.883 15.902 88.153 88.153 0 0 0 4.655 9.375zM8.138 88.397h25.635A88.511 88.511 0 0 1 46.42 50.245H19.353a88.153 88.153 0 0 0-4.704 9.375s0 .114-.114.163A81.236 81.236 0 0 0 9.652 75.49a83.759 83.759 0 0 0-1.628 12.907zm16.488-46.241h27.003A191.606 191.606 0 0 1 82.131 8.708c-2.262.277-4.492.602-6.641 1.058a78.713 78.713 0 0 0-15.87 4.883 89.911 89.911 0 0 0-14.47 7.813 83.824 83.824 0 0 0-20.525 19.532h0zm78.127-33.448a186.577 186.577 0 0 1 30.518 33.448h27.019a79.152 79.152 0 0 0-8.138-9.375 81.073 81.073 0 0 0-12.419-10.205 86.705 86.705 0 0 0-14.405-7.829s-.098 0-.163-.098a79.999 79.999 0 0 0-15.69-4.883c-2.214-.439-4.443-.781-6.657-1.058h0zm-6.25 5.274v28.175h26.84a188.286 188.286 0 0 0-26.84-28.175zm-8.138 157.279v-28.5H60.223a171.993 171.993 0 0 0 28.24 28.5zm0-129.105V13.981a189.295 189.295 0 0 0-26.807 28.175z"/></svg></span>', 1667}; 1668 1669// INITIALIZE THE SCRIPT AND FUNCTIONALITY 1670function atcb_init() { 1671 // let's get started 1672 if (!atcbConsoleInit) { 1673 console.log('Add to Calendar Button Script initialized (version ' + atcbVersion + ')'); 1674 console.log('See https://github.com/add2cal/add-to-calendar-button for details'); 1675 atcbConsoleInit = true; 1676 } 1677 // abort if not in a browser 1678 if (!isBrowser()) { 1679 console.error('no further initialization due to wrong environment (no browser)'); 1680 return; 1681 } 1682 // get all placeholders 1683 const atcButtons = document.querySelectorAll('.atcb'); 1684 // if there are some, move on 1685 if (atcButtons.length > 0) { 1686 // get the amount of already initialized ones first 1687 const atcButtonsInitialized = document.querySelectorAll('.atcb-initialized'); 1688 // generate the buttons one by one 1689 for (let i = 0; i < atcButtons.length; i++) { 1690 // skip already initialized ones 1691 if (atcButtons[parseInt(i)].classList.contains('atcb-initialized')) { 1692 continue; 1693 } 1694 // get JSON from HTML block, but remove real code line breaks before parsing. 1695 // use <br> or \n explicitely in the description to create a line break. 1696 const atcbJsonInput = (function () { 1697 try { 1698 return JSON.parse( 1699 atcb_secure_content(atcButtons[parseInt(i)].innerHTML.replace(/(\r\n|\n|\r)/g, ''), false) 1700 ); 1701 } catch (e) { 1702 console.error( 1703 'Add to Calendar Button generation failed: JSON content provided, but badly formatted (in doubt, try some tool like https://jsonformatter.org/ to validate).\r\nError message: ' + 1704 e 1705 ); 1706 return ''; 1707 } 1708 })(); 1709 if (atcbJsonInput === '') { 1710 continue; 1711 } 1712 // rewrite config for backwards compatibility 1713 const atcbJsonInputPatched = atcb_patch_config(atcbJsonInput); 1714 // check, if all required data is available 1715 if (atcb_check_required(atcbJsonInputPatched)) { 1716 // Rewrite dynamic dates, standardize line breaks and transform urls in the description 1717 const atcbConfig = atcb_decorate_data(atcbJsonInputPatched); 1718 // set identifier 1719 if (atcbConfig.identifier == null || atcbConfig.identifier == '') { 1720 atcbConfig.identifier = 'atcb-btn-' + (i + atcButtonsInitialized.length + 1); 1721 } 1722 // validate the config (JSON iput) ... 1723 if (atcb_validate(atcbConfig)) { 1724 // ... and generate the button on success 1725 atcb_generate(atcButtons[parseInt(i)], atcbConfig); 1726 } 1727 } 1728 } 1729 } 1730} 1731 1732// BACKWARDS COMPATIBILITY REWRITE 1733function atcb_patch_config(configData) { 1734 // you can remove this, if you did not use this script before v1.10.0 1735 // adjusts any old schema.org structure 1736 if (configData.event != null) { 1737 Object.keys(configData.event).forEach((key) => { 1738 // move entries one level up, but skip schema types 1739 if (key.charAt(0) !== '@') { 1740 configData[`${key}`] = configData.event[`${key}`]; 1741 } 1742 }); 1743 delete configData.event; 1744 } 1745 // you can remove this, if you did not use this script before v1.4.0 1746 // adjust deprecated config options 1747 const keyChanges = { 1748 title: 'name', 1749 dateStart: 'startDate', 1750 dateEnd: 'endDate', 1751 timeStart: 'startTime', 1752 timeEnd: 'endTime', 1753 };
1754 Object.keys(keyChanges).forEach((key) => { 1755 if (configData[keyChanges[`${key}`]] == null && configData[`${key}`] != null) { 1756 configData[keyChanges[`${key}`]] = configData[`${key}`]; 1757 } 1758 }); 1759 return configData; 1760} 1761 1762// CLEAN DATA BEFORE FURTHER VALIDATION (CONSIDERING SPECIAL RULES AND SCHEMES) 1763function atcb_decorate_data(data) { 1764 const now = new Date(); 1765 // format RRULE 1766 if (data.recurrence != null && data.recurrence != '') { 1767 // remove spaces and force upper case 1768 data.recurrence = data.recurrence.replace(/\s+/g, '').toUpperCase(); 1769 // pre-validate 1770 if (!/^(RRULE:[\w=;,:+-/\\]+|daily|weekly|monthly|yearly)$/im.test(data.recurrence)) { 1771 data.recurrence = '!wrong rrule format!'; 1772 } else { 1773 // check if RRULE already 1774 if (/^RRULE:/i.test(data.recurrence)) { 1775 // draw easy rules from RRULE if possible 1776 const rruleParts = data.recurrence.substr(6).split(';'); 1777 const rruleObj = new Object(); 1778 rruleParts.forEach(function (rule) { 1779 rruleObj[rule.split('=')[0]] = rule.split('=')[1]; 1780 }); 1781 data.recurrence_until = rruleObj.UNTIL ? rruleObj.UNTIL : ''; 1782 data.recurrence_count = rruleObj.COUNT ? rruleObj.COUNT : ''; 1783 data.recurrence_byDay = rruleObj.BYDAY ? rruleObj.BYDAY : ''; 1784 data.recurrence_byMonth = rruleObj.BYMONTH ? rruleObj.BYMONTH : ''; 1785 data.recurrence_byMonthDay = rruleObj.BYMONTHDAY ? rruleObj.BYMONTHDAY : ''; 1786 data.recurrence_interval = rruleObj.INTERVAL ? rruleObj.INTERVAL : 1; 1787 data.recurrence_frequency = rruleObj.FREQ ? rruleObj.FREQ : ''; 1788 } else { 1789 // set interval if not given 1790 if (data.recurrence_interval == null || data.recurrence_interval == '') { 1791 data.recurrence_interval = 1; 1792 } 1793 // set weekstart if not given 1794 if ( 1795 data.recurrence_weekstart == null || 1796 (data.recurrence_weekstart == '') | (data.recurrence_weekstart.length > 2) 1797 ) { 1798 data.recurrence_weekstart = 'MO'; 1799 } 1800 // save frequency before overriding the main recurrence data 1801 data.recurrence_frequency = data.recurrence; 1802 // generate the RRULE from easy rules 1803 data.recurrence = 1804 'RRULE:FREQ=' + 1805 data.recurrence + 1806 ';WKST=' + 1807 data.recurrence_weekstart + 1808 ';INTERVAL=' + 1809 data.recurrence_interval; 1810 // TODO: If "until" is given, translate it into a "count" and remove the "until" (here and in the above block). This would be way more stable! 1811 if (data.recurrence_until != null && data.recurrence_until != '') { 1812 if (data.endTime != null && data.endTime != '') { 1813 data.recurrence = 1814 data.recurrence + 1815 ';UNTIL=' + 1816 data.recurrence_until.replace(/-/g, '').slice(0, 8) + 1817 'T' + 1818 data.endTime.replace(':', '') + 1819 '00'; 1820 } else { 1821 data.recurrence = 1822 data.recurrence + ';UNTIL=' + data.recurrence_until.replace(/-/g, '').slice(0, 8); 1823 } 1824 } 1825 if (data.recurrence_count != null && data.recurrence_count != '') { 1826 data.recurrence = data.recurrence + ';COUNT=' + data.recurrence_count; 1827 } 1828 if (data.recurrence_byDay != null && data.recurrence_byDay != '') { 1829 data.recurrence = data.recurrence + ';BYDAY=' + data.recurrence_byDay; 1830 } 1831 if (data.recurrence_byMonth != null && data.recurrence_byMonth != '') { 1832 data.recurrence = data.recurrence + ';BYMONTH=' + data.recurrence_byMonth; 1833 } 1834 if (data.recurrence_byMonthDay != null && data.recurrence_byMonthDay != '') { 1835 data.recurrence = data.recurrence + ';BYMONTHDAY=' + data.recurrence_byMonthDay; 1836 } 1837 } 1838 } 1839 } 1840 // cleanup options, standardizing names and splitting off custom labels 1841 data.optionLabels = []; 1842 for (let i = 0; i < data.options.length; i++) {
1843 let cleanOption = data.options[`${i}`].split('|'); 1844 data.options[`${i}`] = cleanOption[0].toLowerCase().replace('microsoft', 'ms').replace('.', ''); 1845 if (cleanOption[1] != null) { 1846 data.optionLabels[`${i}`] = cleanOption[1]; 1847 } else { 1848 data.optionLabels[`${i}`] = ''; 1849 } 1850 } 1851 // remove unsupported options 1852 // for iOS, we remove iCal (further down) and force the Apple option (if it is not there, but iCal is) 1853 if (isiOS() && data.options.includes('ical') && !data.options.includes('apple')) { 1854 data.options.push('apple'); 1855 } 1856 // next, iterrate over the options 1857 for (let i = 0; i < data.options.length; i++) { 1858 // remove iCal for iOS as mentioned above (and potentially others) 1859 if (isiOS() && atcbiOSInvalidOptions.includes(data.options[`${i}`])) { 1860 data.options.splice(i, 1); 1861 if (data.optionLabels[`${i}`] != null) { 1862 delete data.optionLabels[`${i}`]; 1863 } 1864 continue; 1865 } 1866 // in the recurrence case, we strip out all options, which do not support it 1867 if (data.recurrence != null && data.recurrence != '') { 1868 if (!atcbValidRecurrOptions.includes(data.options[`${i}`])) { 1869 data.options.splice(i, 1); 1870 if (data.optionLabels[`${i}`] != null) { 1871 delete data.optionLabels[`${i}`]; 1872 } 1873 continue; 1874 } 1875 // also skip Apple and iCal for rrules with "until" 1876 if ( 1877 data.recurrence_until != null && 1878 data.recurrence_until != '' && 1879 (data.options[`${i}`] == 'apple' || data.options[`${i}`] == 'ical') 1880 ) { 1881 data.options.splice(i, 1); 1882 if (data.optionLabels[`${i}`] != null) { 1883 delete data.optionLabels[`${i}`]; 1884 } 1885 } 1886 } 1887 } 1888 // cleanup different date-time formats 1889 data = atcb_date_cleanup(data); 1890 // calculate the real date values in case that there are some special rules included (e.g. adding days dynamically) 1891 data.startDate = atcb_date_calculation(data.startDate); 1892 data.endDate = atcb_date_calculation(data.endDate); 1893 // set default listStyle 1894 if (data.listStyle == null || data.listStyle == '') { 1895 data.listStyle = 'dropdown'; 1896 } 1897 // force click trigger on modal style 1898 if (data.listStyle === 'modal') { 1899 data.trigger = 'click'; 1900 } 1901 // set button style and force click on styles, where the dropdown is not attached to the button 1902 if (data.buttonStyle != null && data.buttonStyle != '' && data.buttonStyle != 'default') { 1903 if (data.buttonStyle == 'round' || data.buttonStyle == 'text') { 1904 data.trigger = 'click'; 1905 } 1906 } else { 1907 data.buttonStyle = ''; 1908 } 1909 // set size 1910 if (data.size != null && data.size != '' && data.size >= 0 && data.size < 11) { 1911 data.size = 10 + parseInt(data.size); 1912 } else { 1913 data.size = 16; 1914 } 1915 // set sequence 1916 if (data.sequence == null || data.sequence == '') { 1917 data.sequence = 0; 1918 } 1919 // set UID 1920 if (data.uid == null || data.uid == '') { 1921 data.uid = atcb_generate_uuid(); 1922 } 1923 // set created date 1924 if (data.created == null || data.created == '') { 1925 data.created = atcb_format_datetime(now, 'clean', true); 1926 } 1927 // set updated date 1928 if (data.updated == null || data.updated == '') { 1929 data.updated = atcb_format_datetime(now, 'clean', true); 1930 } 1931 // set status 1932 if (data.status == null || data.status == '') { 1933 data.status = 'CONFIRMED'; 1934 } else { 1935 data.status = data.status.toUpperCase(); 1936 } 1937 // determine dark mode 1938 if (data.lightMode == null || data.lightMode == '') { 1939 data.lightMode = 'light'; 1940 } else if (data.lightMode != null && data.lightMode != '') { 1941 const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); 1942 switch (data.lightMode) { 1943 case 'system': 1944 if (prefersDarkScheme.matches) { 1945 data.lightMode = 'dark'; 1946 } else { 1947 data.lightMode = 'light'; 1948 } 1949 break; 1950 case 'bodyScheme': 1951 case 'dark': 1952 break; 1953 default: 1954 data.lightMode = 'light'; 1955 break; 1956 } 1957 } 1958 // set language if not set 1959 if (data.language == null || data.language == '') { 1960 data.language = 'en'; 1961 }
1962 // set right-to-left for relevant languages 1963 if (data.language == 'ar') { 1964 data.rtl = true; 1965 } else { 1966 data.rtl = false; 1967 } 1968 // decorate description 1969 if (data.description != null && data.description != '') { 1970 // store a clean description copy without the URL magic for iCal 1971 data.descriptionHtmlFree = atcb_rewrite_html_elements(data.description, true); 1972 // ...and transform pseudo elements for the regular one 1973 data.description = atcb_rewrite_html_elements(data.description); 1974 } 1975 return data; 1976} 1977 1978// CHECK FOR REQUIRED FIELDS 1979function atcb_check_required(data) { 1980 // check for at least 1 option 1981 if (data.options == null || data.options.length < 1) { 1982 console.error('Add to Calendar Button generation failed: no valid options set'); 1983 return false; 1984 } 1985 // check for min required data (without "options") 1986 const requiredField = ['name', 'startDate']; 1987 return requiredField.every(function (field) { 1988 if (data[`${field}`] == null || data[`${field}`] == '') { 1989 console.error('Add to Calendar Button generation failed: required setting missing [' + field + ']'); 1990 return false; 1991 } 1992 return true; 1993 }); 1994} 1995 1996// CALCULATE AND CLEAN UP THE ACTUAL DATES 1997function atcb_date_cleanup(data) { 1998 // set endDate = startDate, if not provided 1999 if ((data.endDate == null || data.endDate == '') && data.startDate != null) { 2000 data.endDate = data.startDate; 2001 } 2002 // parse date+time format (unofficial alternative to the main implementation) 2003 const endpoints = ['start', 'end']; 2004 endpoints.forEach(function (point) { 2005 if (data[point + 'Date'] != null) { 2006 // remove any milliseconds information 2007 data[point + 'Date'] = data[point + 'Date'].replace(/\.\d{3}/, '').replace('Z', ''); 2008 // identify a possible time information within the date string 2009 const tmpSplitStartDate = data[point + 'Date'].split('T'); 2010 if (tmpSplitStartDate[1] != null) { 2011 data[point + 'Date'] = tmpSplitStartDate[0]; 2012 data[point + 'Time'] = tmpSplitStartDate[1]; 2013 } 2014 } 2015 // remove any seconds from time information 2016 if (data[point + 'Time'] != null && data[point + 'Time'].length === 8) { 2017 const timeStr = data[point + 'Time']; 2018 data[point + 'Time'] = timeStr.substring(0, timeStr.length - 3); 2019 } 2020 // update time zone, if special case set to go for the user's browser 2021 if (data.timeZone == 'currentBrowser') { 2022 data.timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; 2023 } 2024 }); 2025 return data; 2026} 2027 2028function atcb_date_calculation(dateString) { 2029 // replace "today" with the current date first 2030 const today = new Date(); 2031 const todayString = today.getUTCMonth() + 1 + '-' + today.getUTCDate() + '-' + today.getUTCFullYear(); 2032 dateString = dateString.replace(/today/gi, todayString); 2033 // check for any dynamic additions and adjust 2034 const dateStringParts = dateString.split('+'); 2035 const dateParts = dateStringParts[0].split('-'); 2036 const newDate = (function () { 2037 // backwards compatibility for version <1.5.0 2038 if (dateParts[0].length < 4) { 2039 return new Date(dateParts[2], dateParts[0] - 1, dateParts[1]); 2040 } 2041 return new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); 2042 })(); 2043 if (dateStringParts[1] != null && dateStringParts[1] > 0) { 2044 newDate.setDate(newDate.getDate() + parseInt(dateStringParts[1])); 2045 } 2046 return ( 2047 newDate.getFullYear() + 2048 '-' + 2049 ((newDate.getMonth() + 1 < 10 ? '0' : '') + (newDate.getMonth() + 1)) + 2050 '-' + 2051 (newDate.getDate() < 10 ? '0' : '') + 2052 newDate.getDate() 2053 ); 2054} 2055 2056// VALIDATE THE INPUT DATA 2057function atcb_validate(data) { 2058 // validate prefix 2059 if (data.identifier != null && data.identifier != '') { 2060 if (!/^[\w-]+$/.test(data.identifier)) { 2061 data.identifier = ''; 2062 console.error('Add to Calendar Button generation: identifier invalid - using auto numbers instead'); 2063 } 2064 } 2065 const msgPrefix = 'Add to Calendar Button generation (' + data.identifier + ')'; 2066 // validate explicit ics file 2067 if (data.icsFile != null && data.icsFile != '') { 2068 if ( 2069 !atcb_secure_url(data.icsFile, false) || 2070 !/\.ics$/.test(data.icsFile) || 2071 !data.icsFile.startsWith('https://') 2072 ) { 2073 console.error(msgPrefix + ' failed: explicit ics file path not val
2073id'); 2074 return false; 2075 } 2076 } 2077 // validate organizer 2078 if (data.organizer != null && data.organizer != '') { 2079 const organizerParts = data.organizer.split('|'); 2080 if ( 2081 organizerParts.length != 2 || 2082 organizerParts[0].length > 50 || 2083 organizerParts[1].length > 80 || 2084 !atcb_validEmail(organizerParts[1]) 2085 ) { 2086 console.error( 2087 msgPrefix + ' failed: organizer needs to match the schema "NAME|EMAIL" with a valid email address' 2088 ); 2089 return false; 2090 } 2091 } 2092 // validate UID (must have less then 255 characters and only allowes for ; see ) 2093 if (!/^(\w|-){1,254}$/.test(data.uid)) { 2094 console.error( 2095 msgPrefix + 2096 ': UID not valid. May only contain alpha, digits, and dashes; and be less than 255 characters' 2097 ); 2098 return false; 2099 } 2100 // validate UID for the recommended form, which is not forced, but show throw a warning 2101 if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(data.uid)) { 2102 console.warn( 2103 msgPrefix + 2104 ': UID is strictly recommended to be a hex-encoded random Universally Unique Identifier (UUID)!' 2105 ); 2106 data.sequence = 0; 2107 } 2108 // validate sequence number if given and set it 0 if not 2109 if (!/^\d+$/.test(data.sequence)) { 2110 console.log(msgPrefix + ': sequence needs to be a number. Used the default 0 instead'); 2111 data.sequence = 0; 2112 } 2113 // validate created and updated input 2114 if (!/^\d{8}T\d{6}Z$/.test(data.created)) { 2115 console.error( 2116 msgPrefix + 2117 ': created date format not valid. Needs to be a full ISO-8601 UTC date and time string, formatted YYYYMMDDTHHMMSSZ' 2118 ); 2119 return false; 2120 } 2121 if (!/^\d{8}T\d{6}Z$/.test(data.updated)) { 2122 console.error( 2123 msgPrefix + 2124 ': updated date format not valid. Needs to be a full ISO-8601 UTC date and time string, formatted YYYYMMDDTHHMMSSZ' 2125 ); 2126 return false; 2127 } 2128 // validate status 2129 if (data.status != 'TENTATIVE' && data.status != 'CONFIRMED' && data.status != 'CANCELLED') { 2130 console.error(msgPrefix + ': event status needs to be TENTATIVE, CONFIRMED, or CANCELLED'); 2131 return false; 2132 } 2133 // validate options 2134 if ( 2135 !data.options.every(function (option) { 2136 if (!atcbOptions.includes(option)) { 2137 console.error(msgPrefix + ' failed: invalid option [' + option + ']'); 2138 return false; 2139 } 2140 return true; 2141 }) 2142 ) { 2143 return false; 2144 } 2145 // validate time zone 2146 if (data.timeZone != null && data.timeZone != '') { 2147 const validTimeZones = tzlib_get_timezones(); 2148 if (!validTimeZones.includes(data.timeZone)) { 2149 console.error(msgPrefix + ' failed: invalid time zone given'); 2150 return false; 2151 } 2152 } 2153 // validate date 2154 const dates = ['startDate', 'endDate']; 2155 const newDate = dates; 2156 if ( 2157 !dates.every(function (date) { 2158 if (data[`${date}`].length !== 10) { 2159 console.error(msgPrefix + ' failed: date misspelled [-> YYYY-MM-DD]'); 2160 return false; 2161 } 2162 const dateParts = data[`${date}`].split('-'); 2163 if (dateParts.length < 3 || dateParts.length > 3) { 2164 console.error(msgPrefix + ' failed: date misspelled [' + date + ': ' + data[`${date}`] + ']'); 2165 return false; 2166 } 2167 newDate[`${date}`] = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]); 2168 return true; 2169 }) 2170 ) { 2171 return false; 2172 } 2173 // validate time 2174 const times = ['startTime', 'endTime']; 2175 if ( 2176 !times.every(function (time) { 2177 if (data[`${time}`] != null) { 2178 if (data[`${time}`].length !== 5) { 2179 console.error(msgPrefix + ' failed: time misspelled [-> HH:MM]'); 2180 return false; 2181 } 2182 const timeParts = data[`${time}`].split(':'); 2183 // validate the time parts 2184 if (timeParts.length < 2 || timeParts.length > 2) { 2185 console.error(msgPrefix + ' failed: time misspelled [' + time + ': ' + data[`${time}`] + ']'); 2186 return false; 2187 } 2188 if (timeParts[0] > 23) { 2189 console.error( 2190 msgPrefix + 2191 ' failed: time misspelled - hours number too high [' + 2192 time + 2193 ': ' + 2194 timeParts[0] + 2195 ']' 2196 ); 2197 return false;
2198 } 2199 if (timeParts[1] > 59) { 2200 console.error( 2201 msgPrefix + 2202 ' failed: time misspelled - minutes number too high [' + 2203 time + 2204 ': ' + 2205 timeParts[1] + 2206 ']' 2207 ); 2208 return false; 2209 } 2210 // update the date with the time for further validation steps 2211 if (time == 'startTime') { 2212 newDate.startDate = new Date( 2213 newDate.startDate.getTime() + timeParts[0] * 3600000 + timeParts[1] * 60000 2214 ); 2215 } 2216 if (time == 'endTime') { 2217 newDate.endDate = new Date( 2218 newDate.endDate.getTime() + timeParts[0] * 3600000 + timeParts[1] * 60000 2219 ); 2220 } 2221 } 2222 return true; 2223 }) 2224 ) { 2225 return false; 2226 } 2227 if ((data.startTime != null && data.endTime == null) || (data.startTime == null && data.endTime != null)) { 2228 console.error(msgPrefix + ' failed: if you set a starting time, you also need to define an end time'); 2229 return false; 2230 } 2231 // validate whether end is not before start 2232 if (newDate.endDate < newDate.startDate) { 2233 console.error(msgPrefix + ' failed: end date before start date'); 2234 return false; 2235 } 2236 // validate any given RRULE 2237 if (data.recurrence != null && data.recurrence != '' && !/^RRULE:[\w=;,:+-/\\]+$/i.test(data.recurrence)) { 2238 console.error(msgPrefix + ' failed: RRULE data misspelled'); 2239 return false; 2240 } 2241 // also validate the more easy recurrence settings, since any error there would be also hidden in the RRULE 2242 if ( 2243 data.recurrence_interval != null && 2244 data.recurrence_interval != '' && 2245 !/^\d+$/.test(data.recurrence_interval) 2246 ) { 2247 console.error(msgPrefix + ' failed: recurrence data (interval) misspelled'); 2248 return false; 2249 } 2250 if ( 2251 data.recurrence_until != null && 2252 data.recurrence_until != '' && 2253 !/^(\d|-|:)+$/i.test(data.recurrence_until) 2254 ) { 2255 console.error(msgPrefix + ' failed: recurrence data (until) misspelled'); 2256 return false; 2257 } 2258 if (data.recurrence_count != null && data.recurrence_count != '' && !/^\d+$/.test(data.recurrence_count)) { 2259 console.error(msgPrefix + ' failed: recurrence data (interval) misspelled'); 2260 return false; 2261 } 2262 if ( 2263 data.recurrence_byMonth != null && 2264 data.recurrence_byMonth != '' && 2265 !/^(\d|,)+$/.test(data.recurrence_byMonth) 2266 ) { 2267 console.error(msgPrefix + ' failed: recurrence data (byMonth) misspelled'); 2268 return false; 2269 } 2270 if ( 2271 data.recurrence_byMonthDay != null && 2272 data.recurrence_byMonthDay != '' && 2273 !/^(\d|,)+$/.test(data.recurrence_byMonthDay) 2274 ) { 2275 console.error(msgPrefix + ' failed: recurrence data (byMonthDay) misspelled'); 2276 return false; 2277 } 2278 if ( 2279 data.recurrence_byDay != null && 2280 data.recurrence_byDay != '' && 2281 !/^(\d|-|MO|TU|WE|TH|FR|SA|SU|,)+$/im.test(data.recurrence_byDay) 2282 ) { 2283 console.error(msgPrefix + ' failed: recurrence data (byDay) misspelled'); 2284 return false; 2285 } 2286 if ( 2287 data.recurrence_weekstart != null && 2288 data.recurrence_weekstart != '' && 2289 !/^(MO|TU|WE|TH|FR|SA|SU)$/im.test(data.recurrence_weekstart) 2290 ) { 2291 console.error(msgPrefix + ' failed: recurrence data (weekstart) misspelled'); 2292 return false; 2293 } 2294 // on passing the validation, return true 2295 return true; 2296} 2297 2298// GENERATE THE ACTUAL BUTTON 2299// helper function to generate the labels for the button and list options 2300function atcb_generate_label(data, parent, type, icon = false, text = '', oneOption = false) { 2301 let defaultTriggerText = atcb_translate_hook('Add to Calendar', data.language, data); 2302 // if there is only 1 option, we use the trigger text on the option label. Therefore, forcing it here 2303 if (oneOption && text == '') { 2304 text = defaultTriggerText; 2305 } 2306 switch (type) { 2307 case 'trigger': 2308 default: 2309 if (data.trigger === 'click') { 2310 parent.addEventListener('click', (event) => { 2311 event.preventDefault(); 2312 atcb_toggle('auto', data, parent, false, true); 2313 }); 2314 } else { 2315 parent.addEventListener('touchend', (event) => { 2316 event.preventDefault(); 2317 atcb_toggle('auto', data, parent, false, true); 2318 }); 2319 parent.addEventListener( 2320 'mouseenter', 2321 atcb_debounce_leading((event) => { 2322 event.preventDefault(); 2323 atcb_toggle('open', data, parent, false, true); 2324 }) 2325 ); 2326 } 2327 parent.id = data.identifier;
2328 text = text || defaultTriggerText; 2329 break; 2330 case 'apple': 2331 parent.addEventListener( 2332 'click', 2333 atcb_debounce(() => { 2334 oneOption ? parent.blur() : atcb_toggle('close'); 2335 atcb_generate_ical(data); 2336 }) 2337 ); 2338 parent.id = data.identifier + '-apple'; 2339 text = text || 'Apple'; 2340 break; 2341 case 'google': 2342 parent.addEventListener( 2343 'click', 2344 atcb_debounce(() => { 2345 oneOption ? parent.blur() : atcb_toggle('close'); 2346 atcb_generate_google(data); 2347 }) 2348 ); 2349 parent.id = data.identifier + '-google'; 2350 text = text || 'Google'; 2351 break; 2352 case 'ical': 2353 parent.addEventListener( 2354 'click', 2355 atcb_debounce(() => { 2356 oneOption ? parent.blur() : atcb_toggle('close'); 2357 atcb_generate_ical(data); 2358 }) 2359 ); 2360 parent.id = data.identifier + '-ical'; 2361 text = text || atcb_translate_hook('iCal File', data.language, data); 2362 break; 2363 case 'msteams': 2364 parent.addEventListener( 2365 'click', 2366 atcb_debounce(() => { 2367 oneOption ? parent.blur() : atcb_toggle('close'); 2368 atcb_generate_teams(data); 2369 }) 2370 ); 2371 parent.id = data.identifier + '-msteams'; 2372 text = text || 'Microsoft Teams'; 2373 break; 2374 case 'ms365': 2375 parent.addEventListener( 2376 'click', 2377 atcb_debounce(() => { 2378 oneOption ? parent.blur() : atcb_toggle('close'); 2379 atcb_generate_microsoft(data, '365'); 2380 }) 2381 ); 2382 parent.id = data.identifier + '-ms365'; 2383 text = text || 'Microsoft 365'; 2384 break; 2385 case 'outlookcom': 2386 parent.addEventListener( 2387 'click', 2388 atcb_debounce(() => { 2389 oneOption ? parent.blur() : atcb_toggle('close'); 2390 atcb_generate_microsoft(data, 'outlook'); 2391 }) 2392 ); 2393 parent.id = data.identifier + '-outlook'; 2394 text = text || 'Outlook.com'; 2395 break; 2396 case 'yahoo': 2397 parent.addEventListener( 2398 'click', 2399 atcb_debounce(() => { 2400 oneOption ? parent.blur() : atcb_toggle('close'); 2401 atcb_generate_yahoo(data); 2402 }) 2403 ); 2404 parent.id = data.identifier + '-yahoo'; 2405 text = text || 'Yahoo'; 2406 break; 2407 case 'close': 2408 parent.addEventListener( 2409 'click', 2410 atcb_debounce(() => { 2411 oneOption ? parent.blur() : atcb_toggle('close'); 2412 }) 2413 ); 2414 parent.addEventListener( 2415 'focus', 2416 atcb_debounce(() => atcb_close(false)) 2417 ); 2418 parent.id = data.identifier + '-close'; 2419 text = atcb_translate_hook('Close', data.language, data); 2420 break; 2421 } 2422 // override the id for the oneOption button, since the button always needs to have the button id 2423 if (oneOption) { 2424 parent.id = data.identifier; 2425 } 2426 // support keyboard input 2427 if (!oneOption && type === 'trigger') { 2428 parent.addEventListener( 2429 'keyup', 2430 atcb_debounce_leading((event) => { 2431 if (event.key == 'Enter') { 2432 event.preventDefault(); 2433 atcb_toggle('auto', data, parent, true, true); 2434 } 2435 }) 2436 ); 2437 } else { 2438 parent.addEventListener( 2439 'keyup', 2440 atcb_debounce_leading((event) => { 2441 if (event.key == 'Enter') { 2442 event.preventDefault(); 2443 parent.click(); 2444 } 2445 }) 2446 ); 2447 } 2448 // add icon and text label 2449 if (icon) { 2450 const iconEl = document.createElement('span'); 2451 iconEl.classList.add('atcb-icon'); 2452 iconEl.innerHTML = atcbIcon[`${type}`]; 2453 parent.appendChild(iconEl); 2454 } 2455 const textEl = document.createElement('span'); 2456 textEl.classList.add('atcb-text'); 2457 textEl.textContent = text; 2458 parent.appendChild(textEl); 2459} 2460 2461// generate the triggering button 2462function atcb_generate(button, data) { 2463 // clean the placeholder 2464 button.textContent = ''; 2465 // create schema.org data, if possible (https://schema.org/Event) 2466 // see https://developers.google.com/search/docs/advanced/structured-data/event for more details on how this affects Google search results 2467 if (data.name && data.location && data.startDate) { 2468 const schemaEl = document.createElement('script'); 2469 schemaEl.type = 'application/ld+json'; 2470 const schemaContent = []; 2471 schemaContent.push('{\r\n"event": {\r\n"@context":"https://schema.org",\r\n"@type":"Event"'); 2472 if (data.status == 'CANCELLED') { 2473 schemaContent.push('"eventStatus":"https://schema.org/EventCancelled"'); 2474 } 2475 schemaContent.push('"name":"' + data.name + '"'); 2476 if (data.descriptionHtmlFree) { 2477 schemaContent.push('"description":"' + data.descriptionHtmlFree + '"'); 2478 } 2479 const formattedDate = atcb_generate_time(data, 'delimiters', 'general', true); 2480 schemaContent.push('"startDate":"' + formattedDate.start + '"'); 2481 schemaContent.push('"duration":"' + formattedDate.duration + '"'); 2482 if (data.recurrence != null && data.recurrence != '') { 2483 schemaContent.push('"eventSchedule": { "@type": "Schedule"'); 2484 if (data.timeZone != null && data.timeZone != '') {
2485 schemaContent.push('"scheduleTimezone":"' + data.timeZone + '"'); 2486 } 2487 const repeatFrequency = 'P' + data.recurrence_interval + data.recurrence_frequency.substr(0, 1); 2488 schemaContent.push('"repeatFrequency":"' + repeatFrequency + '"'); 2489 if (data.recurrence_byDay != null && data.recurrence_byDay != '') { 2490 const byDayString = (function () { 2491 if (/\d/.test(data.recurrence_byDay)) { 2492 return '"' + data.recurrence_byDay + '"'; 2493 } else { 2494 const byDays = data.recurrence_byDay.split(','); 2495 const helperMap = { 2496 MO: 'https://schema.org/Monday', 2497 TU: 'https://schema.org/Tuesday', 2498 WE: 'https://schema.org/Wednesday', 2499 TH: 'https://schema.org/Thursday', 2500 FR: 'https://schema.org/Friday', 2501 SA: 'https://schema.org/Saturday', 2502 SU: 'https://schema.org/Sunday', 2503 }; 2504 const output = []; 2505 for (let i = 0; i < byDays.length; i++) { 2506 output.push('"' + helperMap[byDays[`${i}`]] + '"'); 2507 } 2508 return '[' + output.join(',') + ']'; 2509 } 2510 })(); 2511 schemaContent.push('"byDay":' + byDayString); 2512 } 2513 if (data.recurrence_byMonth != null && data.recurrence_byMonth != '') { 2514 const byMonthString = data.recurrence_byMonth.includes(',') 2515 ? '[' + data.recurrence_byMonth + ']' 2516 : data.recurrence_byMonth; 2517 schemaContent.push('"byMonth":"' + byMonthString + '"'); 2518 } 2519 if (data.recurrence_byMonthDay != null && data.recurrence_byMonthDay != '') { 2520 const byMonthDayString = data.recurrence_byMonthDay.includes(',') 2521 ? '[' + data.recurrence_byMonthDay + ']' 2522 : data.recurrence_byMonthDay; 2523 schemaContent.push('"byMonthDay":"' + byMonthDayString + '"'); 2524 } 2525 if (data.recurrence_count != null && data.recurrence_count != '') { 2526 schemaContent.push('"repeatCount":"' + data.recurrence_count + '"'); 2527 } 2528 if (data.recurrence_until != null && data.recurrence_until != '') { 2529 schemaContent.push('"endDate":"' + data.recurrence_until + '"'); 2530 } 2531 if (data.startTime != null && data.startTime != '' && data.endTime != null && data.endTime != '') { 2532 schemaContent.push('"startTime":"' + data.startTime + ':00"'); 2533 schemaContent.push('"endTime":"' + data.endTime + ':00"'); 2534 schemaContent.push('"duration":"' + formattedDate.duration + '"'); 2535 } 2536 schemaContent.push('"startDate":"' + data.startDate + '" }'); 2537 } else { 2538 schemaContent.push('"endDate":"' + formattedDate.end + '"'); 2539 } 2540 schemaContent.push( 2541 data.location.startsWith('http') 2542 ? '"eventAttendanceMode":"https://schema.org/OnlineEventAttendanceMode",\r\n"location": {\r\n"@type":"VirtualLocation",\r\n"url":"' + 2543 data.location + 2544 '"\r\n}' 2545 : '"location":"' + data.location + '"' 2546 ); 2547 if (data.organizer != null && data.organizer != '') { 2548 const organizerParts = data.organizer.split('|'); 2549 schemaContent.push( 2550 '"organizer":{\r\n"@type":"Person",\r\n"name":"' + 2551 organizerParts[0] + 2552 '",\r\n"email":"' + 2553 organizerParts[1] + 2554 '"\r\n}' 2555 ); 2556 } 2557 const imageData = []; 2558 if (data.images != null) { 2559 if (Array.isArray(data.images)) { 2560 for (let i = 0; i < data.images.length; i++) { 2561 if (atcb_secure_url(data.images[`${i}`]) && data.images[`${i}`].startsWith('http')) { 2562 imageData.push('"' + data.images[`${i}`] + '"'); 2563 } 2564 } 2565 } 2566 } else { 2567 imageData.push('"https://add-to-calendar-button.com/demo_assets/img/1x1.png"'); 2568 imageData.push('"https://add-to-calendar-button.com/demo_assets/img/4x3.png"'); 2569 imageData.push('"https://add-to-calendar-button.com/demo_assets/img/16x9.png"'); 2570 } 2571 schemaContent.push('"image":[\r\n' + imageData.join(',\r\n') + ']'); 2572 schemaEl.textContent = schemaContent.join(',\r\n') + '\r\n}\r\n}'; 2573 button.appendChild(schemaEl); 2574 } 2575 // generate the wrapper div 2576 const buttonTriggerWrapper = document.createElement('div'); 2577 buttonTriggerWrapper.classList.add('atcb-button-wrapper'); 2578 buttonTriggerWrapper.classList.add('atcb-' + data.lightMode); 2579 if (data.rtl) { 2580 buttonTriggerWrapper.classList.add('atcb-rtl'); 2581 } 2582 buttonTriggerWrapper.style.fontSize = data.size + 'px'; 2583 button.appendChild(buttonTriggerWrapper); 2584 // generate the button trigger div 2585 const buttonTrigger = document.createElement('button'); 2586 buttonTrigger.classList.add('atcb-button'); 2587 if (data.trigger === 'click') { 2588 buttonTrigger.classList.add('atcb-click'); 2589 } 2590 if (data.listStyle === 'overlay') {
2591 buttonTrigger.classList.add('atcb-dropoverlay'); 2592 } 2593 buttonTrigger.type = 'button'; 2594 buttonTriggerWrapper.appendChild(buttonTrigger); 2595 // generate the label incl. eventListeners 2596 // if there is only 1 calendar option, we directly show this at the button, but with the trigger's label text 2597 if (data.options.length === 1) { 2598 buttonTrigger.classList.add('atcb-single'); 2599 atcb_generate_label(data, buttonTrigger, data.options[0], true, data.label, true); 2600 } else { 2601 atcb_generate_label(data, buttonTrigger, 'trigger', true, data.label); 2602 // create an empty anchor div to place the dropdown, while the position can be defined via CSS 2603 const buttonDropdownAnchor = document.createElement('div'); 2604 buttonDropdownAnchor.classList.add('atcb-dropdown-anchor'); 2605 buttonTrigger.appendChild(buttonDropdownAnchor); 2606 } 2607 // update the placeholder class to prevent multiple initializations 2608 button.classList.remove('atcb'); 2609 button.classList.add('atcb-initialized'); 2610 // show the placeholder div 2611 if (data.inline) { 2612 button.style.display = 'inline-block'; 2613 } else { 2614 button.style.display = 'block'; 2615 } 2616 // console log 2617 console.log('Add to Calendar Button "' + data.identifier + '" created'); 2618} 2619 2620// generate the dropdown list (can also appear wihtin a modal, if option is set) 2621function atcb_generate_dropdown_list(data) { 2622 const optionsList = document.createElement('div'); 2623 optionsList.classList.add('atcb-list'); 2624 optionsList.classList.add('atcb-' + data.lightMode); 2625 if (data.rtl) { 2626 optionsList.classList.add('atcb-rtl'); 2627 } 2628 optionsList.style.fontSize = data.size + 'px'; 2629 // generate the list items 2630 let listCount = 0; 2631 data.options.forEach(function (option) { 2632 const optionItem = document.createElement('div'); 2633 optionItem.classList.add('atcb-list-item'); 2634 optionItem.tabIndex = 0; 2635 listCount++; 2636 optionItem.dataset.optionNumber = listCount; 2637 optionsList.appendChild(optionItem); 2638 // generate the label incl. individual eventListener 2639 atcb_generate_label(data, optionItem, option, true, data.optionLabels[listCount - 1]); 2640 }); 2641 // in the modal case, we also render a close option 2642 if (data.listStyle === 'modal') { 2643 const optionItem = document.createElement('div'); 2644 optionItem.classList.add('atcb-list-item', 'atcb-list-item-close'); 2645 optionItem.tabIndex = 0; 2646 optionsList.appendChild(optionItem); 2647 atcb_generate_label(data, optionItem, 'close', true); 2648 } 2649 return optionsList; 2650} 2651 2652// create the background overlay, which also acts as trigger to close any dropdowns 2653function atcb_generate_bg_overlay(listStyle = 'dropdown', trigger = '', lightMode = 'light', darken = true) { 2654 const bgOverlay = document.createElement('div'); 2655 bgOverlay.id = 'atcb-bgoverlay'; 2656 if (listStyle !== 'modal' && darken) { 2657 bgOverlay.classList.add('atcb-animate-bg'); 2658 } 2659 if (!darken) { 2660 bgOverlay.classList.add('atcb-no-bg'); 2661 } 2662 bgOverlay.classList.add('atcb-' + lightMode); 2663 bgOverlay.tabIndex = 0; 2664 bgOverlay.addEventListener( 2665 'click', 2666 atcb_debounce((e) => { 2667 if (e.target !== e.currentTarget) return; 2668 atcb_toggle('close'); 2669 }) 2670 ); 2671 let fingerMoved = false; 2672 bgOverlay.addEventListener( 2673 'touchstart', 2674 atcb_debounce_leading(() => (fingerMoved = false)), 2675 { passive: true } 2676 ); 2677 bgOverlay.addEventListener( 2678 'touchmove', 2679 atcb_debounce_leading(() => (fingerMoved = true)), 2680 { passive: true } 2681 ); 2682 bgOverlay.addEventListener( 2683 'touchend', 2684 atcb_debounce((e) => { 2685 if (fingerMoved !== false || e.target !== e.currentTarget) return; 2686 atcb_toggle('close'); 2687 }), 2688 { passive: true } 2689 ); 2690 bgOverlay.addEventListener( 2691 'focus', 2692 atcb_debounce_leading((e) => { 2693 if (e.target !== e.currentTarget) return; 2694 atcb_toggle('close'); 2695 }) 2696 ); 2697 if (trigger !== 'click') { 2698 bgOverlay.addEventListener( 2699 'mousemove', 2700 atcb_debounce_leading((e) => { 2701 if (e.target !== e.currentTarget) return; 2702 atcb_toggle('close'); 2703 }) 2704 ); 2705 } else { 2706 // if trigger is not set to 'click', we render a close icon, when hovering over the background 2707 bgOverlay.classList.add('atcb-click'); 2708 } 2709 return bgOverlay; 2710} 2711 2712// FUNCTIONS TO CONTROL THE INTERACTION 2713function atcb_toggle(action, data = '', button = '', keyboardTrigger = false, generatedButton = false) { 2714 // check for state and adjust accordingly 2715 // action can be 'open', 'close', or 'auto' 2716 if (action == 'open') { 2717 atcb_open(data, button, keyboardTrigger, generatedButton); 2718 } else if ( 2719 action == 'close' || 2720 button.classList.contains('atcb-active') || 2721 document.querySelector('.atcb-active-modal') 2722 ) { 2723 atcb_close(keyboardTrigger); 2724 } else { 2725 atcb_open(data, button, keyboardTrigger, generatedButton); 2726 } 2727} 2728 2729// show the dropdown list + background overlay 2730function atcb_open(data, button, keyboardTrigger = false, generatedButton = false) { 2731 // abort early if an add to calendar dropdown or modal already opened 2732 if (document.querySelector('.atcb-list') || document.querySelector('.atcb-modal')) return; 2733 // generate list and prepare wrapper 2734 const list = atcb_generate_dropdown_list(data); 2735 const listWrapper = document.createElement('div'); 2736 listWrapper.classList.add('atcb-list-wrapper'); 2737 // set list styles, set button to atcb-active and force modal listStyle if no button is set 2738 if (button) { 2739 button.classList.add('atcb-active'); 2740 if (data.listStyle === 'modal') { 2741 button.classList.add('atcb-modal-style'); 2742 list.classList.add('atcb-modal'); 2743 } else { 2744 listWrapper.appendChild(list); 2745 listWrapper.classList.add('atcb-dropdown'); 2746 if (data.listStyle === 'overlay') {
2747 listWrapper.classList.add('atcb-dropoverlay'); 2748 } 2749 } 2750 if (generatedButton) { 2751 list.classList.add('atcb-generated-button'); // if the button has been generated by the script, we add some more specifics 2752 } 2753 } else { 2754 list.classList.add('atcb-modal'); 2755 } 2756 // define background overlay 2757 const bgOverlay = atcb_generate_bg_overlay(data.listStyle, data.trigger, data.lightMode, data.background); 2758 // render the items depending on the liststyle 2759 const atcbL = document.createElement('div'); 2760 atcbL.id = 'add-to-calendar-button-reference'; 2761 atcbL.style.width = '150px'; 2762 atcbL.style.padding = '10px 0'; 2763 atcbL.style.height = 'auto'; 2764 atcbL.style.transform = 'translate3d(0, 0, 0)'; 2765 atcbL.style.zIndex = '15000000'; 2766 setTimeout(() => { 2767 atcbL.innerHTML = 2768 '<a href="https://add-to-calendar-button.com" target="_blank" rel="noopener">' + 2769 atcbIcon['atcb'] + 2770 '</a>'; 2771 }, 500); 2772 if (data.listStyle === 'modal') { 2773 document.body.appendChild(bgOverlay); 2774 bgOverlay.appendChild(list); 2775 //document.body.appendChild(atcbL); 2776 atcbL.style.position = 'fixed'; 2777 atcbL.style.bottom = '15px'; 2778 atcbL.style.right = '30px'; 2779 document.body.classList.add('atcb-modal-no-scroll'); 2780 } else { 2781 atcbL.style.position = 'absolute'; 2782 document.body.appendChild(listWrapper); 2783 listWrapper.appendChild(list); 2784 listWrapper.classList.add('atcb-style-' + data.buttonStyle); 2785 //document.body.appendChild(atcbL); 2786 document.body.appendChild(bgOverlay); 2787 if (data.listStyle === 'dropdown-static') { 2788 // in the dropdown-static case, we do not dynamically adjust whether we show the dropdown upwards 2789 atcb_position_list(button, listWrapper, true); 2790 } else { 2791 atcb_position_list(button, listWrapper); 2792 } 2793 } 2794 // set overlay size just to be sure 2795 atcb_set_fullsize(bgOverlay); 2796 // give keyboard focus to first item in list, if not blocked, because there is definitely no keyboard trigger 2797 if (keyboardTrigger) { 2798 list.firstChild.focus(); 2799 } else { 2800 list.firstChild.focus({ preventScroll: true }); 2801 } 2802 list.firstChild.blur(); 2803} 2804 2805function atcb_close(keyboardTrigger = false) { 2806 // focus triggering button if available - especially relevant for keyboard navigation 2807 const newFocusEl = document.querySelector('.atcb-active, .atcb-active-modal'); 2808 if (newFocusEl) { 2809 newFocusEl.focus({ preventScroll: true }); 2810 if (!keyboardTrigger) { 2811 newFocusEl.blur(); 2812 } 2813 } 2814 // inactivate all buttons 2815 Array.from(document.querySelectorAll('.atcb-active')).forEach((button) => { 2816 button.classList.remove('atcb-active'); 2817 }); 2818 Array.from(document.querySelectorAll('.atcb-active-modal')).forEach((button) => { 2819 button.classList.remove('atcb-active-modal'); 2820 }); 2821 // make body scrollable again 2822 document.body.classList.remove('atcb-modal-no-scroll'); 2823 // remove dropdowns, modals, and bg overlays (should only be one of each at max) 2824 Array.from(document.querySelectorAll('.atcb-list-wrapper')) 2825 .concat(Array.from(document.querySelectorAll('.atcb-list'))) 2826 .concat(Array.from(document.querySelectorAll('.atcb-info-modal'))) 2827 .concat(Array.from(document.querySelectorAll('#add-to-calendar-button-reference'))) 2828 .concat(Array.from(document.querySelectorAll('#atcb-bgoverlay'))) 2829 .forEach((el) => el.remove()); 2830} 2831 2832// prepare data when not using the init function 2833// eslint-disable-next-line @typescript-eslint/no-unused-vars 2834function atcb_action(data, triggerElement, keyboardTrigger = true) { 2835 if (!atcbConsoleInit) { 2836 console.log('Add to Calendar Button Script initialized (version ' + atcbVersion + ')'); 2837 console.log('See https://github.com/add2cal/add-to-calendar-button for details'); 2838 atcbConsoleInit = true; 2839 } 2840 data = atcb_secure_content(data); 2841 // decorate & validate data 2842 if (!atcb_check_required(data)) { 2843 throw new Error('Add to Calendar Button generation failed: required data missing; see console logs'); 2844 } 2845 data = atcb_decorate_data(data); 2846 if (triggerElement) { 2847 data.identifier = triggerElement.id; 2848 // if listStyle some dropdown one, force overlay 2849 if (data.listStyle != 'modal') { 2850 data.listStyle = 'overlay'; 2851 } 2852 } else { 2853 data.identifier = 'atcb-btn-custom'; 2854 // if no button is defined, fallback to listStyle "modal" and "click" trigger 2855 data.listStyle = 'modal'; 2856 data.trigger = 'click'; 2857 } 2858 if (!atcb_validate(data)) { 2859 throw new Error( 2860 'Add to Calendar Button generation (' + data.identifier + ') failed: invali
2860d data; see console logs' 2861 ); 2862 } 2863 // if all is fine, open the options list 2864 atcb_toggle('open', data, triggerElement, keyboardTrigger); 2865} 2866 2867// FUNCTION TO GENERATE THE GOOGLE URL 2868// See specs at: TODO 2869function atcb_generate_google(data) { 2870 // url parts 2871 const urlParts = []; 2872 urlParts.push('https://calendar.google.com/calendar/render?action=TEMPLATE'); 2873 // generate and add date 2874 const formattedDate = atcb_generate_time(data, 'clean', 'google'); 2875 urlParts.push( 2876 'dates=' + encodeURIComponent(formattedDate.start) + '%2F' + encodeURIComponent(formattedDate.end) 2877 ); 2878 // setting time zone if given and not GMT +/- something, since this is not supported by Google Calendar 2879 if (data.timeZone != null && data.timeZone != '' && !/GMT[+|-]\d{1,2}/i.test(data.timeZone)) { 2880 urlParts.push('ctz=' + data.timeZone); 2881 } 2882 // add details (if set) 2883 if (data.name != null && data.name != '') { 2884 urlParts.push('text=' + encodeURIComponent(data.name)); 2885 } 2886 const tmpDataDescription = []; 2887 if (data.description != null && data.description != '') { 2888 tmpDataDescription.push(data.description); 2889 } 2890 if (data.location != null && data.location != '') { 2891 urlParts.push('location=' + encodeURIComponent(data.location)); 2892 // TODO: Find a better solution for the next temporary workaround. 2893 if (isiOS()) { 2894 // workaround to cover a bug, where, when using Google Calendar on an iPhone, the location is not recognized. So, for the moment, we simply add it to the description. 2895 if (tmpDataDescription.length > 0) { 2896 tmpDataDescription.push('<br><br>'); 2897 } 2898 tmpDataDescription.push('📍: ' + data.location); 2899 } 2900 } 2901 if (tmpDataDescription.length > 0) { 2902 urlParts.push('details=' + encodeURIComponent(tmpDataDescription.join())); 2903 } 2904 if (data.recurrence != null && data.recurrence != '') { 2905 urlParts.push('recur=' + encodeURIComponent(data.recurrence)); 2906 } 2907 // We also push the UID. It has no real effect, but at least becomes part of the url that way 2908 urlParts.push('uid=' + encodeURIComponent(data.uid)); 2909 const url = urlParts.join('&'); 2910 if (atcb_secure_url(url)) { 2911 // eslint-disable-next-line security/detect-non-literal-fs-filename 2912 window.open(url, atcbDefaultTarget).focus(); 2913 } 2914} 2915 2916// FUNCTION TO GENERATE THE YAHOO URL 2917// See specs at: TODO 2918function atcb_generate_yahoo(data) { 2919 // url parts 2920 const urlParts = []; 2921 urlParts.push('https://calendar.yahoo.com/?v=60'); 2922 // generate and add date 2923 const formattedDate = atcb_generate_time(data, 'clean'); 2924 urlParts.push( 2925 'st=' + encodeURIComponent(formattedDate.start) + '&et=' + encodeURIComponent(formattedDate.end) 2926 ); 2927 if (formattedDate.allday) { 2928 urlParts.push('dur=allday'); 2929 } 2930 // add details (if set) 2931 if (data.name != null && data.name != '') { 2932 urlParts.push('title=' + encodeURIComponent(data.name)); 2933 } 2934 if (data.location != null && data.location != '') { 2935 urlParts.push('in_loc=' + encodeURIComponent(data.location)); 2936 } 2937 if (data.descriptionHtmlFree != null && data.descriptionHtmlFree != '') { 2938 // using descriptionHtmlFree instead of description, since Yahoo does not support html tags in a stable way 2939 urlParts.push('desc=' + encodeURIComponent(data.descriptionHtmlFree)); 2940 } 2941 // We also push the UID. It has no real effect, but at least becomes part of the url that way 2942 urlParts.push('uid=' + encodeURIComponent(data.uid)); 2943 const url = urlParts.join('&'); 2944 if (atcb_secure_url(url)) { 2945 // eslint-disable-next-line security/detect-non-literal-fs-filename 2946 window.open(url, atcbDefaultTarget).focus(); 2947 } 2948} 2949 2950// FUNCTION TO GENERATE THE MICROSOFT 365 OR OUTLOOK WEB URL 2951// See specs at: TODO 2952function atcb_generate_microsoft(data, type = '365') { 2953 // redirect to iCal solution on mobile devices, since the Microsoft web apps are buggy on mobile devices (see https://github.com/add2cal/add-to-calendar-button/discussions/113) 2954 if (isMobile()) { 2955 atcb_generate_ical(data); 2956 return; 2957 } 2958 // url parts 2959 const urlParts = []; 2960 const basePath = '/calendar/0/deeplink/compose?path=%2Fcalendar%2Faction%2Fcompose&rru=addevent'; 2961 const baseUrl = (function () { 2962 if (type == 'outlook') { 2963 return 'https://outlook.live.com' + basePath; 2964 } else { 2965 return 'https://outlook.office.com' + basePath; 2966 } 2967 })(); 2968 urlParts.push(baseUrl); 2969 // generate and add date 2970 const formattedDate = atcb_generate_time(data, 'delimiters', 'microsoft'); 2971 urlParts.push('startdt=' + encodeURIComponent(formattedDate.start)); 2972 urlParts.push('enddt=' + encodeURIComponent(formattedDate.end)); 2973 if (formattedDate.allday) { 2974 urlParts.push('allday=true'); 2975 } 2976 // add details (if set) 2977 if (data.name != null && data.name != '') { 2978 urlParts.push('subject=' + encodeURIComponent(data.name)); 2979 } 2980 if (data.location != null && data.location != '') { 2981 urlParts.push('location=' + encodeURIComponent(data.location)); 2982 } 2983 if (data.description != null && data.description != '') { 2984 urlParts.push('body=' + encodeURIComponent(data.description.replace(/\n/g, '<br>'))); 2985 } 2986 // We also push the UID. It has no real effect, but at least becomes part of the url that way 2987 urlParts.push('uid=' + encodeURIComponent(data.uid)); 2988 const url = urlParts.join('&'); 2989 if (atcb_secure_url(url)) { 2990 // eslint-disable-next-line security/detect-non-literal-fs-filename 2991 window.open(url, atcbDefaultTarget).focus(); 2992 } 2993} 2994 2995// FUNCTION TO GENERATE THE MICROSOFT TEAMS URL
2996// See specs at: https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/build-and-test/deep-links#deep-linking-to-the-scheduling-dialog 2997// Mind that this is still in development mode by Microsoft! Location, html tags and linebreaks in the description are not supported yet. 2998function atcb_generate_teams(data) { 2999 // url parts 3000 const urlParts = []; 3001 const baseUrl = 'https://teams.microsoft.com/l/meeting/new?'; 3002 // generate and add date 3003 const formattedDate = atcb_generate_time(data, 'delimiters', 'microsoft'); 3004 urlParts.push('startTime=' + encodeURIComponent(formattedDate.start)); 3005 urlParts.push('endTime=' + encodeURIComponent(formattedDate.end)); 3006 // add details (if set) 3007 if (data.name != null && data.name != '') { 3008 urlParts.push('subject=' + encodeURIComponent(data.name)); 3009 } 3010 let locationString = ''; 3011 if (data.location != null && data.location != '') { 3012 locationString = encodeURIComponent(data.location); 3013 urlParts.push('location=' + locationString); 3014 locationString += ' // '; // preparing the workaround putting the location into the description, since the native field is not supported yet 3015 } 3016 if (data.descriptionHtmlFree != null && data.descriptionHtmlFree != '') { 3017 // using descriptionHtmlFree instead of description, since Teams does not support html tags 3018 urlParts.push('content=' + locationString + encodeURIComponent(data.descriptionHtmlFree)); 3019 } 3020 // We also push the UID. It has no real effect, but at least becomes part of the url that way 3021 urlParts.push('uid=' + encodeURIComponent(data.uid)); 3022 const url = baseUrl + urlParts.join('&'); 3023 if (atcb_secure_url(url)) { 3024 // eslint-disable-next-line security/detect-non-literal-fs-filename 3025 window.open(url, atcbDefaultTarget).focus(); 3026 } 3027} 3028 3029// FUNCTION TO GENERATE THE iCAL FILE (also for the Apple option) 3030// See specs at: https://www.rfc-editor.org/rfc/rfc5545.html 3031function atcb_generate_ical(data) { 3032 // define the right filename 3033 let filename = 'event-to-save-in-my-calendar'; 3034 if (data.iCalFileName != null && data.iCalFileName != '') { 3035 filename = data.iCalFileName; 3036 } else if (data.icsFile != null && data.icsFile != '') { 3037 const filenamePart = data.icsFile.split('/').pop().split('.')[0]; 3038 if (filenamePart != '') { 3039 filename = filenamePart; 3040 } 3041 } 3042 // check for a given explicit file (not if iOS and WebView - will be catched further down) 3043 if (data.icsFile != null && data.icsFile != '' && (!isiOS() || !isWebView())) { 3044 atcb_save_file(data.icsFile, filename); 3045 return; 3046 } 3047 // otherwise, generate one on the fly 3048 const now = new Date(); 3049 const formattedDate = atcb_generate_time(data, 'clean', 'ical'); 3050 const ics_lines = ['BEGIN:VCALENDAR', 'VERSION:2.0']; 3051 ics_lines.push('PRODID:-// https://add-to-calendar-pro.com // button v' + atcbVersion + ' //EN'); 3052 ics_lines.push('CALSCALE:GREGORIAN'); 3053 // we set CANCEL, whenever the status says so 3054 if (data.status == 'CANCELLED') { 3055 ics_lines.push('METHOD:CANCEL'); 3056 } else { 3057 // for all other cases, we use REQUEST for organized/hosted events 3058 if (data.organizer != null && data.organizer != '') { 3059 ics_lines.push('METHOD:REQUEST'); 3060 } else { 3061 // and PUBLISH for events without a host 3062 ics_lines.push('METHOD:PUBLISH'); 3063 } 3064 } 3065 // include time zone information, if set and if not allday (not necessary in that case) 3066 const timeAddon = (function () { 3067 if (formattedDate.allday) { 3068 return ';VALUE=DATE'; 3069 } 3070 if (data.timeZone != null && data.timeZone != '') { 3071 const timeZoneBlock = tzlib_get_ical_block(data.timeZone); 3072 ics_lines.push(timeZoneBlock[0]); 3073 return ';' + timeZoneBlock[1]; 3074 } 3075 })(); 3076 ics_lines.push('BEGIN:VEVENT'); 3077 ics_lines.push('UID:' + data.uid); 3078 ics_lines.push('DTSTAMP:' + atcb_format_datetime(now, 'clean', true)); 3079 ics_lines.push('DTSTART' + timeAddon + ':' + formattedDate.start); 3080 ics_lines.push('DTEND' + timeAddon + ':' + formattedDate.end); 3081 ics_lines.push('SUMMARY:' + data.name.replace(/.{65}/g, '$&' + '\r\n ')); // making sure it does not exceed 75 characters per line 3082 if (data.descriptionHtmlFree != null && data.descriptionHtmlFree != '') { 3083 ics_lines.push( 3084 'DESCRIPTION:' + data.descriptionHtmlFree.replace(/\n/g, '\\n').replace(/.{60}/g, '$&' + '\r\n ') // adjusting for intended line breaks + making sure it does not exceed 75 characters per line 3085 ); 3086 } 3087 if (data.description != null && data.description != '') { 3088 ics_lines.push( 3089 'X-ALT-DESC;FMTTYPE=text/html:\r\n <!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 3.2//EN"">\r\n <HTML><BODY>\r\n ' + 3090 data.description.replace(/\n/g, '<br>').replace(/.{60}/g, '$&' + '\r\n ') + 3091 '\r\n </BODY></HTML>' 3092 ); 3093 } 3094 if (data.location != null && data.location != '') { 3095 ics_lines.push('LOCATION:' + data.location); 3096 } 3097 if (data.organizer != null && data.organizer != '') { 3098 const organizerParts = data.organizer.split('|'); 3099 ics_lines.push('ORGANIZER;CN=' + organizerParts[0] + ':MAILTO:' + organizerParts[1]); 3100 } 3101 if (data.recurrence != null && data.recurrence != '') { 3102 ics_lines.push(data.recurrence); 3103 } 3104 ics_lines.push('STATUS:' + data.status); 3105 ics_lines.push('CREATED:' + data.created); 3106 ics_lines.push('LAST-MODIFIED:' + data.updated); 3107 ics_lines.push('SEQUENCE:' + data.sequence); 3108 ics_lines.push('END:VEVENT'); 3109 ics_lines.push('END:VCALENDAR'); 3110 const dataUrl = (function () { 3111 // if we got to this point with an explicitely given iCal file, we are on an iOS device within an in-app browser (WebView). In this case, we use this as dataUrl 3112 if (data.icsFile != null && data.icsFile != '') { 3113 return data.icsFile; 3114 } 3115 // otherwise, we generate it from the array 3116 return 'data:text/calendar;charset=utf-8,' + encodeURIComponent(ics_lines.join('\r\n')); 3117 })(); 3118 // in in-app browser cases (WebView), we offer a copy option, since the on-the-fly client side generation is usually not supported 3119 // for Android, we are more specific and only go for specific apps at the moment 3120 // for Chrome on iOS we basically do the same 3121 if ((isiOS() && isChrome()) || (isWebView() && (isiOS() || (isAndroid() && isProblematicWebView())))) { 3122 // putting the download url to the clipboard 3123 const tmpInput = document.createElement('input'); 3124 document.body.appendChild(tmpInput); 3125 const editable = tmpInput.contentEditable; 3126 const readOnly = tmpInput.readOnly; 3127 tmpInput.value = dataUrl; 3128 tmpInput.contentEditable = true; 3129 tmpInput.readOnly = false;
3130 if (isiOS()) { 3131 var range = document.createRange(); 3132 range.selectNodeContents(tmpInput); 3133 var selection = window.getSelection(); 3134 selection.removeAllRanges(); 3135 selection.addRange(range); 3136 tmpInput.setSelectionRange(0, 999999); 3137 } else { 3138 // the next 2 lines are basically doing the same in different ways (just to be sure) 3139 navigator.clipboard.writeText(dataUrl); 3140 tmpInput.select(); 3141 } 3142 tmpInput.contentEditable = editable; 3143 tmpInput.readOnly = readOnly; 3144 document.execCommand('copy'); 3145 tmpInput.remove(); 3146 // creating the modal 3147 if (isiOS() && isChrome()) { 3148 atcb_create_modal( 3149 data, 3150 'browser', 3151 atcb_translate_hook('Crios iCal headline', data.language, data), 3152 atcb_translate_hook('Crios iCal info', data.language, data) + 3153 '<br>' + 3154 atcb_translate_hook('WebView iCal solution 1', data.language, data) + 3155 '<br>' + 3156 atcb_translate_hook('Crios iCal solution 2', data.language, data) 3157 ); 3158 } else { 3159 atcb_create_modal( 3160 data, 3161 'browser', 3162 atcb_translate_hook('WebView iCal headline', data.language, data), 3163 atcb_translate_hook('WebView iCal info', data.language, data) + 3164 '<br>' + 3165 atcb_translate_hook('WebView iCal solution 1', data.language, data) + 3166 '<br>' + 3167 atcb_translate_hook('WebView iCal solution 2', data.language, data) 3168 ); 3169 } 3170 } else { 3171 atcb_save_file(dataUrl, filename); 3172 } 3173} 3174 3175// SHARED FUNCTION TO SAVE A FILE 3176function atcb_save_file(file, filename) { 3177 try { 3178 const save = document.createElementNS('http://www.w3.org/1999/xhtml', 'a'); 3179 save.rel = 'noopener'; 3180 save.href = file; 3181 // not using default target here, since this needs to happen _self on iOS (abstracted to mobile in general) and _blank at Firefox (abstracted to other setups) due to potential cross-origin restrictions 3182 if (isMobile()) { 3183 save.target = '_self'; 3184 } else { 3185 save.target = '_blank'; 3186 } 3187 save.download = filename + '.ics'; 3188 const evt = new MouseEvent('click', { 3189 view: window, 3190 button: 0, 3191 bubbles: true, 3192 cancelable: false, 3193 }); 3194 save.dispatchEvent(evt); 3195 (window.URL || window.webkitURL).revokeObjectURL(save.href); 3196 } catch (e) { 3197 console.error(e); 3198 } 3199} 3200 3201// SHARED FUNCTION TO GENERATE A TIME STRING 3202function atcb_generate_time(data, style = 'delimiters', targetCal = 'general', addTimeZoneOffset = false) { 3203 const startDate = data.startDate.split('-'); 3204 const endDate = data.endDate.split('-'); 3205 if (data.startTime != null && data.endTime != null) { 3206 // for the input, we assume UTC per default 3207 const newStartDate = new Date( 3208 startDate[0] + '-' + startDate[1] + '-' + startDate[2] + 'T' + data.startTime + ':00.000+00:00' 3209 ); 3210 const newEndDate = new Date( 3211 endDate[0] + '-' + endDate[1] + '-' + endDate[2] + 'T' + data.endTime + ':00.000+00:00' 3212 ); 3213 const durationMS = newEndDate - newStartDate; 3214 const durationHours = Math.floor(durationMS / 1000 / 60 / 60); 3215 const durationMinutes = Math.floor(((durationMS - durationHours * 60 * 60 * 1000) / 1000 / 60) % 60); 3216 const durationString = (function () { 3217 if (durationHours < 10) { 3218 return '0' + durationHours + ':' + ('0' + durationMinutes).slice(-2); 3219 } 3220 return durationHours + ':' + ('0' + durationMinutes).slice(-2); 3221 })(); 3222 // if no time zone is given and we need to add the offset to the datetime string, do so directly and return 3223 if ((data.timeZone == null || (data.timeZone != null && data.timeZone == '')) && addTimeZoneOffset) { 3224 return { 3225 start: newStartDate.toISOString().replace('.000Z', '+00:00'), 3226 end: newEndDate.toISOString().replace('.000Z', '+00:00'), 3227 duration: durationString, 3228 allday: false, 3229 }; 3230 } 3231 // if a time zone is given, we adjust the diverse cases 3232 // (see https://tz.add-to-calendar-technology.com/ for available TZ names) 3233 if (data.timeZone != null && data.timeZone != '') { 3234 if (targetCal == 'ical' || (targetCal == 'google' && !/GMT[+|-]\d{1,2}/i.test(data.timeZone))) { 3235 // in the iCal case, we simply return and cut off the Z. Same applies to Google, except for GMT +/- time zones, which are not supported there. 3236 // everything else will be done by injecting the VTIMEZONE block at the iCal function 3237 return { 3238 start: atcb_format_datetime(newStartDate, 'clean', true, true), 3239 end: atcb_format_datetime(newEndDate, 'clean', true, true), 3240 duration: durationString, 3241 allday: false, 3242 }; 3243 } 3244 // we get the correct offset via the timeZones iCal Library 3245 const offsetStart = tzlib_get_offset(data.timeZone, data.startDate, data.startTime); 3246 const offsetEnd = tzlib_get_offset(data.timeZone, data.endDate, data.endTime); 3247 // if we need to add the offset to the datetime string, do so respectively 3248 if (addTimeZoneOffset) { 3249 const formattedOffsetStart = offsetStart.slice(0, 3) + ':' + offsetStart.slice(3); 3250 const formattedOffsetEnd = offsetEnd.slice(0, 3) + ':' + offsetEnd.slice(3); 3251 return { 3252 start: newStartDate.toISOString().replace('.000Z', formattedOffsetStart), 3253 end: newEndDate.toISOString().replace('.000Z', formattedOffsetEnd), 3254 duration: durationString, 3255 allday: false, 3256 }; 3257 } 3258 // in other cases, we substract the offset from the dates 3259 // (substraction to reflect the fact that the user assumed his timezone and to convert to UTC; since calendars assume UTC and add offsets again) 3260 const calcOffsetStart = 3261 parseInt(offsetStart[0] + 1) * 3262 -1 * 3263 ((parseInt(offsetStart.substr(1, 2)) * 60 + parseInt(offsetStart.substr(3, 2))) * 60 * 1000); 3264 const calcOffsetEnd = 3265 parseInt(offsetEnd[0] + 1) * 3266 -1 * 3267 ((parseInt(offsetEnd.substr(1, 2)) * 60 + parseInt(offsetEnd.substr(3, 2))) * 60 * 1000); 3268 newStartDate.setTime(newStartDate.getTime() + calcOffsetStart); 3269 newEndDate.setTime(newEndDate.getTime() + calcOffsetEnd); 3270 } 3271 // return formatted data 3272 return { 3273 start: atcb_format_datetime(newStartDate, style), 3274 end: atcb_format_datetime(newEndDate, style), 3275 duration: durationString, 3276 allday: false, 3277 }; 3278 } else { 3279 // would be an allday event then 3280 const newStartDate = new Date(Date.UTC(startDate[0], startDate[1] - 1, startDate[2])); 3281 const newEndDate = new Date(Date.UTC(endDate[0], endDate[1] - 1, endDate[2])); 3282 // increment the end day by 1 for Google Calendar, iCal and Outlook 3283 if (targetCal == 'google' || targetCal == 'microsoft' || targetCal == 'ical') { 3284 newEndDate.setDate(newEndDate.getDate() + 1); 3285 } 3286 // return formatted data 3287 return { 3288 start: atcb_format_datetime(newStartDate, style, false), 3289 end: atcb_format_datetime(newEndDate, style, false), 3290 allday: true, 3291 }; 3292 } 3293} 3294 3295function atcb_format_datetime(datetime, style = 'delimiters', includeTime = true, removeZ = false) { 3296 const regex = (function () { 3297 if (includeTime) { 3298 if (style == 'clean') { 3299 return /(-|:|(\.\d{3}))/g; 3300 } 3301 return /(\.\d{3})/g; 3302 } 3303 if (style == 'clean') { 3304 return /(-|T(\d{2}:\d{2}:\d{2}\.\d{3})Z)/g; 3305 } 3306 return /T(\d{2}:\d{2}:\d{2}\.\d{3})Z/g; 3307 })(); 3308 const output = removeZ 3309 ? datetime.toISOString().replace(regex, '').replace('Z', '') 3310 : datetime.toISOString().replace(regex, ''); 3311 return output; 3312} 3313 3314// SHARED FUNCTION TO SECURE DATA 3315function atcb_secure_content(data, isJSON = true) { 3316 // strip HTML tags (especially since stupid Safari adds stuff) - except for <br> 3317 const toClean = isJSON ? JSON.stringify(data) : data; 3318 const cleanedUp = toClean.replace(/(<(?!br)([^>]+)>)/gi, ''); 3319 if (isJSON) { 3320 return JSON.parse(cleanedUp); 3321 } else { 3322 return cleanedUp; 3323 } 3324} 3325 3326// SHARED FUNCTION TO SECURE URLS 3327function atcb_secure_url(url, throwError = true) { 3328 if ( 3329 url.match( 3330 /((\.\.\/)|(\.\.\\)|(%2e%2e%2f)|(%252e%252e%252f)|(%2e%2e\/)|(%252e%252e\/)|(\.\.%2f)|(\.\.%252f)|(%2e%2e%5c)|(%252e%252e%255c)|(%2e%2e\\)|(%252e%252e\\)|(\.\.%5c)|(\.\.%255c)|(\.\.%c0%af)|(\.\.%25c0%25af)|(\.\.%c1%9c)|(\.\.%25c1%259c))/gi 3331 ) 3332 ) { 3333 if (throwError) { 3334 console.error( 3335 'Seems like the generated URL includes at least one security issue and got blocked. Please check the calendar button parameters!' 3336 ); 3337 } 3338 return false;
3339 } else { 3340 return true; 3341 } 3342} 3343 3344// SHARED FUNCTION TO VALIDATE EMAIL ADDRESSES 3345function atcb_validEmail(email, mx = false) { 3346 // rough format check first 3347 if (!/^.{0,70}@.{1,30}\.[\w.]{2,9}$/.test(email)) { 3348 return false; 3349 } 3350 // testing for mx records second, if activated 3351 if (mx) { 3352 // TODO: call external service to validate email address 3353 } 3354 return true; 3355} 3356 3357// SHARED FUNCTION TO REPLACE HTML PSEUDO ELEMENTS 3358function atcb_rewrite_html_elements(content, clear = false) { 3359 // standardize any line breaks 3360 content = content.replace(/<br\s*\/?>/gi, '\n'); 3361 // remove any pseudo elements, if necessary 3362 if (clear) { 3363 content = content.replace(/\[(|\/)(url|br|hr|p|b|strong|u|i|em|li|ul|ol|h\d)\]|((\|.*)\[\/url\])/gi, ''); 3364 // and build html for the rest 3365 // supporting: br, hr, p, strong, u, i, em, li, ul, ol, h (like h1, h2, h3, ...), url (= a) 3366 } else { 3367 content = content.replace(/\[(\/|)(br|hr|p|b|strong|u|i|em|li|ul|ol|h\d)\]/gi, '<$1$2>'); 3368 content = content.replace(/\[url\]([\w&$+.,:;=~!*'?@^%#|\s\-()/]*)\[\/url\]/gi, function (match, p1) { 3369 const urlText = p1.split('|'); 3370 const text = (function () { 3371 if (urlText.length > 1 && urlText[1] != '') { 3372 return urlText[1]; 3373 } else { 3374 return urlText[0]; 3375 } 3376 })(); 3377 return ( 3378 '<a href="' + urlText[0] + '" target="' + atcbDefaultTarget + '" rel="noopener">
3378' + text + '</a>' 3379 ); 3380 }); 3381 } 3382 return content; 3383} 3384 3385// SHARED FUNCTION TO CREATE INFO MODALS 3386function atcb_create_modal(data, icon = '', headline, content, buttons) { 3387 // setting the stage 3388 const bgOverlay = atcb_generate_bg_overlay('modal', 'click', data.lightMode); 3389 const infoModalWrapper = document.createElement('div'); 3390 infoModalWrapper.classList.add('atcb-modal', 'atcb-info-modal'); 3391 infoModalWrapper.tabIndex = 0; 3392 bgOverlay.appendChild(infoModalWrapper); 3393 document.body.appendChild(bgOverlay); 3394 document.body.classList.add('atcb-modal-no-scroll'); 3395 const parentButton = document.getElementById(data.identifier); 3396 if (parentButton != null) { 3397 parentButton.classList.add('atcb-active-modal'); 3398 } 3399 const infoModal = document.createElement('div'); 3400 infoModal.classList.add('atcb-modal-box'); 3401 infoModal.classList.add('atcb-' + data.lightMode); 3402 if (data.rtl) { 3403 infoModal.classList.add('atcb-rtl'); 3404 } 3405 infoModal.style.fontSize = data.size + 'px'; 3406 infoModalWrapper.appendChild(infoModal); 3407 // set overlay size just to be sure 3408 atcb_set_fullsize(bgOverlay); 3409 // adding closing button 3410 const infoModalClose = document.createElement('div'); 3411 infoModalClose.classList.add('atcb-modal-close'); 3412 infoModalClose.innerHTML = atcbIcon.close; 3413 infoModal.appendChild(infoModalClose); 3414 infoModalClose.addEventListener( 3415 'click', 3416 atcb_debounce(() => atcb_close()) 3417 ); 3418 infoModalClose.addEventListener( 3419 'keyup', 3420 atcb_debounce_leading((event) => { 3421 if (event.key == 'Enter') { 3422 event.preventDefault(); 3423 atcb_toggle('close', '', '', true); 3424 } 3425 }) 3426 ); 3427 if (buttons == null || buttons.length == 0) { 3428 infoModalClose.tabIndex = 0; 3429 infoModalClose.focus(); 3430 } 3431 // adding headline (incl. icon) 3432 const infoModalHeadline = document.createElement('div'); 3433 infoModalHeadline.classList.add('atcb-modal-headline'); 3434 infoModal.appendChild(infoModalHeadline); 3435 if (icon != '') { 3436 const infoModalHeadlineIcon = document.createElement('span'); 3437 infoModalHeadlineIcon.classList.add('atcb-modal-headline-icon'); 3438 infoModalHeadlineIcon.innerHTML = atcbIcon[`${icon}`]; 3439 infoModalHeadline.appendChild(infoModalHeadlineIcon); 3440 } 3441 let infoModalHeadlineText = document.createTextNode(headline); 3442 infoModalHeadline.appendChild(infoModalHeadlineText); 3443 // and text content 3444 const infoModalContent = document.createElement('div'); 3445 infoModalContent.classList.add('atcb-modal-content'); 3446 infoModalContent.innerHTML = content; 3447 infoModal.appendChild(infoModalContent); 3448 // and buttons (array of objects; attributes: href, type, label, primary(boolean)) 3449 if (buttons != null && buttons.length > 0) { 3450 const infoModalButtons = document.createElement('div'); 3451 infoModalButtons.classList.add('atcb-modal-buttons'); 3452 infoModal.appendChild(infoModalButtons); 3453 buttons.forEach((button, index) => { 3454 let infoModalButton; 3455 if (button.href != null && button.href != '') { 3456 infoModalButton = document.createElement('a'); 3457 infoModalButton.setAttribute('target', atcbDefaultTarget); 3458 infoModalButton.setAttribute('href', button.href); 3459 infoModalButton.setAttribute('rel', 'noopener'); 3460 } else { 3461 infoModalButton = document.createElement('button'); 3462 infoModalButton.type = 'button'; 3463 } 3464 infoModalButton.classList.add('atcb-modal-btn'); 3465 if (button.primary) { 3466 infoModalButton.classList.add('atcb-modal-btn-primary'); 3467 } 3468 if (button.label == null || button.label == '') { 3469 button.label = atcb_translate_hook('Click me', data.language, data); 3470 } 3471 infoModalButton.textContent = button.label; 3472 infoModalButtons.appendChild(infoModalButton); 3473 if (index == 0) { 3474 infoModalButton.focus(); 3475 } 3476 switch (button.type) { 3477 default: 3478 case 'close': 3479 infoModalButton.addEventListener( 3480 'click', 3481 atcb_debounce(() => atcb_close()) 3482 ); 3483 infoModalButton.addEventListener( 3484 'keyup', 3485 atcb_debounce((event) => { 3486 if (event.key == 'Enter') { 3487 atcb_toggle('close', '', '', true); 3488 } 3489 }) 3490 ); 3491 break; 3492 } 3493 }); 3494 } 3495} 3496 3497// SHARED FUNCTION TO CALCULATE THE POSITION OF THE DROPDOWN LIST 3498function atcb_position_list(trigger, list, blockUpwards = false, resize = false) { 3499 // check for position anchor 3500 let anchorSet = false;
3501 const originalTrigger = trigger; 3502 if (trigger.querySelector('.atcb-dropdown-anchor') !== null) { 3503 trigger = trigger.querySelector('.atcb-dropdown-anchor'); 3504 anchorSet = true; 3505 } 3506 // calculate position 3507 let triggerDim = trigger.getBoundingClientRect(); 3508 let listDim = list.getBoundingClientRect(); 3509 const btnDim = originalTrigger.getBoundingClientRect(); 3510 if (anchorSet === true && !list.classList.contains('atcb-dropoverlay')) { 3511 // in the regular case, we also check for the ideal direction 3512 // not in the !updateDirection case and not if there is not enough space above 3513 const viewportHeight = document.documentElement.clientHeight; 3514 if ( 3515 (list.classList.contains('atcb-dropup') && resize) || 3516 (!blockUpwards && 3517 triggerDim.top + listDim.height > viewportHeight - 20 && 3518 2 * btnDim.top + btnDim.height - triggerDim.top - listDim.height > 20) 3519 ) { 3520 originalTrigger.classList.add('atcb-dropup'); 3521 list.classList.add('atcb-dropup'); 3522 list.style.bottom = 3523 2 * viewportHeight - 3524 (viewportHeight + (btnDim.top + (btnDim.top + btnDim.height - triggerDim.top))) - 3525 window.scrollY + 3526 'px'; 3527 } else { 3528 list.style.top = window.scrollY + triggerDim.top + 'px'; 3529 if (originalTrigger.classList.contains('atcb-dropup')) { 3530 originalTrigger.classList.remove('atcb-dropup'); 3531 } 3532 } 3533 // read trigger dimensions again, since after adjusting the top value of the list, something might have changed (e.g. re-adjustment due to missing scrollbars at this point in time) 3534 triggerDim = trigger.getBoundingClientRect(); 3535 if (list.classList.contains('atcb-style-round') || list.classList.contains('atcb-style-text')) { 3536 list.style.minWidth = triggerDim.width + 'px'; 3537 } else { 3538 list.style.width = triggerDim.width + 'px'; 3539 } 3540 // read list dimensions again, since we altered the width in the step before 3541 listDim = list.getBoundingClientRect(); 3542 list.style.left = triggerDim.left - (listDim.width - triggerDim.width) / 2 + 'px'; 3543 } else { 3544 // when there is no anchor set (only the case with custom implementations) or the listStyle is set respectively (overlay), we render the modal centered above the trigger 3545 // make sure the trigger is not moved over it via CSS in this case! 3546 let listWidth = triggerDim.width + 20 + 'px'; 3547 list.style.minWidth = listWidth; 3548 // read list dimensions again, since we altered the width in the step before 3549 listDim = list.getBoundingClientRect(); 3550 list.style.top = window.scrollY + btnDim.top + btnDim.height / 2 - listDim.height / 2 + 'px'; 3551 list.style.left = triggerDim.left - (listDim.width - triggerDim.width) / 2 + 'px'; 3552 } 3553 const atcbL = document.getElementById('add-to-calendar-button-reference'); 3554 if (atcbL) { 3555 if (originalTrigger.classList.contains('atcb-dropup')) { 3556 atcbL.style.top = window.scrollY + btnDim.top + btnDim.height + 'px'; 3557 atcbL.style.left = btnDim.left + (btnDim.width - 150) / 2 + 'px'; 3558 } else { 3559 listDim = list.getBoundingClientRect(); 3560 if (originalTrigger.classList.contains('atcb-dropoverlay') || !anchorSet) { 3561 atcbL.style.top = window.scrollY + listDim.top + listDim.height + 'px'; 3562 } else { 3563 atcbL.style.top = window.scrollY + triggerDim.top + listDim.height + 'px'; 3564 } 3565 atcbL.style.left = listDim.left + (listDim.width - 150) / 2 + 'px'; 3566 } 3567 } 3568} 3569 3570// SHARED FUNCTION TO DEFINE WIDTH AND HEIGHT FOR "FULLSCREEN" FULLSIZE ELEMENTS 3571function atcb_set_fullsize(el) { 3572 el.style.width = window.innerWidth + 'px'; 3573 el.style.height = window.innerHeight + 100 + 'px'; 3574} 3575 3576// SHARED FUNCTION TO GENERATE UUIDs 3577function atcb_generate_uuid() { 3578 //return crypto.randomUUID(); // lacking support of Safari < 15.4 and Firefox < 95, which is to important for now 3579 return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => 3580 (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) 3581 ); 3582} 3583 3584// SHARED DEBOUNCE AND THROTTLE FUNCTIONS 3585// going for last call debounce 3586function atcb_debounce(func, timeout = 200) { 3587 let timer; 3588 return (...args) => { 3589 clearTimeout(timer); 3590 timer = setTimeout(() => { 3591 func.apply(this, args); 3592 }, timeout); 3593 }; 3594} 3595// dropping subsequent calls debounce 3596function atcb_debounce_leading(func, timeout = 300) { 3597 let timer; 3598 return (...args) => { 3599 if (!timer) { 3600 func.apply(this, args); 3601 } 3602 clearTimeout(timer); 3603 timer = setTimeout(() => { 3604 timer = undefined; 3605 }, timeout); 3606 }; 3607} 3608// throttle
3609function atcb_throttle(func, delay = 10) { 3610 let result; 3611 let timeout = null; 3612 let previous = 0; 3613 let later = (...args) => { 3614 previous = Date.now(); 3615 timeout = null; 3616 result = func.apply(this, args); 3617 }; 3618 return (...args) => { 3619 let now = Date.now(); 3620 let remaining = delay - (now - previous); 3621 if (remaining <= 0 || remaining > delay) { 3622 if (timeout) { 3623 clearTimeout(timeout); 3624 timeout = null; 3625 } 3626 previous = now; 3627 result = func.apply(this, args); 3628 } else if (!timeout) { 3629 timeout = setTimeout(later, remaining); 3630 } 3631 return result; 3632 }; 3633} 3634 3635// GLOBAL KEYBOARD AND DEVICE LISTENERS 3636if (isBrowser()) { 3637 // global listener to ESC key to close dropdown 3638 document.addEventListener( 3639 'keyup', 3640 atcb_debounce_leading((event) => { 3641 if (event.key === 'Escape') { 3642 atcb_toggle('close', '', '', true); 3643 } 3644 }) 3645 ); 3646 // global listener to arrow key optionlist navigation 3647 document.addEventListener('keydown', (event) => { 3648 if ( 3649 document.querySelector('.atcb-list') && 3650 (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Tab') 3651 ) { 3652 let targetFocus = 0; 3653 let currFocusOption = document.activeElement; 3654 const optionListCount = document.querySelectorAll('.atcb-list-item').length; 3655 if (currFocusOption.classList.contains('atcb-list-item')) { 3656 if (event.key === 'ArrowDown' && currFocusOption.dataset.optionNumber < optionListCount) { 3657 event.preventDefault(); 3658 targetFocus = parseInt(currFocusOption.dataset.optionNumber) + 1; 3659 } else if (event.key === 'ArrowUp' && currFocusOption.dataset.optionNumber >= 1) { 3660 event.preventDefault(); 3661 targetFocus = parseInt(currFocusOption.dataset.optionNumber) - 1; 3662 } 3663 if (targetFocus > 0) { 3664 document.querySelector('.atcb-list-item[data-option-number="' + targetFocus + '"]').focus(); 3665 } 3666 } else { 3667 event.preventDefault(); 3668 if ( 3669 document.querySelector('.atcb-list-wrapper.atcb-dropup') && 3670 (event.key === 'ArrowDown' || event.key === 'ArrowUp') 3671 ) { 3672 document.querySelector('.atcb-list-item[data-option-number="' + optionListCount + '"]').focus(); 3673 } else { 3674 document.querySelector('.atcb-list-item[data-option-number="1"]').focus(); 3675 } 3676 } 3677 } 3678 }); 3679 // Global listener to any screen changes 3680 window.addEventListener( 3681 'resize', 3682 atcb_throttle(() => { 3683 const activeOverlay = document.getElementById('atcb-bgoverlay'); 3684 if (activeOverlay != null) { 3685 atcb_set_fullsize(activeOverlay); 3686 } 3687 const activeButton = document.querySelector('.atcb-active'); 3688 const activeList = document.querySelector('.atcb-dropdown'); 3689 if (activeButton != null && activeList != null) { 3690 atcb_position_list(activeButton, activeList, false, true); 3691 } 3692 }) 3693 ); 3694} 3695 3696// TRANSLATIONS 3697// the database object 3698const i18nStrings = { 3699 en: { 3700 'Add to Calendar': 'Add to Calendar', 3701 'iCal File': 'iCal File', 3702 Close: 'Close', 3703 'Close Selection': 'Close Selection', 3704 'Click me': 'Click me', 3705 'WebView iCal headline': 'Open your browser', 3706 'WebView iCal info': 3707 'Unfortunately, in-app browsers have problems with the way we generate the calendar file.', 3708 'WebView iCal solution 1': "We automatically put a magical URL into your phone's clipboard.", 3709 'WebView iCal solution 2': 3710 '<ol><li><strong>Open another browser</strong> on your phone, ...</li><li><strong>Paste</strong> the clipboard content and go.</li></ol>', 3711 'Crios iCal headline': 'Open Safari', 3712 'Crios iCal info': 3713 'Unfortunately, Chrome on iOS has problems with the way we generate the calendar file.', 3714 'Crios iCal solution 2': 3715 '<ol><li><strong>Open Safari</strong>, ...</li><li><strong>Paste</strong> the clipboard content and go.</li></ol>', 3716 }, 3717 de: { 3718 'Add to Calendar': 'Im Kalender speichern', 3719 'iCal File': 'iCal-Datei', 3720 Close: 'SchlieÃen', 3721 'Close Selection': 'Auswahl schlieÃen', 3722 'Click me': 'Klick mich', 3723 'WebView iCal headline': 'Ãffne deinen Browser', 3724 'WebView iCal info': 3725 'Leider haben In-App-Browser Probleme mit der Art, wie wir Kalender-Dateien erzeugen.', 3726 'WebView iCal solution 1': 3727 'Wir haben automatisch eine magische URL in die Zwischenablage deines Smartphones kopiert.', 3728 'WebView iCal solution 2': 3729 '<ol><li><strong>Ãffne einen anderen Browser</strong> auf deinem Smartphone, ...</li><li>Nutze die <strong>Einfügen</strong>-Funktion, um fortzufahren.</li></ol>', 3730 'Crios iCal headline': 'Ãffne Safari', 3731 'Crios iCal info': 'Leider Chrome unter iOS Probleme mit der Art, wie wir Kalender-Dateien erzeugen.', 3732 'Crios iCal solution 2': 3733 '<ol><li><strong>Ãffne Safari</strong>, ...</li><li>Nutze die <strong>Einfügen</strong>-Funktion, um fortzufahren.</li></ol>', 3734 }, 3735 es: { 3736 'Add to Calendar': 'Añadir al Calendario', 3737 'iCal File': 'iCal Ficha', 3738 Close: 'Ciérralo', 3739 'Close Selection': 'Cerrar Selección', 3740 'Click me': 'Haz clic mÃ', 3741 'WebView iCal headline': 'Abra su browser', 3742 'WebView iCal info':
3743 'Lamentablemente, los browsers in-app tienen problemas con la forma en que generamos el archivo del calendario.', 3744 'WebView iCal solution 1': 3745 'Hemos copiado automáticamente una URL mágica en el portapapeles de tu smartphone.', 3746 'WebView iCal solution 2': 3747 '<ol><li><strong>Abre otro browser</strong> en tu smartphone, ...</li><li>Utilice la función de <strong>pegar</strong> para continuar.</li></ol>', 3748 'Crios iCal headline': 'Abrir Safari', 3749 'Crios iCal info': 3750 'Lamentablemente, Chrome en iOS tiene problemas con la forma de generar el archivo de calendario.', 3751 'Crios iCal solution 2': 3752 '<ol><li><strong>Abrir Safari</strong>, ...</li><li>Utilice la función de <strong>pegar</strong> para continuar.</li></ol>', 3753 }, 3754 pt: { 3755 'Add to Calendar': 'Incluir no Calendário', 3756 'iCal File': 'Ficheiro iCal', 3757 Close: 'Fechar', 3758 'Close Selection': 'Fechar selecção', 3759 'Click me': 'Clicar-me', 3760 'WebView iCal headline': 'Abra o seu browser', 3761 'WebView iCal info': 3762 'Infelizmente, os navegadores em tampas têm problemas com a forma como geramos o ficheiro de calendário.', 3763 'WebView iCal solution 1': 3764 'Copiámos automaticamente um URL mágico para a área de transferência do seu smartphone.', 3765 'WebView iCal solution 2': 3766 '<ol><li><strong>Abrir outro browser</strong> en tu smartphone, ...</li><li>Use a função <forte>colar</strong> para continuar.</li></ol>', 3767 'Crios iCal headline': 'Safari aberto', 3768 'Crios iCal info': 3769 'Infelizmente, o cromado no iOS tem problemas com a forma como geramos o ficheiro do calendário.', 3770 'Crios iCal solution 2': 3771 '<ol><li><strong>Safari aberto</strong>, ...</li><li>Use a função <forte>colar</strong> para continuar.</li></ol>', 3772 }, 3773 fr: { 3774 'Add to Calendar': 'Ajout au Calendrier', 3775 'iCal File': 'iCal Fichier', 3776 Close: 'Fermez', 3777 'Close Selection': 'Fermez la sélection', 3778 'Click me': 'Cliquez-moi', 3779 'WebView iCal headline': 'Ouvrez votre navigateur', 3780 'WebView iCal info': 3781 'Malheureusement, les navigateurs in-app ont des problèmes avec la manière dont nous créons les fichiers de calendrier.', 3782 'WebView iCal solution 1': 3783 'Nous avons automatiquement copié une URL magique dans le presse-papiers de ton smartphone.', 3784 'WebView iCal solution 2': 3785 '<ol><li><strong>Ouvre un autre navigateur</strong> sur ton smartphone, ...</li><li>Utilise la fonction <strong>insérer</strong> pour continuer.</li></ol>', 3786 'Crios iCal headline': 'Ouvre Safari', 3787 'Crios iCal info': 3788 'Malheureusement, Chrome sur iOS a des problèmes avec la façon dont nous générons le fichier du calendrier.', 3789 'Crios iCal solution 2': 3790 '<ol><li><strong>Ouvre Safari</strong>, ...</li><li>Utilise la fonction <strong>insérer</strong> pour continuer.</li></ol>', 3791 }, 3792 nl: { 3793 'Add to Calendar': 'Opslaan in Kalender', 3794 'iCal File': 'iCal File', 3795 Close: 'Sluiten', 3796 'Close Selection': 'Sluit selectie', 3797 'Click me': 'Klik me', 3798 'WebView iCal headline': 'Open uw browser', 3799 'WebView iCal info': 3800 'Helaas hebben in-app browsers problemen met de manier waarop wij kalenderbestanden maken.', 3801 'WebView iCal solution 1': 3802 'We hebben automatisch een magische URL naar het klembord van uw smartphone gekopieerd.', 3803 'WebView iCal solution 2': 3804 '<ol><li><strong>Open een andere browser</strong> op uw smartphone, ...</li><li>Gebruik de <strong>insert</strong> functie om verder te gaan.</li></ol>', 3805 'Crios iCal headline': 'Open Safari', 3806 'Crios iCal info': 3807 'Helaas heeft Chrome op iOS problemen met de manier waarop we het kalenderbestand genereren.', 3808 'Crios iCal solution 2': 3809 '<ol><li><strong>Open Safari</strong>, ...</li><li>Gebruik de <strong>insert</strong> functie om verder te gaan.</li></ol>', 3810 }, 3811 tr: { 3812 'Add to Calendar': 'Takvime Ekle', 3813 'iCal File': 'iCal Dosyası', 3814 Close: 'Kapat', 3815 'Close Selection': 'Seçimi kapat', 3816 'Click me': 'Beni tıklayın', 3817 'WebView iCal headline': 'Tarayıcınızı açın', 3818 'WebView iCal info': 3819 'Ne yazık ki, uygulama içi tarayıcılar takvim dosyalarını oluÅturma Åeklimizle ilgili sorunlar yaÅıyor.', 3820 'WebView iCal solution 1': 'Akıllı telefonunuzun panosuna otomatik olarak sihirli bir URL kopyaladık.', 3821 'WebView iCal solution 2': 3822 '<ol><li><strong>Akıllı telefonunuzda baÅka bir tarayıcı açın</strong>, ...</li><li>Devam etmek için <strong>insert</strong> fonksiyonunu kullanın.</li></ol>', 3823 'Crios iCal headline': 'Açık Safari', 3824 'Crios iCal info': 3825 "Ne yazık ki iOS'ta Chrome'un takvim dosyası oluÅturma yöntemiyle ilgili sorunları var.", 3826 'Crios iCal solution 2': 3827 '<ol><li><strong>Açık Safari</strong>, ...</li><li>Devam etmek için <strong>insert</strong> fonksiyonunu kullanın.</li></ol>', 3828 }, 3829 zh: { 3830 'Add to Calendar': 'æ·»å å°æ¥å', 3831 'iCal File': 'iCal æä»¶', 3832 Close: 'å ³', 3833 'Close Selection': 'å ³ééæ©', 3834 'Click me': 'ç¹æ',
3835 'WebView iCal headline': 'æå¼æµè§å¨', 3836 'WebView iCal info': 'ä¸å¹¸çæ¯ï¼åºç¨å æµè§å¨å¨æä»¬çææ¥åæä»¶çæ¹å¼ä¸åå¨é®é¢.', 3837 'WebView iCal solution 1': 'æä»¬ä¼èªå¨å°ä¸ä¸ªç¥å¥ç URL æ¾å ¥æ¨ææºçåªè´´æ¿.', 3838 'WebView iCal solution 2': 3839 '<ol><li>æå¼ææºä¸çä»»ä½å ¶ä»æµè§å¨, ...</li><li>ç²è´´åªè´´æ¿å 容并å¼å§.</li></ol>', 3840 'Crios iCal headline': 'æå¼ Safari', 3841 'Crios iCal info': 'ä¸å¹¸çæ¯ï¼iOS ä¸ç Chrome å¨æä»¬çææ¥åæä»¶çæ¹å¼ä¸åå¨é®é¢.', 3842 'Crios iCal solution 2': 3843 '<ol><li><strong>æå¼ Safari</strong>, ...</li><li>ç²è´´åªè´´æ¿å 容并å¼å§.</li></ol>', 3844 }, 3845 ar: { 3846 'Add to Calendar': 'Ø¥Ø¶Ø§ÙØ© Ø¥ÙÙ Ø§ÙØªÙÙÙÙ ', 3847 'iCal File': 'Ù ÙÙ iCal', 3848 Close: 'ÙØ±Ùب', 3849 'Close Selection': 'Ø¥ØºÙØ§Ù Ø§ÙØªØØ¯Ùد', 3850 'Click me': 'اÙÙØ± ÙÙÙ ÙÙ', 3851 'WebView iCal headline': 'Ø§ÙØªØ اÙ٠ستعرض Ø§ÙØ®Ø§Øµ بÙ', 3852 'WebView iCal info': 'ÙØ³ÙØ¡ Ø§ÙØØ¸ Ø ØªÙØ§Ø¬Ù اÙÙ ØªØµÙØØ§Øª Ø¯Ø§Ø®Ù Ø§ÙØªØ·Ø¨Ù٠٠شاÙÙ Ù٠طرÙÙØ© Ø¥ÙØ´Ø§Ø¡ Ù ÙÙ Ø§ÙØªÙÙÙÙ .', 3853 'WebView iCal solution 1': 'ÙØ¶Ø¹ تÙÙØ§Ø¦ÙÙØ§ عÙÙØ§Ù ÙÙØ¨ Ø³ØØ±ÙÙØ§ ÙÙ ØØ§Ùظة ÙØ§ØªÙÙ.', 3854 'WebView iCal solution 2': 3855 '<ol><li>Ø§ÙØªØ Ø£Ù Ù ØªØµÙØ آخر عÙÙ ÙØ§ØªÙÙ Ø§ÙØ°ÙÙ, ...</li><li>Ø§ÙØµÙ Ù ØØªÙÙ Ø§ÙØØ§ÙØ¸Ø© ÙØ§Ø°Ùب.</li></ol>', 3856 'Crios iCal headline': 'Ø§ÙØªØ Safari', 3857 'Crios iCal info': 'ÙØ³ÙØ¡ Ø§ÙØØ¸ Ø ÙÙØ§Ø¬Ù Chrome عÙÙ iOS ٠شاÙÙ Ù٠طرÙÙØ© Ø¥ÙØ´Ø§Ø¡ Ù ÙÙ Ø§ÙØªÙÙÙÙ .', 3858 'Crios iCal solution 2': 3859 '<ol><li><strong>Ø§ÙØªØ Safari</strong>, ...</li><li>Ø§ÙØµÙ Ù ØØªÙÙ Ø§ÙØØ§ÙØ¸Ø© ÙØ§Ø°Ùب.</li></ol>', 3860 }, 3861 hi: { 3862 'Add to Calendar': 'à¤à¥à¤²à¥à¤à¤¡à¤° मà¥à¤ à¤à¥à¤¡à¤¼à¥à¤', 3863 'iCal File': 'iCal फ़ाà¤à¤²', 3864 Close: 'बà¤à¤¦ à¤à¤°à¤¨à¤¾', 3865 'Close Selection': 'à¤à¤¯à¤¨ बà¤à¤¦ à¤à¤°à¥à¤', 3866 'Click me': 'मà¥à¤à¥ à¤à¥à¤²à¤¿à¤ à¤à¤°à¥à¤', 3867 'WebView iCal headline': 'ठपना बà¥à¤°à¤¾à¤à¤à¤¼à¤° à¤à¥à¤²à¥à¤', 3868 'WebView iCal info': 'दà¥à¤°à¥à¤à¤¾à¤à¥à¤¯ सà¥, à¤à¤¨-à¤à¤ª बà¥à¤°à¤¾à¤à¤à¤¼à¤° मà¥à¤ à¤à¥à¤²à¥à¤à¤¡à¤° फ़ाà¤à¤² बनानॠà¤à¥ तरà¥à¤à¥ मà¥à¤ समसà¥à¤¯à¤¾à¤à¤ हà¥à¤à¥¤', 3869 'WebView iCal solution 1': 'हम सà¥à¤µà¤à¤¾à¤²à¤¿à¤¤ रà¥à¤ª सॠà¤à¤ªà¤à¥ फ़à¥à¤¨ à¤à¥ à¤à¥à¤²à¤¿à¤ªà¤¬à¥à¤°à¥à¤¡ मà¥à¤ à¤à¤ à¤à¤¾à¤¦à¥à¤ URL डालतॠहà¥à¤à¥¤', 3870 'WebView iCal solution 2': 3871 '<ol><li>ठपनॠफ़à¥à¤¨ पर <strong>दà¥à¤¸à¤°à¤¾ बà¥à¤°à¤¾à¤à¤à¤¼à¤° à¤à¥à¤²à¥à¤</strong>, ...</li><li>à¤à¥à¤²à¤¿à¤ªà¤¬à¥à¤°à¥à¤¡ सामà¤à¥à¤°à¥ <strong>à¤à¤¿à¤ªà¤à¤¾à¤à¤</strong> à¤à¤° à¤à¤¾à¤à¤à¥¤</li></ol>', 3872 'Crios iCal headline': 'सफारॠà¤à¥à¤²à¥à¤', 3873 'Crios iCal info': 3874 'दà¥à¤°à¥à¤à¤¾à¤à¥à¤¯ सà¥, iOS पर Chrome à¤à¥ à¤à¥à¤²à¥à¤à¤¡à¤° फ़ाà¤à¤² à¤à¥à¤¨à¤°à¥à¤ à¤à¤°à¤¨à¥ à¤à¥ हमारॠतरà¥à¤à¥ मà¥à¤ समसà¥à¤¯à¤¾ हà¥à¥¤', 3875 'Crios iCal solution 2': 3876 '<ol><li><strong>सफारॠà¤à¥à¤²à¥à¤</strong>, ...</li><li>à¤à¥à¤²à¤¿à¤ªà¤¬à¥à¤°à¥à¤¡ सामà¤à¥à¤°à¥ <strong>à¤à¤¿à¤ªà¤à¤¾à¤à¤</strong> à¤à¤° à¤à¤¾à¤à¤à¥¤</li></ol>', 3877 }, 3878 pl: { 3879 'Add to Calendar': 'Dodaj do kalendarza', 3880 'iCal File': 'Plik iCal', 3881 Close: 'Zamknij', 3882 'Close Selection': 'Zamknij wybór', 3883 'Click me': 'Kliknij mnie', 3884 'WebView iCal headline': 'Otwórz przeglÄ darkÄ', 3885 'WebView iCal info': 3886 'Niestety, przeglÄ darki in-app majÄ problemy ze sposobem, w jaki generujemy plik kalendarza.', 3887 'WebView iCal solution 1': 'Automatycznie umieszczamy magiczny adres URL w schowku telefonu.', 3888 'WebView iCal solution 2': 3889 '<ol><li><strong>Otwórz innÄ przeglÄ darkÄ</strong> w swoim telefonie, ...</li><li><strong>Wklej</strong> zawartoÅÄ schowka i ruszaj.</li></ol>', 3890 'Crios iCal headline': 'Otwórz Safari', 3891 'Crios iCal info': 'Niestety, Chrome na iOS ma problemy ze sposobem generowania pliku kalendarza.', 3892 'Crios iCal solution 2': 3893 '<ol><li><strong>Otwórz Safari</strong>, ...</li><li><strong>Wklej</strong> zawartoÅÄ schowka i ruszaj.</li></ol>', 3894 }, 3895 id: { 3896 'Add to Calendar': 'Tambahkan ke Kalender', 3897 'iCal File': 'File iCal', 3898 Close: 'Tutup', 3899 'Close Selection': 'Seleksi Tutup', 3900 'Click me': 'Klik saya', 3901 'WebView iCal headline': 'Buka browser Anda', 3902 'WebView iCal info': 3903 'Sayangnya, browser dalam aplikasi memiliki masalah dengan cara kami menghasilkan file kalender.', 3904 'WebView iCal solution 1': 'Kami secara otomatis memasukkan URL ajaib ke
3904clipboard ponsel Anda.', 3905 'WebView iCal solution 2': 3906 '<ol><li><strong>Buka peramban lain</strong> pada ponsel Anda, ...</li><li>Tempelkan konten clipboard dan pergi.</li></ol>', 3907 'Crios iCal headline': 'Buka Safari', 3908 'Crios iCal info': 3909 'Sayangnya, Chrome di iOS memiliki masalah dengan cara kami menghasilkan file kalender.', 3910 'Crios iCal solution 2': 3911 '<ol><li><strong>Buka Safari</strong>, ...</li><li>Tempelkan konten clipboard dan pergi.</li></ol>', 3912 }, 3913 no: { 3914 'Add to Calendar': 'Legg til i kalenderen', 3915 'iCal File': 'iCal-fil', 3916 Close: 'Lukk', 3917 'Close Selection': 'Lukk utvalg', 3918 'Click me': 'Klikk pÃ¥ meg', 3919 'WebView iCal headline': 'à pne nettleseren din', 3920 'WebView iCal info': 3921 'Dessverre har nettlesere i appen problemer med mÃ¥ten vi genererer kalenderfilen pÃ¥.', 3922 'WebView iCal solution 1': 'Vi legger automatisk inn en magisk URL i telefonens utklippstavle.', 3923 'WebView iCal solution 2': 3924 '<ol><li><strong>à pne en annen nettleser</strong> pÃ¥ telefonen, ...</li><li><strong>Lim inn</strong> innholdet pÃ¥ utklippstavlen og gÃ¥.</li></ol>', 3925 'Crios iCal headline': 'à pne Safari', 3926 'Crios iCal info': 'Dessverre har Chrome pÃ¥ iOS problemer med mÃ¥ten vi genererer kalenderfilen pÃ¥.', 3927 'Crios iCal solution 2': 3928 '<ol><li><strong>à pne Safari</strong>, ...</li><li><strong>Lim inn</strong> innholdet pÃ¥ utklippstavlen og gÃ¥.</li></ol>', 3929 }, 3930 fi: { 3931 'Add to Calendar': 'Lisää kalenteriin', 3932 'iCal File': 'iCal-tiedosto', 3933 Close: 'Sulje', 3934 'Close Selection': 'Sulje valinta', 3935 'Click me': 'Klikkaa minua', 3936 'WebView iCal headline': 'Avaa selain', 3937 'WebView iCal info': 3938 'Valitettavasti sovelluksen sisäisillä selaimilla on ongelmia kalenteritiedoston luomisessa.', 3939 'WebView iCal solution 1': 'Laitamme automaattisesti maagisen URL-osoitteen puhelimesi leikepöydälle.', 3940 'WebView iCal solution 2': 3941 '<ol><li><strong>Avaa toinen selain</strong> puhelimessasi., ...</li><li><strong>liitä</strong> leikepöydän sisältö ja lähde.</li></ol>', 3942 'Crios iCal headline': 'Avaa Safari', 3943 'Crios iCal info': 'Valitettavasti iOS:n Chromessa on ongelmia kalenteritiedoston luomisessa.', 3944 'Crios iCal solution 2': 3945 '<ol><li><strong>Avaa Safari</strong>, ...</li><li><strong>liitä</strong> leikepöydän sisältö ja lähde.</li></ol>', 3946 }, 3947 sv: { 3948 'Add to Calendar': 'Lägg till i kalender', 3949 'iCal File': 'iCal-fil', 3950 Close: 'Stäng', 3951 'Close Selection': 'Stäng urvalet', 3952 'Click me': 'Klicka pÃ¥ mig', 3953 'WebView iCal headline': 'Ãppna din webbläsare', 3954 'WebView iCal info': 'Tyvärr har webbläsare i appen problem med hur vi genererar kalenderfilen.', 3955 'WebView iCal solution 1': 'Vi lägger automatiskt in en magisk webbadress i telefonens klippbräda.', 3956 'WebView iCal solution 2': 3957 '<ol><li><strong>Ãppna en annan webbläsare</strong> pÃ¥ telefonen, ...</li><li><strong>Insätt</strong> innehÃ¥llet i klippbordet och kör.</li></ol>', 3958 'Crios iCal headline': 'Ãppna Safari', 3959 'Crios iCal info': 'Tyvärr har Chrome pÃ¥ iOS problem med hur vi genererar kalenderfilen.', 3960 'Crios iCal solution 2': 3961 '<ol><li><strong>Ãppna Safari</strong>, ...</li><li><strong>Insätt</strong> innehÃ¥llet i klippbordet och kör.</li></ol>', 3962 }, 3963 cs: { 3964 'Add to Calendar': 'PÅidat do kalendáÅe', 3965 'iCal File': 'Soubor iCal', 3966 Close: 'ZavÅÃt', 3967 'Close Selection': 'ZavÅÃt výbÄr', 3968 'Click me': 'KliknÄte na mÄ', 3969 'WebView iCal headline': 'OtevÅete prohlÞeÄ', 3970 'WebView iCal info': 3971 'ProhlÞeÄe v aplikacÃch majà bohužel problémy se způsobem generovánà souboru kalendáÅe.', 3972 'WebView iCal solution 1': 'Do schránky telefonu automaticky vložÃme kouzelnou adresu URL.', 3973 'WebView iCal solution 2': 3974 '<ol><li><strong>OtevÅenà jiného prohlÞeÄe</strong> v telefonu, ...</li><li><strong>Vložte</strong> obsah schránky a pÅejdÄte.</li></ol>', 3975 'Crios iCal headline': 'OtevÅÃt Safari', 3976 'Crios iCal info': 'Chrome v systému iOS má bohužel problémy se způsobem generovánà souboru kalendáÅe.', 3977 'Crios iCal solution 2': 3978 '<ol><li><strong>OtevÅÃt Safari</strong>, ...</li><li><strong>Vložte</strong>
3978 obsah schránky a pÅejdÄte.</li></ol>', 3979 }, 3980 ja: { 3981 'Add to Calendar': 'ã«ã¬ã³ãã¼ã«è¿½å ', 3982 'iCal File': 'iCalãã¡ã¤ã«', 3983 Close: 'éãã', 3984 'Close Selection': 'ã¯ãã¼ãºé¸æ', 3985 'Click me': 'ã¯ãªãã¯ãã¦ãã ãã', 3986 'WebView iCal headline': 'ãã©ã¦ã¶ãèµ·åãã', 3987 'WebView iCal info': 'æ®å¿µãªãããã¢ããªå ãã©ã¦ã¶ã¯ãã«ã¬ã³ãã¼ãã¡ã¤ã«ã®çææ¹æ³ã«åé¡ãããã¾ãã', 3988 'WebView iCal solution 1': 'ããªãã®æºå¸¯é»è©±ã®ã¯ãªãããã¼ãã«ãéæ³ã®ãããªURLãèªåçã«å ¥ãã¾ãã', 3989 'WebView iCal solution 2': 3990 '<ol><li>ã¹ãã¼ããã©ã³ã§å¥ã®ãã©ã¦ã¶ãèµ·åãã, ...</li><li>ã¯ãªãããã¼ãã®å 容ãè²¼ãä»ãã¦è¡ãã</li></ol>', 3991 'Crios iCal headline': 'ãªã¼ãã³Safari', 3992 'Crios iCal info': 'æ®å¿µãªãããiOSçChromeã§ã¯ãã«ã¬ã³ãã¼ãã¡ã¤ã«ã®çææ¹æ³ã«åé¡ãããã¾ãã', 3993 'Crios iCal solution 2': 3994 '<ol><li><strong>ãªã¼ãã³Safari</strong>, ...</li><li>ã¯ãªãããã¼ãã®å 容ãè²¼ãä»ãã¦è¡ãã</li></ol>', 3995 }, 3996 it: { 3997 'Add to Calendar': 'Aggiungi al calendario', 3998 'iCal File': 'File iCal', 3999 Close: 'Chiudere', 4000 'Close Selection': 'Chiudere la selezione', 4001 'Click me': 'Clicca su di me', 4002 'WebView iCal headline': 'Aprire il browser', 4003 'WebView iCal info': 4004 'Purtroppo i browser in-app hanno problemi con il modo in cui generiamo il file del calendario.', 4005 'WebView iCal solution 1': 'Inseriamo automaticamente un URL magico negli appunti del telefono.', 4006 'WebView iCal solution 2': 4007 '<ol><li><strong>Aprire un altro browser</strong> sul cellulare, ...</li><li><strong>Incollare</strong> il contenuto degli appunti e partire.</li></ol>', 4008 'Crios iCal headline': 'Aprire Safari', 4009 'Crios iCal info': 4010 'Purtroppo, Chrome su iOS ha problemi con il modo in cui generiamo il file del calendario.', 4011 'Crios iCal solution 2': 4012 '<ol><li><strong>Aprire Safari</strong>, ...</li><li><strong>Incollare</strong> il contenuto degli appunti e partire.</li></ol>', 4013 }, 4014 ko: { 4015 'Add to Calendar': 'ìºë¦°ëì ì¶ê°', 4016 'iCal File': 'iCal íì¼', 4017 Close: 'ë«ë¤', 4018 'Close Selection': 'ì í ë«ê¸°', 4019 'Click me': 'í´ë¦ í´ì£¼ì¸ì', 4020 'WebView iCal headline': 'ë¸ë¼ì°ì ì´ê¸°', 4021 'WebView iCal info': 'ë¶ííë ì¸ì± ë¸ë¼ì°ì ë ìºë¦°ë íì¼ì ìì±íë ë°©ìì 문ì ê° ììµëë¤.', 4022 'WebView iCal solution 1': 'ìëì¼ë¡ ë§ë²ì URLì í´ëì íì í´ë¦½ë³´ëì ë£ìµëë¤.', 4023 'WebView iCal solution 2': 4024 '<ol><li>í´ëì íìì ë¤ë¥¸ ë¸ë¼ì°ì ì´ê¸°, ...</li><li>í´ë¦½ë³´ë ë´ì©ì ë¶ì¬ë£ê³ ì´ëí©ëë¤.</li></ol>', 4025 'Crios iCal headline': 'Safari ì´ê¸°', 4026 'Crios iCal info': 'ë¶ííë iOSì Chromeì ìºë¦°ë íì¼ì ìì±íë ë°©ìì 문ì ê° ììµëë¤.', 4027 'Crios iCal solution 2': 4028 '<ol><li><strong>Safari ì´ê¸°</strong>, ...</li><li>í´ë¦½ë³´ë ë´ì©ì ë¶ì¬ë£ê³ ì´ëí©ëë¤.</li></ol>', 4029 }, 4030 vi: { 4031 'Add to Calendar': 'Thêm và o Lá»ch', 4032 'iCal File': 'Tá»p iCal', 4033 Close: 'Äóng', 4034 'Close Selection': 'Äóng lá»±a chá»n', 4035 'Click me': 'Nhấp và o Äây', 4036 'WebView iCal headline': 'Má» trình duyá»t cá»§a bạn', 4037 'WebView iCal info': 4038 'Rất tiếc, các trình duyá»t trong ứng dụng gặp sá»± cá» vá»i cách chúng tôi tạo tá»p lá»ch.', 4039 'WebView iCal solution 1': 4040 'Chúng tôi tá»± Äá»ng Äặt má»t URL kỳ diá»u và o khay nhá» tạm thá»i trên Äiá»n thoại cá»§a bạn.', 4041 'WebView iCal solution 2': 4042 '<ol><li><strong> Má» trình duyá»t khác </strong> trên Äiá»n thoại cá»§a bạn, ...</li><li><strong> Dán </strong> ná»i dung khay nhá» tạm và bắt Äầu.</li></ol>', 4043 'Crios iCal headline': 'Má» Safari', 4044 'Crios iCal info': 'Rất tiếc, Chrome trên iOS gặp sá»± cá» vá»i cách chúng tôi tạo tá»p lá»ch.', 4045 'Crios iCal solution 2': 4046 '<ol><li><strong>Má» Safari</strong>, ...</li><li><strong> Dán </strong> ná»i dung khay nhá» tạm và bắt Äầu.</li></ol>', 4047 }, 4048}; 4049 4050// hook, which can be used to override all potential "hard" strings by setting customLabel_ + the key (without spaces) as option key and the intended string as value 4051function atcb_translate_hook(identifier, language, data) { 4052 const searchKey = identifier.replace(/\s+/g, '').toLowerCase(); 4053 if ( 4054 data.customLabels != null && 4055 data.customLabels[`${searchKey}`] != null && 4056 data.customLabels[`${searchKey}`] != '' 4057 ) { 4058 return atcb_rewrite_html_elements(data.customLabels[`${searchKey}`]); 4059 } else { 4060 return atcb_translate(identifier, language); 4061 } 4062} 4063 4064function atcb_translate(identifier, language) { 4065 // set default language 4066 if (!language) { 4067 language = 'en'; 4068 } 4069 // return string, if available 4070 if (i18nStrings[`${language}`][`${identifier}`]) { 4071 return i18nStrings[`${language}`][`${identifier}`]; 4072 } 4073 // if nothing found, return the original identifier 4074 return identifier; 4075} 4076 4077// START INIT 4078if (isBrowser()) { 4079 if (document.readyState !== 'loading') { 4080 // if the script is loaded after the page has been loaded, run the initilization 4081 atcb_init(); 4082 } else { 4083 // otherwise, init the magic as soon as the DOM has been loaded 4084 document.addEventListener('DOMContentLoaded', atcb_init, false); 4085 } 4086} 4087// END INIT
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.