When converting times from UTC to local time, the DTSTART times from the timezone definition seem to be interpreted as (almost) UTC instead of in their TZOFFSETFROM as set out in RFC5545 and as used by every ICS I've been able to find in the wild.
From a brief look, it seems that utcOffset in timezone.js compares the provided time with the timezone's change points assuming they're both normalised to the same UTC offset, but this is not necessarily true.
Below is a minimal reproduction and table setting out the provided input, actual output and expected output for this example.
const zoneDef = `BEGIN:VCALENDAR
PRODID:-//tzurl.org//NONSGML Olson 2025a//EN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Australia/Sydney
LAST-MODIFIED:20250121T082933Z
TZURL:https://www.tzurl.org/zoneinfo/Australia/Sydney
X-LIC-LOCATION:Australia/Sydney
X-PROLEPTIC-TZNAME:LMT
BEGIN:STANDARD
TZNAME:AEST
TZOFFSETFROM:+1100
TZOFFSETTO:+1000
DTSTART:20080406T030000
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:AEDT
TZOFFSETFROM:+1000
TZOFFSETTO:+1100
DTSTART:20081005T020000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=1SU
END:DAYLIGHT
END:VTIMEZONE
END:VCALENDAR
`
const sydTZ = new ICAL.Timezone({
component: new ICAL.Component(ICAL.parse(zoneDef)).getFirstSubcomponent("vtimezone"),
tzid: "Australia/Sydney"
})
console.log(ICAL.Time.fromDateTimeString('2025-04-05T16:00:00Z').convertToZone(sydTZ).toString())
| Input |
Output |
Expected |
| 2025-04-05T14:00:00Z |
2025-04-06T01:00:00 |
2025-04-06T01:00:00 |
| 2025-04-05T15:00:00Z |
2025-04-06T02:00:00 |
2025-04-06T02:00:00 |
| 2025-04-05T16:00:00Z |
2025-04-06T03:00:00 |
2025-04-06T02:00:00 |
| 2025-04-05T17:00:00Z |
2025-04-06T04:00:00 |
2025-04-06T03:00:00 |
| ... |
... |
... |
| 2025-04-06T01:00:00Z |
2025-04-06T12:00:00 |
2025-04-06T11:00:00 |
| 2025-04-06T02:00:00Z |
2025-04-06T12:00:00 |
2025-04-06T12:00:00 |
| 2025-04-06T03:00:00Z |
2025-04-06T13:00:00 |
2025-04-06T13:00:00 |
| 2025-04-06T04:00:00Z |
2025-04-06T14:00:00 |
2025-04-06T14:00:00 |
When converting times from UTC to local time, the
DTSTARTtimes from the timezone definition seem to be interpreted as (almost) UTC instead of in theirTZOFFSETFROMas set out in RFC5545 and as used by every ICS I've been able to find in the wild.From a brief look, it seems that
utcOffsetintimezone.jscompares the provided time with the timezone's change points assuming they're both normalised to the same UTC offset, but this is not necessarily true.Below is a minimal reproduction and table setting out the provided input, actual output and expected output for this example.