Better preserve timezones in ical - #569
Conversation
…timezone info is present and update related specs
|
@sergioisidoro this is great work! I could really use this feature, do you need any help on getting this PR across the finish line? |
|
@wwahammy I'm fairly pessimistic about seeing this merged in this project. This is a breaking change, and the last commit to main has been done last year. There have been other people struggling with this issue with some atempts at solving it, and they have not been merged. At the moment I'm using my fork installing the gem through git, and I've been considering publishing my fork to rubygems for our own use, as we're starting to use recurrent events more and more, with iCal as serialization format with the API. But if you find any way of helping this through the finnish line, I would appreciate it. Or if we would find enough people to help with the maintenace of a separate fork? |
I don't think I have any more influence than you on the project. I'd be open to helping with the fork. |
|
Oh, one thing I had been meaning to ask: is the format you use for the timezones follow the iCal standard? I haven't looked into it so I wasn't sure but I wanted to know. On particular use-case is that, if we can serialize to standard iCal, we could use a postgres extensions supporting iCal to do actual queries based upon the values. |
Yes, and no. The standard does not specify a specific time zone format
https://www.ietf.org/rfc/rfc2445.txt However, it suggests the Olson tz database - ie the timezone database matinained by IANA. The current example of the timezone identifier can be seen here: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones And as you can see there are same abbreviations for multiple timezones (the old implementation). From postgres docs I can see that the "full timezome" format is supported as input
|
|
Hello, Thank you for your work on this! Am keen to keep this project alive. I will try to put some time aside this weekend to review this PR properly and see if there's a way we can get this merged. |
Thank you for all of the work you do @pacso! Totally get that you're doing this on the side and that's a lot of work. If you're open to it, it might make sense to work together on developing a path forward that spreads the workload more so you're not responsible for so much. We're all benefiting so we should all pitch-in after all. |
There was a problem hiding this comment.
I self reviewed the PR :) there were a few typos and a small nitpick. This work was done a bit in a hurry to fix our bugs with the lost time in serialization.
Let me know if there's anything else that needs attention on this PR, and I'll fixe everything in one go
|
Status? @wwahammy are you still open to help maintaining a fork? |
Follow-up fixes to the TZID support added earlier on this branch, found by auditing the round-trip for issues of the same class. Parser: - Do not require ActiveSupport. The TZID path called String#in_time_zone, which raised NoMethodError for anyone parsing an iCal string containing a TZID without ActiveSupport loaded; ice_cube has no runtime dependency on it. Zones now resolve against ActiveSupport when present, then TZInfo, and otherwise warn and fall back to zone-less parsing. - Read TZID from any parameter position. Only the first parameter was inspected, so DTSTART;VALUE=DATE-TIME;TZID=America/New_York silently lost its zone and fell back to the system zone. - Accept double-quoted TZID values, and split the content line at the first colon that is not inside a quoted parameter, since quoting exists precisely to allow a colon in the value (RFC 5545 sections 3.1 and 3.2.19). - Match the TZID parameter name case-insensitively. - Warn instead of silently falling back to the system zone for an unresolvable TZID, which Outlook's Windows zone names hit routinely. Builder: - Serialize the IANA identifier rather than the ActiveSupport zone label. A schedule built with Time.zone = "Eastern Time (US & Canada)" emitted TZID=Eastern Time (US & Canada), which round-trips within ice_cube but is meaningless to every other iCalendar implementation. - Warn once per process, not on every occurrence, when coercing a zone-less Time to UTC. The suite alone emitted this 25 times. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019tjCXM4KcVq2wjuj2QAZkJ
Hi @sergioisidoro, yes I'm still open to it! |
|
|
||
| @missing_time_zone_warned = true | ||
| warn "IceCube: Time object does not have timezone info. Coercing into UTC: #{caller(2..2).first}" | ||
| end |
There was a problem hiding this comment.
I think this warn change should be its own PR, it's not really related to preserving timezones in ical. EDIT: Oops, nevermind, I realized you added the warn in this PR originally. Again, 7:30 in the morning 😄
Additionally, it's 7:30 in the morning so I might be reading this wrong but are we sure this is actually going to warn once per process? Is there a situation where the class gets reloaded or there's some sort of race condition on this?
There was a problem hiding this comment.
Yes, I added this warning because it was one of the reasons I made this PR - it defaulted to falling back to UTC without anything surfaced to the dev. If I would have seen this in the initial test runs, it would have been clear that the behaviour was not expected. I think this also serves a bit as a mitigation for the breaking change.
Good question. This change was introduced by claude code in the lastest round of reviewing (which fixed the ActiveRecord dependency issue). I previosuly was issuing warning per instance, which could easily overwhelm the logs. First question is -- is it enough to warn once, to allow the dev to know of this behaviour? Or is issuing a warning per instance actually desired?
About the per process, I had to search this. Ruby classes are instances of a Class, so that instance variable missing_time_zone_warned will be set once per class when it is loaded. I assume that a reload will re-instantiate that Class instance 🤔 It solves the problem of spamming the logs, but the comment may be factually incorrect.

Trying to approach #568
May also fix:
#535
Claude summary
The commit
Harden iCal TZID handlingfollows up on the original TZID support in this PR, after auditing the iCal round-trip for issues of the same class. Each item below was reproduced before fixing and is covered by a spec.Parser
Dropped the hard ActiveSupport dependency. The TZID path called
String#in_time_zone, so any iCal string containing aTZID=raisedNoMethodErrorfor users without ActiveSupport loaded — ice_cube has no runtime dependency on it. Zones now resolve against ActiveSupport when present, then TZInfo, and otherwise warn and fall back to zone-less parsing. (The TZInfo result is flattened to a plain fixed-offsetTime: TZInfo 2 returns aTimeWithOffsetwhose#zoneis an abbreviation, which sendsmatch_zonedown its system-local branch and relocates every occurrence into the system zone. That path is DST-unaware after the start time and warns once to say so.)TZID is now read from any parameter position. Only the first parameter was inspected, so
DTSTART;VALUE=DATE-TIME;TZID=America/New_York:...silently lost its zone and fell back to the system zone.Quoted TZID values are accepted, and the content line is split at the first colon that is not inside a quoted parameter — quoting exists precisely to permit a colon in the value (RFC 5545 §3.1 and §3.2.19). The TZID parameter name is also matched case-insensitively.
An unresolvable TZID warns instead of silently falling back to the system zone. Outlook emits Windows zone names such as
TZID=Eastern Standard Time, which hit this constantly and produced silently wrong times.Builder
Serializes the IANA identifier rather than the ActiveSupport zone label. A schedule built with
Time.zone = "Eastern Time (US & Canada)"emittedTZID=Eastern Time (US & Canada). That round-trips within ice_cube, which is why the specs passed, but it is meaningless to every other iCalendar implementation — relevant to @wwahammy's question above about standards conformance and the Postgres use case. It now emitsAmerica/New_York. This is why theto_icalspec expectations changed.Warns once per process rather than on every occurrence when coercing a zone-less
Timeto UTC. The suite alone emitted this 25 times.Also folded in the unresolved review comments on this PR: the
reversivibletypo, the "Coercing into UTC" wording, moving the helper intoTimeUtil, and the warning on the unknown-zone fallback.Known gaps, not addressed here
Pre-existing issues found in the same audit and deliberately left out to keep this PR reviewable:
DTSTART's zone.EXDATE:20130102T090000under a zonedDTSTARTsilently fails to exclude the occurrence, and a floatingUNTILdrops the last one. Fixing this needs a two-pass parse to establish the schedule's zone before reading the other properties.dtstart:...is silently ignored and the schedule starts atTime.now.DURATIONis still a no-op (the existing# FIXME), andVALUE=DATEall-day events become midnight in the system zone.Happy to take any of these on in a follow-up if that would help.
Suite: 1259 examples, 0 failures;
standardrbclean.🤖 Generated with Claude Code