jkbrzt / rrule

JavaScript library for working with recurrence rules for calendar dates as defined in the iCalendar RFC and more.
https://jkbrzt.github.io/rrule
Other
3.24k stars 506 forks source link

DTSTART (date, local time, timezone) and UNTIL creates invalid RRULE #440

Open robertgartman opened 3 years ago

robertgartman commented 3 years ago

When generating a rrule with a DTSTART having timezone (TZID), and an UNTIL then the resulting RRULE must specify UNTIL with a UTC DateTime. However, UNTIL is generated without 'Z'.

From the spec (https://icalendar.org/iCalendar-RFC-5545/3-3-10-recurrence-rule.html): If the "DTSTART" property is specified as a date with UTC time or a date with local time and time zone reference, then the UNTIL rule part MUST be specified as a date with UTC time.

To reproduce, head over to http://jakubroztocil.github.io/rrule/

Enter:

Result copied from (http://jakubroztocil.github.io/rrule/)

new RRule({
    freq: RRule.DAILY,
    dtstart: new Date(Date.UTC(2020, 11, 3, 14, 0, 0)),
    tzid: Europe/Amsterdam,
    until: new Date(Date.UTC(2020, 11, 20, 14, 0, 0)),
    count: 30,
    interval: 1
  })

rule.toString():
DTSTART;TZID=Europe/Amsterdam:20201203T140000
RRULE:FREQ=DAILY;UNTIL=20201220T140000;COUNT=30;INTERVAL=1;WKST=MO

Note the absence of 'Z' in UNTIL. The expected outcome:

DTSTART;TZID=Europe/Amsterdam:20201203T140000
RRULE:FREQ=DAILY;UNTIL=20201220T140000Z;COUNT=30;INTERVAL=1;WKST=MO

This issue is present on http://jakubroztocil.github.io/rrule/ as of Dec 2, 2020 and in release 2.6.6

iduart commented 3 years ago

I checked this library's code and It's only adding the Z if a tzid is not specified, as we can see on timeToUntilString function. By specification, it seems until should always be UTC.

 case 'UNTIL':
        outValue = dateutil.timeToUntilString(value, !options.tzid)
        break
export const timeToUntilString = function (time: number, utc = true) {
    const date = new Date(time)
    return [
      padStart(date.getUTCFullYear().toString(), 4, '0'),
      padStart(date.getUTCMonth() + 1, 2, '0'),
      padStart(date.getUTCDate(), 2, '0'),
      'T',
      padStart(date.getUTCHours(), 2, '0'),
      padStart(date.getUTCMinutes(), 2, '0'),
      padStart(date.getUTCSeconds(), 2, '0'),
      utc ? 'Z' : ''
    ].join('')
  }