// This is deliberately browser-compatible JS, so we avoid fat arrows etc. var locale = "EN-GB"; var now = getTimeNow(); var nextNewYearUTC = new Date(Date.UTC(getTimeNow().getFullYear()+1, 0, 1)); var secsRemaining = 0; var nextZone; var counter = document.getElementById("counter"); var loc = document.getElementById("location"); var time = document.getElementById("localtime"); var untilDate = document.getElementById("until-date"); var countDownSound = new Audio('get-ready.ogg'); // This function returns the current time - or we can adjust it to a // fake time, if we're testing! function getTimeNow() { var now = new Date(); var offset = 0; // hours return new Date(now.valueOf() + offset*3600000); } // Convert the next new year time to 'date, hours:mins:secs' in the target timezone function zoneTime(zone, refTime) { return refTime.toLocaleString('EN-US', { hour: '2-digit', hour12: false, minute: '2-digit', second: '2-digit', year: 'numeric', month: 'numeric', day: 'numeric', timeZone: zone, }); } function zoneOffsetMillis(timestr, baseDate) { var v = timestr.split(/[/ :,]+/) .map(function(str) { return Number(str); }); var date = new Date(v[2],v[0]-1,v[1],v[3],v[4],v[5]); return date.valueOf() - baseDate.valueOf(); } // Compile a list of timezones from Intl.supportedValuesOf var zones = Intl.supportedValuesOf('timeZone') .map(function(zone) { return { zone: zone, offset: zoneOffsetMillis(zoneTime(zone, nextNewYearUTC), nextNewYearUTC) }; }) .sort(function(a,b) { return b.offset - a.offset; }); // Now concoct an index of timezone hour offsets to timezone info. // Each zone has: // { abbrevs, offset, hOffset, nydDate } var zoneIndex = zones .reduce((a, v) => { var hOffset = v.offset/3600000; var zoneInfo = a[hOffset]; if (zoneInfo) zoneInfo.abbrevs.push(v.zone); else { a[hOffset] = { abbrevs: [v.zone], offset: v.offset, hOffset: hOffset, nydDate: new Date(nextNewYearUTC.valueOf() + v.offset), }; } return a; }, {}); console.debug(nextNewYearUTC, zoneIndex); // This info is just used to say what cities / countries are in the timezone var zoneInfo = { "-12": { country: "small region of U.S.A.", city: "Baker Island, Howland Island" }, "-11": { country: "American Samoa, Midway Atoll/U.S.A. and 1 more", city: "Alofi, Midway, Pago Pago" }, "-10": { country: "Cook Islands, small region of U.S.A. and 2 more", city: "Honolulu, Rarotonga, Adak, Papeete" }, "-9.5": { country: "French Polynesia, Marquesas Islands", city: "Taiohae" }, "-9": { country: "Alaska/U.S.A. and French Polynesia", city: "Anchorage, Fairbanks, Unalaska, Juneau" }, "-8": { country: "Pitcairn Islands, regions of U.S.A. Mexico, and Canada", city: "Los Angeles, San Francisco, Las Vegas, Seattle" }, "-7": { country: "some regions of U.S.A., Mexico, some regions of Canada", city: "Calgary, Denver, Edmonton, Phoenix" }, "-6": { country: "Belize, regions of U.S.A., Mexico, some regions of Canada and 8 more", city: "Mexico City, Chicago, Guatemala, Dallas" }, "-5": { country: "regions of U.S.A., regions of Canada and 12 more", city: "New York, Washington DC, Detroit, Havana" }, "-4": { country: "Venezuela", city: "Caracas, Barquisimeto, Maracaibo, Maracay" }, "-3.5": { country: "Newfoundland and Labrador/Canada", city: "St. John's, Conception Bay South, Corner Brook, Gander" }, "-3": { country: "regions of Brazil, Argentina and 7 more", city: "Buenos Aires, Santiago, Asuncion, Paramaribo" }, "-2": { country: "South Georgia and the South Sandwich Islands", city: "Grytviken", }, "-1": { country: "Cape Verde, some regions of Greenland and 1 more", city: "Praia, Ponta Delgada (Azores), Ittoqqortoormiit, Mindelo" }, "0": { country: "United Kingdom and 24 more", city: "London, Casablanca, Dublin, Lisbon" }, "1": { country: "Germany and 43 more", city: "Brussels, Madrid, Paris, Rome" }, "2": { country: "Greece and 30 more", city: "Cairo, Ankara, Athens, Bucharest" }, "3": { country: "Iraq and 20 more", city: "Baghdad, Khartoum, Nairobi, Addis Ababa" }, "3.5": { country: "Iran", city: "Tehran, Rasht, Esfahãn, Bandar-Abbas" }, "4": { country: "UAE, parts of Russia, Georgia, Armenia, Mauritius", city: "Tblisi, Dubai, Abu Dhabi", }, "4.5": { country: "Afghanistan", city: "Kabul, Kandahar, Mazari Sharif, Herat" }, "5": { country: "Pakistan and 8 more", city: "Tashkent, Islamabad, Lahore, Karachi" }, "5.5": { country: "India and Sri Lanka", city: "New Delhi, Mumbai, Kolkata, Bangalore" }, "5.75": { country: "Nepal", city: "Kathmandu, Biratnagar, Pokhara" }, "6": { country: "Bangladesh, some regions of Russia and 4 more", city: "Dhaka, Almaty, Bishkek, Thimphu" }, "6.5": { country: "Myanmar and Cocos Islands", city: "Yangon, Naypyidaw, Mandalay, Bantam" }, "7": { country: "much of Indonesia, Thailand and 7 more", city: "Jakarta, Bangkok, Hanoi, Phnom Penh" }, "8": { country: "China and 12 more", city: "Beijing, Hong Kong, Manila, Singapore" }, "8.75": { country: "Western Australia/Australia", city: "Eucla" }, "9": { country: "Japan and 6 more", city: "Tokyo, Seoul, Pyongyang, Dili" }, "9.5": { country: "Northern Territory/Australia", city: "Darwin, Alice Springs, Uluru" }, "10": { country: "Queensland/Australia and 5 more", city: "Brisbane, Port Moresby, Guam (Hagåtña), Cairns" }, "10.5": { country: "small region of Australia", city: "Adelaide, Broken Hill" }, "11": { country: "Vanuatu, Solomon Islands, much of Australia and 5 more", city: "Melbourne, Sydney, Canberra, Honiara" }, "12": { country: "Kiribati, Marshall Islands, Norfolk Island, Tuvalu, Fiji", city: "Kingston", }, "13": { country: "New Zealand with exceptions and 5 more", city: "Auckland, Suva, Wellington, Nukualofa" }, "13.75": { country: "Chatham Islands/New Zealand", city: "Chatham Islands" }, }; console.debug(zoneInfo); var zoneOrder = Object.keys(zoneIndex) .map(Number) .sort(function(a,b) { return a-b; }); console.debug(zoneOrder); function pad(num) { return (num<10? "0"+num : ""+num); } function counterClass(millisecsRemaining) { if (millisecsRemaining > 3600000) return "hours"; if (millisecsRemaining > 60000) return "minutes"; if (millisecsRemaining > 10000) return "seconds"; return "countdown"; } function timeRemaining(millisecsRemaining) { // var days = Math.floor(millisecsRemaining / millisInDay) // millisecsRemaining -= days * millisInDay var hours = Math.floor(millisecsRemaining / 3600000) millisecsRemaining -= hours * 3600000 var mins = Math.floor(millisecsRemaining / 60000) millisecsRemaining -= mins * 60000 var secs = Math.round(millisecsRemaining / 1000) return hours+"h "+pad(mins)+"m "+pad(secs)+"s" } var playing = false; function handleTick(now) { console.debug(now, now.getMilliseconds()); while (!nextZone || zoneIndex[nextZone].nydDate <= now) { if (zoneOrder.length == 0) return; nextZone = zoneOrder.shift(); console.debug("next up: zone", nextZone); } var millisecsRemaining = zoneIndex[nextZone].nydDate.getTime() - now.getTime(); console.debug("seconds",now.getSeconds()); if (millisecsRemaining < 2*60*1000) { if (!playing) { countDownSound.play(); playing = true; } } else // resets the announcement playing = false; var info = zoneInfo[nextZone]; var zone = zoneIndex[nextZone]; untilDate.innerHTML = nextNewYearUTC.toLocaleDateString(locale, { weekday: "long", year: "numeric", month: "long", day: "numeric", }); counter.innerHTML = timeRemaining(millisecsRemaining); loc.innerHTML = info? info.city || info.country : zone.abbrev[0]; time.innerHTML = zone.nydDate.toLocaleDateString(locale, { hour: "2-digit", minute: "2-digit", second: "2-digit", }); counter.className = counterClass(millisecsRemaining); } function ticker() { var now = getTimeNow(); handleTick(now); var millis = now.getMilliseconds() var millisTilNextSecond = 1000-millis; setTimeout(ticker, millisTilNextSecond); } ticker();