Rrule: DTSTART with TZID: between 产生不正确的时间

创建于 2019-07-16  ·  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

当我设置由@davidgoli实现的tzid ,我得到了不同的结果:

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) {}

这是什么原因?
规则版本:2.6.2
我在 CDT 时区下运行节点。 我的系统时区是America/Los_Angeles

所有16条评论

我克隆了这个 repo 并添加了这个测试:

  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 时区下运行它时它才会成功(当我在launch.json设置"env": { "TZ": "UTC" }时)。 测试在任何其他时区失败。
@davidgoli是这个意思吗,我在这里遗漏了什么? 如果是,如何正确使用tzid参数? 我认为添加它是为了告诉rrule在指定的时区运行。

我有同样的问题,但使用 rrule.all()。 UTC 有效,而任何其他时区在不应该时再次应用偏移量。

我认为这可能与 rrule-tz.js 的第 1849 行有关。

var rezonedDate = rezoneIfNeeded(res, options);

rezoneIfNeeded与其名称相反,不执行任何检查并且总是重新分区,除非它是 UTC。

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

看起来它正在重新分区日期,即使它已经正确分区。

rezonedDate()更改this.date似乎为我解决了问题。 我不能完全确定我在其他地方弄坏了一些东西,但粗略地看一下,当进行此更改时,其他一切似乎都正常。

我认为这可能与 rrule-tz.js 的第 1849 行有关。

var rezonedDate = rezoneIfNeeded(res, options);

rezoneIfNeeded与其名称相反,不执行任何检查并且总是重新分区,除非它是 UTC。

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

看起来它正在重新分区日期,即使它已经正确分区。

这是不正确的。 rezonedDate方法检查是否设置了tzid以确定是否应用区域偏移。

@agordeev我会调查这个问题。 @hlee5zebra 不需要简单地删除时区支持的“修复”。 如果您在任何一种情况下都不使用tzid参数,您会得到预期的结果吗?

无论如何,RRule 和 RRuleSet 行为之间的不一致与我有关,并且可能是一个需要更深入研究的错误。 感谢测试用例!

我看到了导致我的问题的问题——我的服务器代码没有正确处理 DTSTART 值时间,当它应该本地化到 TZID 指定的时区时传入 UTC。 也许这可以帮助您解决您的问题@agordeev 。 感谢@davidgoli为我指明了正确的方向。

看起来我可能在代码库中偶然发现了一个问题。 datewithzone.tstoString()方法将在提供 TZID 时输出 UTC 时间,而此时它应该输出本地化时间。

@hlee5zebra确保在自述文件中注明此文本:

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

重要提示:使用 UTC 日期

JavaScript 中的日期很棘手。 RRule 试图在不添加任何大的所需的 3rd 方依赖项的情况下支持尽可能多的灵活性,但这意味着我们也有一些特殊的规则。

默认情况下,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 设置为“America/Adak”、“America/Chicago”(我的当地时间)、“America/New_York”和“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都通过.toISOString()打印时,第一个参数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(例如 America/Adak、America/New_York)时,将从 DTSTART 日期中减去本地时间与所选时区之间的偏移量。 所以纽约 ISO 字符串显示 23:00,因为我的当地时间和纽约之间的偏移量为 +1,当从 07/18/2019 的午夜减去时,结果是我们看到的,即 07/17 的晚上 11 点/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我会调查这个问题。 @hlee5zebra 不需要简单地删除时区支持的“修复”。 如果您在任何一种情况下都不使用 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。 当我省略 tzid 参数时,rrule 会考虑 UTC 中的 DTSTART。

如果您始终希望重复出现在用户的本地时区,则不应使用 tzid。

我认为在用户的时区中获得重复的唯一选择是将该时区传递给 tzid 参数? 请记住,该库与服务器上的 node.js 一起使用。

如果重复发生在用户本地时间的 1800 点_不管时区_,那么您不需要使用tzid 。 请确保您已阅读自述文件中描述的“浮动”时间: https :

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

grigio picture grigio  ·  7评论

elazar picture elazar  ·  18评论

marcoancona picture marcoancona  ·  22评论

shorlbeck picture shorlbeck  ·  21评论

Prinzhorn picture Prinzhorn  ·  15评论