仅计算一个特定时区的时区偏移

IT技术 javascript date time
2021-03-04 15:28:36

我正在尝试为一个在德国在线教学的人构建一个应用程序。我想将人的日程安排存储在约会开始时间的数组中。例如:

let schedule = [
  new Date(`${currentDate}T07:00:00Z`),
  new Date(`${currentDate}T08:00:00Z`),
  new Date(`${currentDate}T09:00:00Z`)
  ...
]

问题是在一些国家,比如德国,有标准时间和夏令时。因此,在夏季,德国的出发时间是 9:00、10:00 和 11:00,而在冬季,它们会提前一小时。与此同时,在新加坡等国家,他们将保持不变:15:00、16:00、17:00……我必须做什么才能使德国的开始时间全年稳定,而在其他地方有所变化分别:

if (summerTimeInGermany) {
  schedule = [
    new Date(`${currentDate}T07:00:00Z`), // 9:00 in Germany
    new Date(`${currentDate}T08:00:00Z`), // 10:00
    new Date(`${currentDate}T09:00:00Z`) // 11:00
    ...
  ]
} else {
  schedule = [
    new Date(`${currentDate}T08:00:00Z`), // 9:00
    new Date(`${currentDate}T09:00:00Z`), // 10:00
    new Date(`${currentDate}T10:00:00Z`) // 11:00
    ...
  ]
}

我需要这个日程表,以便我可以将它与我从 Google 日历中获取的每天的约会列表进行比较,并排除已经完成的约会。

2个回答

我认为您要做的是根据另一个时区的时间戳并给定 IANA 代表位置来生成 UTC 时间戳。

一种方法是使用时间戳作为 UTC 为该位置生成日期,并查看小时和分钟的差异。那应该是偏移量。然后将偏移量应用于原始时间戳。然后,当应用时区偏移量时,应该会生成一个时间戳,其中包含目标位置所需的日期和时间。

以下函数使用 Intl.DateTimeFormat 和 IANA 代表位置实现此算法。它只是经过轻微测试,所以请进一步测试。它对调整进行循环以检查应用偏移量不会将日期移动到 DST 边界上,因此需要重新调整偏移量。

默认情况下,它返回等效时间的 ISO 8601 UTC 时间戳,如果可选的returnOffset参数设置为true,则返回偏移量。

我提供了格林威治标准时间东部和西部的标准和夏令时时间戳示例(但不是很多)。请彻底测试并根据需要进行修改。

/* @param {string} isoString - ISO 8601 timestamp without timezone
**                             e.g. YYYY-MM-DDTHH:mm:ss or YYYY-MM-DD HH:mm:ss
** @param {string} loc - IANA representateive location
**                       e.g. Europe/Berlin
** @param {boolean} returnOffset - if true, return the offset instead of timestamp
** @returns {string} if returnOffset is true, offset is ±HH:mm[:ss] (seconds only if not zero)
**                   if returnOffset is false, equivalent ISO 8601 UTC timestamp
*/
let getUTCTime = (function() {

  let n = 'numeric';
  let formatterOpts = {year:n, month:n, day:n, hour:n, minute:n, second:n, hour12: false};

  // Parse YYYY-MM-DDTHH:mm:ss as UTC (T can be space)
  function parse (isoString) {
    let [Y,M,D,H,m,s] = isoString.split(/[\DT\s]/);
    return new Date(Date.UTC(Y,M-1,D,H,m,s));
  }
  
  // Get date parts, use supplied formatter
  function toParts(date, formatter) {
    return formatter.formatToParts(date).reduce((acc, part) => {
      acc[part.type] = part.value;
      return acc;
    }, Object.create(null));
  }

  return function (isoString, loc, returnOffset = false) {
 
    // Update formatter options with loc so get target loc values
    formatterOpts.timeZone = loc;
    // Formatter
    let formatter = new Intl.DateTimeFormat('en', formatterOpts);
  
    // Parse input string as UTC
    let oDate = parse(isoString);
    // Date to adjust to wanted values
    let utcDate = new Date(oDate);

    // maxLoops limits do..while in dilemma zone, ensures sensible value
    let maxLoops = 3,
        p, diff;

    // Adjust utcDate so it generates required local date values
    // Adjustment may shift over DST boundary so may need 2nd adjustment
    // Limit number of loops (shouldn't be required but just in case...)
    do {
      // Get date parts in target timezone
      p = toParts(utcDate, formatter);
   
      // Get difference between local and adjusted values
      diff = new Date(Date.UTC(p.year, p.month-1, p.day, p.hour, p.minute, p.second)) - oDate;
   
      // If necessary, adjust utcDate so it generates required values when shifted
      if (diff) {
        utcDate.setTime(utcDate.getTime() - diff);
      }
    // Loop until generated values match original or maxLoops
    } while (diff && maxLoops--)
    
    // If maxLoops is -1, hit DST dilemma zone: time doesn't exist on that date
    // E.g. going into daylight saving at 02:00, then 02:30 doesn't exist
    // and loop will flip in/out of DST until stopped by maxLoops
    // So generate valid date and offset in DST period
    let dDiff = null;
    if (maxLoops < 0) {
      p = toParts(utcDate, formatter);
      dDiff = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second) - utcDate;
      let msg = isoString + ' does not exist at ' + loc + ' due to ' +
                'daylight saving change-over, shifting into DST';
      // console.log(msg);
      // throw new RangeError(msg);
    }

    // Convert diff between local and adjusted to get ±HH:mm offset
    // Use dilemma diff (dDiff) if has been set
    let oDiff = dDiff || oDate - utcDate;
    let sign = oDiff > 0? '+' : '-';
    oDiff = Math.abs(oDiff);
//    console.log(sign + new Date(oDiff).toISOString().substring(11,19).replace(/:00$/,''));
    let offH = oDiff / 3.6e6 | 0;
    let offM = (oDiff % 3.6e6) / 6e4 | 0;
    let offS = (oDiff % 6e4) / 1e3 | 0;

    let z = n=>(n<10?'0':'')+n;
    // Return offset (with offset seconds if not zero) or ISO 8601 UTC string
    return returnOffset? `${sign}${z(offH)}:${z(offM)}${offS? ':' + z(offS) : ''}` :
                         utcDate.toISOString();
  }
})();

// Given a local timestmap in format YYYY-MM-DDTHH:mm:ss and
// loc as IANA representative location
// Return equivalent ISO 8061 UTC timestmap
function getUTCString(timestamp, loc) {
  return getUTCTime(timestamp, loc);
}
// Given a local timestmap in format YYYY-MM-DDTHH:mm:ss and
// loc as IANA representative location
// Return offset at loc as ±HH:mm[:ss]
//  - seconds only included if not zero (typically pre-1900)
function getUTCOffset(timestamp, loc) {
  return getUTCTime(timestamp, loc, true);
}

// Examples
window.onload = function() {
  let t = document.getElementById('t0');
  let params = ['Local time', 'UTC time', false];
  let showData = (localTime, loc, offset, timeUTC) => {
    let row = t.insertRow();
    [localTime, loc, timeUTC, null, null].forEach((s, i) => {
      cell = row.insertCell();
      cell.textContent = i == 0? localTime.replace('T',' '):
                         i == 1? loc:
                         i == 2? offset  :
                         i == 3? timeUTC :
                         new Date(timeUTC).toLocaleString('en-CA', {timeZone: loc, hour12: false}).replace(',','') ;
    });
    return new Date(timeUTC).toLocaleString('en-CA', {timeZone: loc, hour12: false}).replace(',','');
  };
  // Local time required   Location
  [['2020-04-22T09:00:00','Europe/Berlin'],   // DST offset +2
   ['2020-01-22T09:00:00','Europe/Berlin'],   // Std offset +1
   ['2020-04-22T09:00:00','America/Denver'],  // DST offset -6
   ['2020-01-22T09:00:00','America/Denver'],  // Std offset -7
   ['2020-04-22T09:00:00','US/Aleutian'],     // DST offset -9
   ['2020-01-22T09:00:00','US/Aleutian'],     // Std offset -10
   ['2020-01-22T09:00:00','Pacific/Honolulu'],// Std offset -11
   ['2020-01-22T19:00:00','Pacific/Honolulu'],// Std offset -11
   ['2020-04-22T09:00:00','Asia/Singapore'],  // Std offset +8
   ['2020-04-22T09:00:00','Pacific/Apia'],    // Std offset +13
   ['2020-01-22T09:00:00','Pacific/Apia'],    // DST offset +14
   ['2020-01-22T09:00:00','Asia/Yangon'],     // Std offset +6:30
   ['2020-04-22T09:00:00','Pacific/Chatham'], // Std offset +12:45
   ['2020-01-22T09:00:00','Pacific/Chatham'], // DST offset +13:45
   
   // Historic offsets pre 1900
   ['1857-01-01T00:00:00','Europe/Berlin'],   // Std offset +00:53:28
   ['1857-01-01T00:00:00','Australia/Sydney'],// Std offset +10:04:52
   ['1857-01-01T00:00:00','America/New_York'],// Std offset -04:56:02
   ['1857-01-01T00:00:00','America/Sao_Paulo'],//Std offset -03:06:28
   
   // DST boundary check out of DST (2 to 3 am counted as "out")
   ['2020-04-05T01:45:00','Australia/Sydney'],// DST offset +11:00
   ['2020-04-05T01:59:59','Australia/Sydney'],// DST offset +11:00
   ['2020-04-05T02:00:00','Australia/Sydney'],// Std offset +10:00
   ['2020-04-05T02:30:00','Australia/Sydney'],// Std offset +10:00
   ['2020-04-05T03:00:00','Australia/Sydney'],// Std offset +10:00
   ['2020-04-05T03:15:00','Australia/Sydney'],// Std offset +10:00
   
   // DST boundary check into DST (2 to 3 am counted as "in")
   ['2020-10-04T01:45:00','Australia/Sydney'],// Std offset +10:00
   ['2020-10-04T02:00:00','Australia/Sydney'],// DST offset +11:00
   ['2020-10-04T02:30:00','Australia/Sydney'],// DST offset +11:00
   ['2020-10-04T02:59:59','Australia/Sydney'],// DST offset +11:00
   ['2020-10-04T03:00:00','Australia/Sydney'],// DST offset +11:00
   ['2020-10-04T03:15:00','Australia/Sydney'] // DST offset +11:00
  ].forEach(([localTime,loc]) => {
    // Show results
    let timeUTC = getUTCString(localTime, loc);
    let offset  = getUTCOffset(localTime, loc);
    showData(localTime, loc, offset, timeUTC);
  });
};

// Example use
let timestamp = '2020-06-30 08:30:00';
let locBer = 'Europe/Berlin';
let locSng = 'Asia/Singapore';
// Get UTC timestamp and offset for Berlin
let utc = getUTCString(timestamp, locBer);
let off = getUTCOffset(timestamp, locBer);
// Show times and offset - offset is just for display, not used to
// generate Singapore timestamp
console.log('Berlin   : ' + timestamp + ' ' + off); // 
console.log('Singapore: ' + new Date(utc).toLocaleString(
  'en-CA',{hour12:false, timeZone:locSng, timeZoneName:'short'}
).replace(',',''));
table {
  border-collapse: collapse;
}
td {
  font-family: geneva, arial;
  font-size: 80%;
  padding: 5px;
  border: 1px solid #bbbbbb;
}
td:nth-child(2) {
  font-family: monospace;
  font-size: 100%;
}
<table id="t0">
  <tr><th>Local time<th>Place<th>Offset<th>UTC<th>Check
</table>

@PetarKaragenov——我添加了一个可选的returnOffset参数,如果为真,则返回偏移量而不是时间戳。将其称为getUTCTime(localTime, location[, true]). 您可能希望将该函数库里化为两个新函数:getUTCStringgetUTCOffset
2021-04-22 15:28:36
问题是当从另一个国家/地区访问应用程序时,如何检查给定日期在贝林时间的偏移量是多少。
2021-04-24 15:28:36
@PetarKaragenov-对于您的柏林/新加坡示例,用于getUTCTime(string, 'Europe/Berlin')获取等效的 UTC 字符串(如果您喜欢显示,还可以使用偏移量)。然后使用Intl.DateTimeFormat生成的 UTC 字符串和 timeZone Asia/Singapore 获取新加坡等效时间的时间戳。如果您需要更多关于如何做到这一点的帮助,请提出另一个问题。:-)
2021-04-30 15:28:36
不,不完全是。我想要实现的是创建一个数组,它将根据国家/地区显示约会的写入开始时间。所以,如果我在德国的 24.04 上访问,我会得到 ['2020-04-24T09:00:00+02:00']。然后如果我在 12.12 再次在德国访问它,我会得到 ['2020-04-24T09:00:00+01:00'] 但如果我从 SG 访问它,我第一次会得到 ['2020-04 -24T15:00:00+08:00'],但是第二个:['2020-04-24T16:00:00+08:00']。这就是我需要做的。
2021-05-05 15:28:36
@RobG 请告诉我你为什么使用utcDatein dDiff = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second) - utcDate;,但utcDate可以在do-while循环中改变这里没有错误吗?
2021-05-20 15:28:36

我试图用帖子中的代码实现一些目标,但我仍然得到了错误的答案。不知道我的逻辑哪里错了。

Date.prototype.stdTimezoneOffset = function () {
            var jan = new Date(this.getFullYear(), 0, 1);
            var jul = new Date(this.getFullYear(), 6, 1);
            return Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
        }

        Date.prototype.isDstObserved = function () {
            return this.getTimezoneOffset() < this.stdTimezoneOffset();
        }

        let currentDate = '2020-12-22';
        const today = new Date();
        let schedule = [];

        if (!today.isDstObserved()) {
            schedule = [
                new Date(`${currentDate}T07:00:00Z`), // 9:00 in Germany
                new Date(`${currentDate}T08:00:00Z`), // 10:00
                new Date(`${currentDate}T09:00:00Z`) // 11:00
            ]
        } else {
            if (today.getTimezoneOffset() < new Date('2019-11-21').getTimezoneOffset()) {
                schedule = [
                    new Date(`${currentDate}T07:00:00Z`), // 9:00 in Germany
                    new Date(`${currentDate}T08:00:00Z`), // 10:00
                    new Date(`${currentDate}T09:00:00Z`) // 11:00
                ]
            } else {
                schedule = [
                    new Date(`${currentDate}T08:00:00Z`), // 9:00
                    new Date(`${currentDate}T09:00:00Z`), // 10:00
                    new Date(`${currentDate}T10:00:00Z`) // 11:00
                ]
            }
        }