Rrule: DTSTART с TZID: между выдает неверное время

Созданный на 16 июл. 2019  ·  16Комментарии  ·  Источник: jakubroztocil/rrule

Сообщение о проблеме

const ruleStr = [
'DTSTART;TZID=America/Los_Angeles:20190603T181500', 
'RRULE:FREQ=WEEKLY;WKST=SU;BYDAY=MO,TU,WE,FR,SA'
].join('\n')
const rule = RRule.fromString(ruleStr)
const result = rule.between(
new Date('2019-07-16T07:00:00.000-07:00'), 
new Date('2019-07-23T07:00:00.000-07:00')
)
console.log(result)

выводит это:

0:Tue Jul 16 2019 15:15:00 GMT-0500 (CDT) {}
1:Wed Jul 17 2019 15:15:00 GMT-0500 (CDT) {}
2:Fri Jul 19 2019 15:15:00 GMT-0500 (CDT) {}
3:Sat Jul 20 2019 15:15:00 GMT-0500 (CDT) {}
4:Mon Jul 22 2019 15:15:00 GMT-0500 (CDT) {}

15:15:00 GMT-0500 (CDT) - 17:15 PDT, а DTSTART - DTSTART;TZID=America/Los_Angeles:20190603T181500

Когда я устанавливаю tzid реализованное @davidgoli, я получаю другой результат:

const ruleStr = [
'DTSTART:20190603T181500', 
'RRULE:FREQ=WEEKLY;WKST=SU;BYDAY=MO,TU,WE,FR,SA'
].join('\n')
const rule = RRule.fromString(ruleStr)
const ruleSet = new RRuleSet()
ruleSet.rrule(rule)
ruleSet.tzid('America/Los_Angeles')
const result = ruleSet.between(
new Date('2019-07-16T07:00:00.000-07:00'), 
new Date('2019-07-23T07:00:00.000-07:00')
)
console.log(result)
0:Tue Jul 16 2019 13:15:00 GMT-0500 (CDT) {}
1:Wed Jul 17 2019 13:15:00 GMT-0500 (CDT) {}
2:Fri Jul 19 2019 13:15:00 GMT-0500 (CDT) {}
3:Sat Jul 20 2019 13:15:00 GMT-0500 (CDT) {}
4:Mon Jul 22 2019 13:15:00 GMT-0500 (CDT) {}

Ожидаемый результат:

0:Tue Jul 16 2019 20:15:00 GMT-0500 (CDT) {}
1:Wed Jul 17 2019 20:15:00 GMT-0500 (CDT) {}
2:Fri Jul 19 2019 20:15:00 GMT-0500 (CDT) {}
3:Sat Jul 20 2019 20:15:00 GMT-0500 (CDT) {}
4:Mon Jul 22 2019 20:15:00 GMT-0500 (CDT) {}

В чем причина этого?
rrule версия: 2.6.2
Я запускаю узел в часовом поясе CDT. Часовой пояс моей системы: America/Los_Angeles

Все 16 Комментарий

Я клонировал это репо и добавил этот тест:

  it('generates correct recurrences when recurrence is WEEKLY and has BYDAY specified', () => {
    const rrule = new RRule({
      freq: RRule.WEEKLY,
      dtstart: new Date(Date.UTC(2019, 6, 17, 18, 0, 0)),
      tzid: 'America/Los_Angeles',
      count: 10,
      interval: 1,
      wkst: RRule.SU,
      byweekday: [RRule.MO, RRule.TU, RRule.WE, RRule.FR, RRule.SA]
    })

    expect(rrule.all()).to.deep.equal([
      new Date('2019-07-17T18:00:00.000-07:00'), // WE
      new Date('2019-07-19T18:00:00.000-07:00'), // FR
      new Date('2019-07-20T18:00:00.000-07:00'), // SA
      new Date('2019-07-22T18:00:00.000-07:00'), // MO
      new Date('2019-07-23T18:00:00.000-07:00'), // TU
      new Date('2019-07-24T18:00:00.000-07:00'),
      new Date('2019-07-26T18:00:00.000-07:00'),
      new Date('2019-07-27T18:00:00.000-07:00'),
      new Date('2019-07-29T18:00:00.000-07:00'),
      new Date('2019-07-30T18:00:00.000-07:00')
    ])
  })

Это удается только тогда, когда я запускаю его в часовом поясе UTC (когда я устанавливаю "env": { "TZ": "UTC" } в launch.json ). Тест не проходит в любом другом часовом поясе.
@davidgoli , это предназначено, и мне что-то здесь не хватает? Если да, то как правильно использовать параметр tzid ? Я думаю, это было добавлено, чтобы указать rrule чтобы он работал в указанном часовом поясе.

У меня такая же проблема, но с rrule.all (). UTC работает, в то время как любой другой часовой пояс снова применяет смещение, когда этого не должно быть.

Я думаю, это может иметь какое-то отношение к строке 1849 файла rrule-tz.js.

var rezonedDate = rezoneIfNeeded(res, options);

rezoneIfNeeded , вопреки своему названию, не выполняет никаких проверок и всегда повторно зонирует, если это не UTC.

function rezoneIfNeeded(date, options) { return new datewithzone_DateWithZone(date, options.tzid).rezonedDate(); }

Похоже, это переназначает дату, хотя она уже правильно зонирована.

Изменение rezonedDate() чтобы просто вернуть this.date похоже, исправляет ситуацию. Я не могу быть полностью уверен, что что-то сломал где-то еще, но с беглого взгляда похоже, что все остальное работает нормально после внесения этого изменения.

Я думаю, это может иметь какое-то отношение к строке 1849 файла rrule-tz.js.

var rezonedDate = rezoneIfNeeded(res, options);

rezoneIfNeeded , вопреки своему названию, не выполняет никаких проверок и всегда повторно зонирует, если это не UTC.

function rezoneIfNeeded(date, options) { return new datewithzone_DateWithZone(date, options.tzid).rezonedDate(); }

Похоже, это переназначает дату, хотя она уже правильно зонирована.

Это не так. Метод rezonedDate проверяет, установлено ли значение tzid чтобы определить, применять ли смещение зоны.

@agordeev Я В "исправлении" поясов не было необходимости. Получите ли вы ожидаемые результаты, если просто не используете параметр tzid в любом случае?

Несмотря на это, меня беспокоит несоответствие между поведением RRule и RRuleSet и, вероятно, ошибка, требующая более глубокого изучения. Спасибо за тестовый пример!

Я вижу проблему, которая привела к моей проблеме - мой серверный код неправильно обрабатывал значение времени DTSTART, передавая время в формате UTC, когда оно должно быть локализовано в часовой пояс, указанный TZID. Возможно, это поможет вам решить вашу проблему @davidgoli за то, что указал мне в правильном направлении.

Похоже, я наткнулся на проблему в кодовой базе. Метод toString() в datewithzone.ts будет выводить время в формате UTC, когда указан TZID, тогда как вместо этого он должен выводить локализованное время.

@ hlee5zebra обязательно обратите внимание на этот текст в README:

https://github.com/jakubroztocil/rrule#important -use-utc -ates

Важно: используйте даты в формате UTC

Даты в JavaScript - это непросто. RRule пытается поддерживать как можно большую гибкость без добавления каких-либо больших требуемых сторонних зависимостей, но это означает, что у нас также есть некоторые особые правила.

По умолчанию RRule работает с «плавающим» временем или часовыми поясами UTC. Если вам нужны результаты в определенном часовом поясе, RRule также предоставляет поддержку часового пояса. В любом случае встроенное смещение "часового пояса" JavaScript имеет тенденцию просто мешать, поэтому эта библиотека просто не использует его вообще. Все времена возвращаются с нулевым смещением, как будто его не существует в JavaScript.

Суть в том, что возвращаемые даты в формате «UTC» всегда должны интерпретироваться как даты в вашем часовом поясе. Это может означать, что вам нужно выполнить дополнительное преобразование, чтобы получить «правильное» местное время с примененным смещением.

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

Дополнительная морщина есть в большинстве реализаций JS, вы либо получаете смещение UTC _, либо_ локальное смещение, но вы не можете переключаться между ними. Это также зависит от реализации. Таким образом, "дата в формате UTC" может быть возвращена с указанием toString() которое включает смещение в одних местах, но не в других.

Например, в Chrome:

> new Date(Date.UTC(2016, 10, 5))
Fri Nov 04 2016 17:00:00 GMT-0700 (Pacific Daylight Time)

но в узле:

> new Date(Date.UTC(2016, 10, 5))
2016-11-05T00:00:00.000Z

Вот почему для достижения наилучшего результата игнорируйте значение toString() и используйте исключительно методы toISOString() и getUTCHours() (и т. Д.).

Обратите внимание, что этот подход - использование только методов UTC для всех дат, а затем «интерпретация» их как находящихся в местном времени - позволяет единообразно получать доступ к датам и времени, возвращаемым rrule _, без необходимости учитывать часовой пояс rrule_.

Хорошо, я немного повозился с rrule.all() чтобы попытаться воспроизвести эту проблему, и вот что я нашел для надежного воспроизведения этой проблемы:

  1. Я попытался установить DTSTART как полночь в соответствующих часовых поясах «Америка / Адак», «Америка / Чикаго» (мое местное время), «Америка / Нью-Йорк» и «UTC», и в результате получилось rrule.toString() печатается точно:
>> rRule.toString()
"DTSTART;TZID=America/Adak:20190718T000000
RRULE:FREQ=DAILY"

>> rRule.toString()
"DTSTART;TZID=America/Chicago:20190718T000000
RRULE:FREQ=DAILY"

>> rRule.toString()
"DTSTART;TZID=America/New_York:20190718T000000
RRULE:FREQ=DAILY"

>> rRule.toString()
"DTSTART:20190718T000000Z
RRULE:FREQ=DAILY"

При просмотре функции итератора, переданной в rrule.all() , первый экземпляр первого параметра date оказывается следующим для каждого часового пояса, когда каждый Date печатается через .toISOString() :

Америка / Адак:
"2019-07-18T04: 00: 00.000Z"

Америка / Чикаго:
"2019-07-18T00: 00: 00.000Z"

Америка / Нью-Йорк:
"2019-07-17T23: 00: 00.000Z"

УНИВЕРСАЛЬНОЕ ГЛОБАЛЬНОЕ ВРЕМЯ:
"2019-07-18T00: 00: 00.000Z"

Похоже, что когда часовой пояс не установлен на UTC (например, Америка / Адак, Америка / Нью-Йорк), то смещение между вашим местным временем и выбранным часовым поясом вычитается из даты DTSTART. Таким образом, строка ISO в Нью-Йорке показывает 23:00, поскольку смещение между моим местным временем и Нью-Йорком составляет +1, что при вычитании из полуночи 18.07.2019 дает то, что мы видим, то есть 23:00 17.07. / 2019.

Обратите внимание, что для UTC этого не происходит, что любопытно.

Как вы думаете, может ли это быть проблемой, или есть какая-то конфигурация, которую я мог пропустить?

В качестве обходного пути к тому, что я видел, я сделал следующее, и это, кажется, надежно дает мне точный экземпляр JavaScript Date :

rRule.all(function (date, i) {
    if (this.getSelectedTzid() !== 'UTC') {
        date = moment.tz({
            year: date.getUTCFullYear(),
            month: date.getUTCMonth(),
            date: date.getUTCDate(),
            hours: date.getUTCHours(),
            minutes: date.getUTCMinutes()
        }, this.getLocalTzid()).toDate();
    }

    ...
}.bind(this));

@ hlee5zebra Да, ваш подход к использованию методов getUTCxxx является правильным, рекомендованным ридми.

Имейте в виду, что дата в формате псевдо-UTC почти никогда не совпадает с временем в формате UTC. UTC просто перегружен, чтобы быть «нейтральным» часовым поясом, поэтому методы getUTCxxx можно использовать для получения _local_ времени независимо от исходной зоны. По этой причине вы должны ожидать увидеть такое же поведение без использования tzid как при использовании вашего местного часового пояса в вашем местном часовом поясе. tzid следует использовать только для получения текущего местного времени повторения _ в другом часовом поясе_. Если вы всегда хотите, чтобы повторения находились в локальном часовом поясе пользователя, вам не следует использовать tzid .

Вот почему при переписывании этой библиотеки я вообще не буду использовать встроенный объект JS Date. Это просто слишком сбивает с толку.

@agordeev Я В "исправлении" поясов не было необходимости. Получите ли вы ожидаемые результаты, если просто не используете параметр tzid в любом случае?

Спасибо за ответ, Дэвид.

    const rrule = new RRule({
      freq: RRule.WEEKLY,
      dtstart: new Date(Date.UTC(2019, 6, 17, 18, 0, 0)),
      // tzid: 'America/Los_Angeles',
      count: 10,
      interval: 1,
      wkst: RRule.SU,
      byweekday: [RRule.MO, RRule.TU, RRule.WE, RRule.FR, RRule.SA]
    })

производит:

  -  [Date: 2019-07-17T18:00:00.000Z]
  -  [Date: 2019-07-19T18:00:00.000Z]
  -  [Date: 2019-07-20T18:00:00.000Z]
  -  [Date: 2019-07-22T18:00:00.000Z]
  etc..

Значит, даты и время правильные, но часовой пояс - UTC. rrule считает DTSTART в UTC, когда я опускаю параметр tzid.

Если вы всегда хотите, чтобы повторения находились в локальном часовом поясе пользователя, вам не следует использовать tzid.

Я думал, что единственный вариант получить повторения в часовом поясе пользователя - передать этот часовой пояс параметру tzid? Помните, что библиотека используется с node.js на сервере.

Если повторение будет в 1800 по местному времени пользователя _не зависимости от часового пояса_, тогда вам не нужно использовать tzid . Убедитесь, что вы прочитали о «плавающем» времени, как описано в README: https://github.com/jakubroztocil/rrule#important -use-utc -ates

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

shavenwalrus picture shavenwalrus  ·  7Комментарии

mapidemic picture mapidemic  ·  7Комментарии

elazar picture elazar  ·  18Комментарии

Prinzhorn picture Prinzhorn  ·  15Комментарии

berardo picture berardo  ·  9Комментарии