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
Я клонировал это репо и добавил этот тест:
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
Даты в 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()
чтобы попытаться воспроизвести эту проблему, и вот что я нашел для надежного воспроизведения этой проблемы:
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