Как я могу хранить часы и минуты в универсальном формате времени? - PullRequest
1 голос
/ 01 мая 2020

Я работаю над сайтом, на котором учителя вводят свои данные в зависимости от своего местного часового пояса. Например, Дэвид из Калифорнии доступен в понедельник и вторник в 16:00 по тихоокеанскому времени. Я хочу показать, что доступность для всех остальных на международном уровне в их местном формате Джон из Нью-Йорка может видеть, что Дэвид доступен в 7 вечера. EST. Есть ли стандартный способ сделать это без сохранения местного часового пояса в БД? Я думал просто выбрать случайную дату (или даже сейчас) и прикрепить час / минуту к ней, сохранить ее в UT C и для отображения просто игнорировать часть даты. это звучит разумно или есть лучший способ?

Ответы [ 3 ]

2 голосов
/ 01 мая 2020

При хранении местного времени также должны храниться данные о часовом поясе. На данный момент наиболее переносимые идентификаторы - это представительства IANA, такие как «Америка / Нью-Йорк» Таким образом, изменения в стандартном и летнем смещениях учитываются, так что с учетом конкретной даты вы можете получить сведения о времени одного человека и отобразить его как дату и время для местоположения другого человека с поправкой на его смещение на эту дату.

Ниже приведен алгоритм, он использует грубую функцию из здесь , но я бы настоятельно рекомендовал использовать библиотеку, подобную 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());
<script src="https://cdn.jsdelivr.net/npm/luxon@1.23.0/build/global/luxon.min.js"></script>
0 голосов
/ 01 мая 2020

Отслеживание начального и конечного часов может привести к странным ошибкам часового пояса.

Например, если кто-то выберет Monday 6pm-9pm в EST, это на самом деле Monday 11pm - Tuesday 2am в UT C. Это означает, что диапазон времени, сохраняемый в UT C, равен Start: 11pm и End: 2am, что требует много кода для обхода этих различных сценариев ios.

Лучшей идеей может быть отслеживание начальный час и количество часов до конечного времени (прошедшего времени).

0 голосов
/ 01 мая 2020

Я бы сохранил:

  • начальное время дня: int
  • время окончания дня: int
  • исходный часовой пояс: строка

затем, показывая, что для пользователей это проблема пользовательского интерфейса. Вы можете динамически рассчитать две даты (на основе сохраненного времени) в исходном часовом поясе и конвертировать его в любой целевой часовой пояс на лету.

альтернативой является проверка разницы во времени между исходным и целевым часовыми поясами (без вычисления каких-либо date) и добавив его к начальному / конечному времени ... но я думаю, что go для первого варианта проще, так как классы даты имеют такой тип утилит .

...