When looking at an occurrence of a recurring event, we calculate a
bunch of start times, and convert them to UTC. So a weekly event
starting on 2025年10月30日 at 9am would generate:
- 10-30 9am -> 10-30 16:00
- 11-06 9am -> 11-06 17:00 (DST ended)
- 10-13 9am -> 11-13 17:00
- ...and so on
To generate end times, we would apply the event's original end hour and
minute to the generated times. However, we would convert the original
end time to UTC first, then grab the hour. So if the above event ended
at 10am, we would generate:
- 10-30 10am -> 10-30 17:00
- 11-06 10am -> 11-06 17:00 (bad) (DST ended)
- 10-13 10am -> 11-13 17:00 (bad)
Occurrences in standard time bound to events in DST suffer a 1-hour
difference in end time because they would be converted to UTC too
early. The hour in DST was converted, then that number (which is not
time zone aware) would be applied to the non-DST start time.
The fix is to calculate the event's duration and then apply that
duration to the start time:
duration = ActiveSupport::Duration.build(
event.ends_at - event.starts_at
)
starts_at + duration
When looking at an occurrence of a recurring event, we calculate a
bunch of start times, and convert them to UTC. So a weekly event
starting on 2025年10月30日 at 9am would generate:
* 10-30 9am -> 10-30 16:00
* 11-06 9am -> 11-06 17:00 (DST ended)
* 10-13 9am -> 11-13 17:00
* ...and so on
To generate end times, we would apply the event's original end hour and
minute to the generated times. However, we would convert the original
end time to UTC first, then grab the hour. So if the above event ended
at 10am, we would generate:
* 10-30 10am -> 10-30 17:00
* 11-06 10am -> 11-06 17:00 (bad) (DST ended)
* 10-13 10am -> 11-13 17:00 (bad)
Occurrences in standard time bound to events in DST suffer a 1-hour
difference in end time because they would be converted to UTC too
early. The hour in DST was converted, then that number (which is not
time zone aware) would be applied to the non-DST start time.
The fix is to calculate the event's _duration_ and then apply that
duration to the start time:
```rb
duration = ActiveSupport::Duration.build(
event.ends_at - event.starts_at
)
starts_at + duration
```