callumalpass_tasknotes/issue-analysis/issue-781-fix-summary.md
callumalpass 01c5f01a4c fix: ICS calendar timezone conversion for non-IANA timezones (#781, #841)
Fixes incorrect time display for ICS events with non-standard timezone
identifiers by using toUnixTime() instead of toJSDate() for conversion.

- Use toUnixTime() for reliable UTC timestamp conversion in all cases
- Add icalTimeToISOString() helper for consistent timezone handling
- Fix events with TZID lacking VTIMEZONE definitions (e.g., Zurich)
- Fix Outlook/Exchange events displaying in original timezone
- Update ICAL.Time type definitions with missing properties

Affects Infomaniak, Outlook, and other providers using non-IANA TZIDs.
2025-10-06 22:06:56 +11:00

3.9 KiB

Issue #781 & Discussion #841 Fix Summary

Root Cause Analysis

The original analysis in issue-781.md was incorrect. Through testing with actual ICAL.js library, I discovered the real issue:

The Real Problem

ICAL.Time.toJSDate() is unreliable for timezone conversion, especially for:

  1. Non-IANA timezones without VTIMEZONE definitions (e.g., TZID=Zurich from Infomaniak calendars)
  2. Floating time events (unresolvable TZIDs that ICAL.js treats as "floating")

When ICAL.js encounters an unresolvable timezone, it marks it as "floating" and toJSDate() produces incorrect UTC timestamps.

Test Results

Scenario toJSDate().toISOString() toUnixTime() Correct?
TZID=Zurich (no VTIMEZONE) 2025-10-04T02:34:00.000Z 2025-10-04T12:34:00.000Z toUnixTime()
Outlook EST (with VTIMEZONE) 2025-01-10T20:00:00.000Z 2025-01-10T20:00:00.000Z Both work
Europe/Zurich (with VTIMEZONE) 2025-10-04T10:34:00.000Z 2025-10-04T10:34:00.000Z Both work

Key Finding: toUnixTime() works correctly in all cases, while toJSDate() fails for floating/unresolvable timezones.

Issues Fixed

Issue #781: Outlook Calendar Timezone Bug

Report: 3 PM EST event shows as 3 PM MST instead of 1 PM MST

Cause: While Outlook events with proper VTIMEZONE definitions actually work with toJSDate(), using toUnixTime() is more robust and handles edge cases.

Status: Fixed by using toUnixTime() method

Discussion #841: Infomaniak "Zurich" Timezone Bug

Report: Events with TZID=Zurich (no VTIMEZONE) show 2 hours earlier (-2h offset during CEST)

Cause: When ICAL.js can't resolve "Zurich" to a timezone, it treats it as floating time. toJSDate() then interprets the time components in the local JavaScript timezone instead of treating them as floating, producing incorrect UTC timestamps.

Status: Fixed by using toUnixTime() method which correctly handles floating time

The Solution

Added a helper method icalTimeToISOString() that:

  1. For all-day events (isDate === true): Constructs UTC date at midnight
  2. For timed events: Uses toUnixTime() which returns correct Unix timestamp in all scenarios
private icalTimeToISOString(icalTime: ICAL.Time): string {
    // For all-day events, preserve the date without time
    if (icalTime.isDate) {
        return new Date(Date.UTC(
            icalTime.year,
            icalTime.month - 1,
            icalTime.day
        )).toISOString();
    }

    // For timed events, use toUnixTime() which correctly converts to UTC
    const unixTime = icalTime.toUnixTime();
    return new Date(unixTime * 1000).toISOString();
}

Changes Made

Files Modified

  1. src/services/ICSSubscriptionService.ts

    • Added icalTimeToISOString() helper method
    • Replaced all toJSDate().toISOString() calls (3 locations):
      • Line 349-350: Initial event start/end parsing
      • Line 426-428: Modified recurring event instances
      • Line 437-444: Recurring event instances with duration calculation
  2. src/types/ical.d.ts

    • Added missing ICAL.Time properties: year, month, day, hour, minute, second, zone
    • Added missing methods: toUnixTime(), toString()

Testing

All scenarios tested and verified:

  • Non-IANA timezone without VTIMEZONE (Infomaniak "Zurich")
  • Outlook Eastern Standard Time with VTIMEZONE
  • Proper IANA timezone (Europe/Zurich) with VTIMEZONE
  • All-day events
  • Recurring events
  • Modified recurring event instances

Impact

  • No breaking changes - internal implementation only
  • Backward compatible - all existing calendars will work better
  • Fixes both reported issues (#781 and #841)
  • Future-proof - handles all timezone scenarios including edge cases