0

My website is using the timezone from my server, which is CET. So the unix-timestamp that is always parsed using

new Date(timestamp * 1000)

to the timezone of the server, and not the client.

However, I know how to make the client view the time of their own timezone. I rather want to get the difference from their timezone to CET.

So clients from e.g Ireland will show (-1 hour to CET) or (-3600 to CET), and so on. To clarify, I don't need help to view the time in the clients correct timezone, I need help to get the clients difference in hours or seconds to CET.

  • Given a time value (like `timestamp * 1000`), the Date constructor treats it as an offset from 1970-01-01 UTC, not CET. All Dates are inherrently UTC, timezones are only used for local *get* methods (i.e. the non–UTC methods) and *toString*, so likely you don't have to deal with timezones at all. – RobG Dec 19 '21 at 23:42

1 Answers1

0

You can get the timezone offset for any IANA representative location using toLocaleString with timeZone and timeZoneName options. The only trick is that sometimes the short timeZoneName is returned as an abbreviation, and other times as UTC/GMT±HH:mm. It seems dependent on the language used and the default language of the host, so use say 'en' first and if that returns an abbreviation, change to 'fr'.

Once you have the offsets, you can calculate the time difference between any two places.

/* 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(/^.*\//,'')}`);
});

You can't just ask for the timezone, so year is included to minimise what has to be parsed.

RobG
  • 142,382
  • 31
  • 172
  • 209