/* Return offset on date for loc in ±HH:mm format
* @param {string} loc - IANA representative location
* @param {Date} date - date to get offset for, default is current date
* @returns {string} offset in ±HH:mm format
*/
function getTimezoneOffset(loc, date = new Date()) {
// Try English
let offset = date.toLocaleString('en',{
year: 'numeric',
timeZone: loc,
timeZoneName: 'short'
});
// If got an abbreviation, use French
if (!/UTC|GMT/.test(offset)) {
offset = date.toLocaleString('fr',{
year: 'numeric',
timeZone: loc,
timeZoneName: 'short'
});
}
// Get offset part. If offset is just "GMT" (e.g. London in winter),
// replace with "+0"
offset = offset.replace(/.*(UTC|GMT)/,'') || '+0';
// Format as ±HH:mm
let sign = offset.substr(0,1);
let [H, m] = offset.match(/\d+/g);
return `${sign}${H.padStart(2,'0')}:${(m||'0').padStart(2,'0')}`;
}
// Examples
console.log('-- Sample current offsets --');
['America/Los_Angeles',
'Europe/Dublin',
'Europe/London',
'Asia/Kolkata',
'Asia/Kathmandu',
'Australia/Yancowinna'].forEach(
loc => console.log(`${loc} : ${getTimezoneOffset(loc)}`)
);
/* Convert ±HH[:mm[:ss]] to ±seconds.
* @param {string} time : in ±HH[:mm[:ss]] format, min and sec optional
* If seconds included, minutes must be present
* @returns {number} time as seconds, e.g. 150, -150
*/
function timeToSecs(time) {
let sign = /^\+/.test(time)? 1 : -1;
let [H,m,s] = time.match(/\d+/g) || [];
return sign * (H||0)*3600 + (m || 0)*60 + (s||0)*1;
}
/* Convert ±seconds to time in ±H[:mm[:ss]] format. Mins and secs
* only included if not zero. Mins always present if secs not zero.
* @param {number} secs - seconds to convert
* @returns {string] time in ±H[:mm[:ss]] format
*/
function secsToTime(secs) {
let sign = secs < 0? '-' : '+';
secs = Math.abs(secs);
let H = secs / 3600 | 0;
let m = String(secs % 3600 / 60 | 0).padStart(2,'0');
let s = String(secs % 60).padStart(2,'0');
return `${sign}${H}${(m != 0 || s != 0)? ':' + m : ''}${(s != 0)? ':' + s : ''}`;
}
/* Difference in minutes between loc0 and loc1
* @param {string} loc0 - IANA representative location
* @param {string} loc1 - IANA representative location
* @param {Date} date - date to get difference for, default is current date
* @returns {number} difference in minutes to add to loc0 to get time at loc1
*/
function differenceBetweenLocs(loc0, loc1, date = new Date()) {
let off0 = getTimezoneOffset(loc0, date);
let off1 = getTimezoneOffset(loc1, date);
return timeToSecs(off1) - timeToSecs(off0);
}
// Examples
console.log('-- Sample conversions from one location to another --');
[['Europe/Dublin','Europe/Berlin'],
['Europe/Berlin','Europe/Dublin'],
['Europe/Moscow','Atlantic/Reykjavik'],
['Asia/Kathmandu','Asia/Irkutsk'],
['America/Bogota','Asia/Kolkata'],
['Pacific/Chatham','Indian/Cocos'],
['America/Indiana/Vincennes','America/St_Johns'],
['Pacific/Midway','Pacific/Kiritimati']
].forEach(([loc0, loc1]) => {
let diff = differenceBetweenLocs(loc0, loc1);
console.log(`${loc0.replace(/^.*\//,'')} ${diff < 0? '':'+'}` +
`${diff} secs (${secsToTime(diff)}) gives time in ${loc1.replace(/^.*\//,'')}`);
});
// Examples for 1 Jan 1801 UTC when offsets were rarely multiples of 15 min
let d = new Date(Date.UTC(1801,0,1));
console.log('-- Sample conversions for 1 Jan 1801 UTC --');
[['Europe/Dublin','Europe/Berlin',d],
['Europe/Berlin','Europe/Dublin',d],
['Europe/Moscow','Atlantic/Reykjavik',d],
['Asia/Kathmandu','Asia/Irkutsk',d],
['America/Bogota','Asia/Kolkata',d],
['Pacific/Chatham','Indian/Cocos',d],
['America/Indiana/Vincennes','America/St_Johns',d],
['Pacific/Midway','Pacific/Kiritimati',d]
].forEach(([loc0, loc1,d]) => {
let diff = differenceBetweenLocs(loc0, loc1,d);
console.log(`${loc0.replace(/^.*\//,'')} ${diff < 0? '':'+'}` +
`${diff} secs (${secsToTime(diff)}) gives time in ${loc1.replace(/^.*\//,'')}`);
});