【问题标题】:How can I store Hour and Minute in a universal time format?如何以通用时间格式存储小时和分钟?
【发布时间】:2020-08-15 10:31:11
【问题描述】:

我正在开发一个网站,教师根据当地时区输入他们的可用性 例如,来自加利福尼亚的 David 在太平洋标准时间周一和周二下午 4 点有空。 我想以本地格式向国际上的其他所有人展示这种可用性 来自纽约的约翰可以看到大卫在美国东部标准时间晚上 7 点有空 有没有在数据库中存储本地时区的标准方法? 我在想只是选择一个随机日期(甚至现在)并将小时/分钟粘贴到它,将其保存为 UTC 并显示它只是忽略日期部分。这听起来合理还是有更好的方法?

【问题讨论】:

  • 您想以用户的本地时区显示时间,还是以他们正在查看的人的时区显示时间,还是两者兼而有之?是的,您需要存储每个用户的时区数据,最好使用 IANA 代表位置,例如America/Los_Angeles 代表加利福尼亚/PST 和 America/New_York 代表 EST。这也将适应夏令时,以便将当地时间下午 4 点适当地转换为其他当地时间。看Calculate Timezone offset only for one particular timezone
  • 对,夏令时似乎是个问题!

标签: javascript date datetime utc


【解决方案1】:

存储当地时间时,还应存储相关的时区数据。目前最便携的标识符是 IANA 代表位置,例如“America/New_York”。这样一来,标准和夏令时偏移量的变化就得到了适应,因此在给定特定日期的情况下,您可以获得一个人的时间详细信息,并将其显示为另一个人所在位置的日期和时间,并根据他们在该日期的偏移量进行调整。

下面展示了一个算法,它使用了来自here的粗略函数,但我强烈建议使用Luxon之类的库,我只是想保留这个普通的JS。

以下内容获取一个用户的时间和位置,然后将其显示为另一个用户位置的等效时间。希望这与您想做的事情相符。

// Function from https://stackoverflow.com/a/61364310/257182
/* @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};
  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));
  }
  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) {
 
    formatterOpts.timeZone = loc;
    let formatter = new Intl.DateTimeFormat('en', formatterOpts);
    let oDate = parse(isoString);
    let utcDate = new Date(oDate);
    let maxLoops = 3,
        p, diff;
    do {
      p = toParts(utcDate, formatter);
      diff = new Date(Date.UTC(p.year, p.month-1, p.day, p.hour, p.minute, p.second)) - oDate;
      if (diff) {
        utcDate.setTime(utcDate.getTime() - diff);
      }
    } while (diff && maxLoops--)
    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';
    }
    let oDiff = dDiff || oDate - utcDate;
    let sign = oDiff > 0? '+' : '-';
    oDiff = Math.abs(oDiff);
    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 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);
}

/* @param {string} person - name of person
** @param {string} date - date to get times in YYYY-MM-DD format
** @param {string} loc - IANA rep. loc. e.g. America/New_York
** @returns {string} timestamp for loc
*/
function showTimes(person, date, loc) {
  // Get loc and time for person
  let sourceLoc  = data[person].loc; 
  let sourceTime = data[person].time;
  // Get UTC date for time
  let sourceDate = date + 'T' + sourceTime + ':00';
  let sourceOffset =  getUTCOffset(sourceDate, sourceLoc);
  let utcDate = new Date(sourceDate + sourceOffset);
  // Return local date for loc
  return utcDate.toLocaleString('en-CA',{timeZone: loc, timeZoneName:'long', hour12: false});
}

let data = {
  john: {
    loc: 'America/Los_Angeles', // IANA representative location
    time: '16:15'               // Must be in HH:mm format
  },
  sally: {
    loc: 'America/New_York',
    time: '08:30'
  }
}

let date = '2020-02-03';
let user1 = 'john';
let user2 = 'sally';

// Standard time
// Show John's time in Sally's location on Monday, 3 February 2020
console.log(
`${date} ${data[user1].time} for ${user1} in ${data[user1].loc } is\n\
${showTimes(user1,date, data[user2].loc)} for ${user2}`
 );
 
 // Daylight saving time
 // Show Sally's time in John's location on Friday, 26 June 2020
 date = '2020-06-26';
console.log(
`${date} ${data[user2].time} for ${user2} in ${data[user2].loc } is\n\
${showTimes(user2,date, data[user1].loc)} for ${user1}`
 );

这是一个使用 Luxon 的与上面类似的示例:

let DateTime  = luxon.DateTime;

let data = {
  john: {
    loc: 'America/Los_Angeles', // IANA representative location
    startTime: '16:15'          // Must be in HH:mm format
  },
  sally: {
    loc: 'America/New_York',
    startTime: '08:30'
  }
}

console.log('----- Standard time -----');
// What is the date and time at Sally's location when John starts on
// on Monday, 3 February 2020?
let targetDate = '2020-02-03';
let johnStartString = targetDate + 'T' + data.john.startTime;
let johnStartDate = DateTime.fromISO(johnStartString, {zone: data.john.loc});

// ISO string for John's startTime
console.log('When John starts at : ' + johnStartDate.toISO());

// Create a date for Sally's loc based on John's
let sallyDate = johnStartDate.setZone(data.sally.loc);
console.log('For Sally it\'s      : ' + sallyDate.toISO());

console.log('----- Daylight Saving time -----');
// What is the date and time at John's location when Sally starts on
// on Monday, 1 June 2020?
targetDate = '2020-06-01';
let sallyStartString = targetDate + 'T' + data.sally.startTime;
sallyStartDate = DateTime.fromISO(sallyStartString, {zone: data.sally.loc});

// ISO string for Sally's startTime
console.log('When Sally starts at: ' + sallyStartDate.toISO());

// Create a date for John's loc based on Sally's
let johnDate = sallyStartDate.setZone(data.john.loc);
console.log('For John it\'s       : ' + johnDate.toISO());
&lt;script src="https://cdn.jsdelivr.net/npm/luxon@1.23.0/build/global/luxon.min.js"&gt;&lt;/script&gt;

【讨论】:

    【解决方案2】:

    我会存储:

    • 一天中的初始时间:int
    • 一天的结束时间:int
    • 原始时区:字符串

    那么,向用户展示这是一个 UI 问题。 您可以动态计算原始时区中的两个日期(基于存储的时间),并即时将其转换为任何目标时区。

    另一种方法是检查原始时区和目标时区之间的时差(不计算任何日期)并将其添加到初始/结束时间。但我想第一个选项更容易选择the date classes have that kind of utils

    【讨论】:

      【解决方案3】:

      跟踪开始和结束时间可能会导致奇怪的时区错误。

      例如,如果有人在 EST 中选择 Monday 6pm-9pm,则实际上是在 UTC 中的 Monday 11pm - Tuesday 2am。这意味着存储在 UTC 中的时间范围是 Start: 11pmEnd: 2am,这需要大量代码来解决这些不同的场景。

      一个更好的主意可能是跟踪开始时间和距离结束时间(经过时间)的小时数。

      【讨论】:

      • 实际上只是更新了问题,范围并不那么重要,重要的是不要将时区保留在数据库中并使用通用时区来保持时间,我猜+ - 或者到时区偏移可以工作?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-08
      • 1970-01-01
      • 1970-01-01
      • 2023-04-07
      相关资源
      最近更新 更多