如何根据时区获取时差?

How to get time difference based on timezone?

提问人:coding.... 提问时间:11/8/2023 最后编辑:coding.... 更新时间:11/9/2023 访问量:88

问:

我需要获取位于 anather 国家/地区的其他用户的时差(以小时和分钟为单位)。这是我所做的。

const timeZone = "Asia/tokyo"; // Time zone can be changed

let arr: any = new Date().toLocaleString("en-US", {
  timeZone: timeZone,
  dateStyle: "full",
  timeStyle: "full",
});

let currentTime = new Date();

需要获取 currentHour 和 arr 之间的差异

JavaScript 打字稿 日期

评论

0赞 Jaromanda X 11/8/2023
Date 构造函数不接受 Array 作为参数 - 您的逻辑也可能有缺陷,因为结果中的小时数将是 1 到 12 ...没有AM或PM指示
0赞 Jaromanda X 11/8/2023
时区总是亚洲/东京吗?如果没有,那么像这样的时区呢?即 甚至Asia/CalcuttaUTC+5:30Australia/Eucla
0赞 coding.... 11/8/2023
不会,时区会改变。我刚刚在那里添加了一个示例数据。时区可能会根据用户所在的国家/地区而变化。
0赞 Jaromanda X 11/8/2023
好吧,那么“currentHour”是不够的
0赞 coding.... 11/8/2023
我添加了亚洲/东京作为示例。如果日本的当前时间是下午 1:30,而我当前的时间是上午 11:00,我需要获得小时和分钟的差值。

答:

-1赞 Jaromanda X 11/8/2023 #1

首先,使用语言环境进行“计算”......它输出,使其易于操作en-CAyyyy-mm-dd hh:mm:ss

其次,根据需要添加 24 小时时间hour12: false

然后你可以

const minutesToHHMM = (m) => {
  const s = m < 0 ? '-' : '';
  m = Math.abs(m);
  const mm = (m % 60).toString().padStart(2, 0);
  const hh = Math.floor(m / 60);
  return `${s}${hh}:${mm}`;
}
const timeZone = 'Australia/Eucla'; // odd timezone to always show minutes in difference
const date = new Date();
date.setMilliseconds(0); // remove millisecond since we are not creating the "other" time with milliseconds
const other = new Date(...date
  .toLocaleString('en-CA', {
    timeZone,
    hour12: false,
  })
  .replaceAll('-',':') // convert yyyy-mm-dd to yyyy:mm:dd
  .replaceAll(', ', ':') // add ':' between date and time
  .split(':') // split all the values
  .map((v,i) => v - (i===1)) // subtract one from month
);

console.log("other time", other.toLocaleString());
console.log("local time", date.toLocaleString());
console.log("difference", minutesToHHMM((other-date)/60000));

我的打字稿不优雅......但

const minutesToHHMM = (m:number) => {
  const s = m < 0 ? '-' : '';
  m = Math.abs(m);
  const mm = (m % 60).toString().padStart(2, "0");
  const hh = Math.floor(m / 60);
  return `${s}${hh}:${mm}`;
}
const timeZone = 'Australia/Eucla'; // odd timezone to always show minutes in difference
const date = new Date();
date.setMilliseconds(0); // remove millisecond since we are not creating the "other" time with milliseconds

const other = new Date(...(date
  .toLocaleString('en-CA', {
      timeZone,
      hour12: false,
  })
  .replaceAll('-',':') // convert yyyy-mm-dd to yyyy:mm:dd
  .replaceAll(', ', ':') // add ':' between date and time
  .split(':') // split all the values
  .map(Number)
  .map((v:number, i:number) => v - ((i===1) ? 1 : 0)) as []) // subtract 1 from month
);

console.log("other time", other.toLocaleString());
console.log("local time", date.toLocaleString());
console.log("difference", minutesToHHMM((+other - +date)/60000));

在 TS Playground 上尝试过,似乎有效

评论

0赞 coding.... 11/8/2023
兄弟,我们怎么能在这里传播日期。我收到类似 spread 参数必须具有元组类型或传递给 rest 参数的错误。打字稿有问题吗?
1赞 RobG 11/8/2023
最好使用 formatToParts,以便直接获取零件。不确定这是否适用于 DST 边界。
1赞 Matt Johnson-Pint 11/9/2023
@JaromandaX - 这是一个良好的开端,但通过测试,您会发现边缘情况存在错误。具体而言,由于它是通过将离散数值传递给构造函数创建的,因此它将受到本地时区的 DST 规则的影响。因此,如果它位于本地区域的过渡期内,即使它是“其他”区域中的有效时间,也可能被视为不明确或无效。像 Luxon 这样的库从这种方法开始,但认为这只是一种猜测。然后他们围绕结果进行一些测试以巩固答案。otherDate
1赞 Matt Johnson-Pint 11/9/2023
不幸的是,今天没有一种简单的方法可以在 JS 中实现这一点,而不使用像 Luxon 这样的库(或等效的复杂代码)。当它准备好时,Temporal 应该会让它变得简单得多。
1赞 RobG 11/9/2023
@JaromandaX,正如 Matt 所说,您传递的是离散值,因此可能会传递一个日期和时间,该日期和时间要么不存在,因为它处于“风向前进”小时,要么在“风向”小时内存在两次。此外,离散值可能位于边界的错误一侧。
0赞 RobG 11/9/2023 #2

像 Jaromanda X 这样的解决方案(下面使用 Intl.DateTimeFormat.formatToParts 复制)是可以的,但最好传递实际的 Date 对象而不是日期和时间值。

我认为获得实际偏移量之间的差异(见下文)更优雅。

function msToHMS(ms) {
  let sign = ms < 0? '-' : '+';
  ms = Math.abs(ms);
  let z = n => (n < 10? '0':'') + n;
  let hr = (ms / 3.6e6) | 0;
  let min = ((ms % 3.6e6) / 6e4) | 0;
  let sec = ((ms % 6e4) / 1e3) | 0;
  return `${sign}${z(hr)}:${z(min)}:${z(sec)}`;
}

function getTZDiff(tz, d = new Date()) {
  let tv = d - (d % 1e3);
  let {year, month, day, hour, minute, second} = new Intl.DateTimeFormat('en', {
    year:'numeric', month:'numeric', day:'numeric',
    hour:'numeric', minute:'numeric', second:'numeric',
    timeZone: tz,
    hour12: false
  }).formatToParts(d).reduce((acc,part) => {
      acc[part.type] = part.value;
      return acc;
    }, Object.create(null)
  );
  return msToHMS(new Date(year, month-1, day, hour, minute, second) - tv);
}

console.log(getTZDiff('America/New_York', new Date(2023,3,2,1,30)));

使用时区偏移:

/* Return offset at loc for date
 * @param {string} loc - IANA representative location
 * @param {Date} date to get offset for
 * @returns {string} offset as ±hh:mm
 */
function getOffsetForLoc(loc, d = new Date()) {
  let offset = d.toLocaleString('en',{
    timeZoneName:'longOffset', timeZone: loc
  }).match(/[+|-][^+-]+$/); 
  return offset[0];
}

/* Convert offset in ±hh:mm:ss format to seconds
 * @param {string} offset - ±hh:mm:ss format
 * @returns {number} offset in seconds
 */
function offsetToSecs(offset) {
  let sign = offset[0] == '-' ? -1 : 1;
  let [hr, min, sec] = offset.match(/\d+/g);
  return sign * (hr*3600 + min*60 + (sec||0)*1); 
}

/* Convert secons to time in ±hh:mm:ss format
 * @param {number} secs - seconds to convert
 * @returns {string} equivalent in ±hh:mm:ss format
 */
function secsToHMS(secs) {
  let sign = secs < 0? '-' : '+';
  secs = Math.abs(secs);
  let z = n => (n < 10? '0':'') + n;
  let hr = (secs / 3600) | 0;
  let min = ((secs % 3600) / 60) | 0;
  let sec = secs % 60;
  return `${sign}${z(hr)}:${z(min)}:${z(sec)}`;
}

// Get diference in offset between two locations.
// Add difference to loc1 to get time in loc2
function getOffsetDifference(loc1, loc2, d = new Date()) {
  let off1 = offsetToSecs(getOffsetForLoc(loc1, d));
  let off2 = offsetToSecs(getOffsetForLoc(loc2, d));
  return secsToHMS(off2 - off1);
}

// Examples
let yourLoc = new Intl.DateTimeFormat().resolvedOptions().timeZone;
let cha = 'Pacific/Chatham';

console.log(
    `You  : ${yourLoc} ${getOffsetForLoc(yourLoc)}` +
  `\nOther: ${cha} ${getOffsetForLoc(cha)}` +
  `\nDiff : ${getOffsetDifference(yourLoc, cha)}` +
  ` (i.e. You + Diff == Other)` +
  `\nRev  : ${getOffsetDifference(cha, yourLoc)}` +
  ` (i.e. Other + Rev == You)`
);