Skip to content

Commit

Permalink
realign time to the correct hour during DST changes
Browse files Browse the repository at this point in the history
  • Loading branch information
larskuhnt committed Apr 26, 2024
1 parent 0df29bc commit d6b49d2
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 34 deletions.
15 changes: 9 additions & 6 deletions lib/ice_cube/rules/weekly_rule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@ def realign(step_time, start_time)
offset = wday_offset(step_time, start_time)
time.add(:day, offset)
realigned_time = time.to_time
# when the realigned time is in a different hour, we need to adjust the
# time to the correct hour with a fixed timezone offset, otherwise
# the time will be off by an hour
# WARNING: if the next DST change is within the interval, the occurrences
# after the next DST change will be off by an hour because the timezone is fixed
realigned_time = time.to_timezoneless_time if realigned_time.hour != start_time.hour
# when the realigned time is in a different hour, it means that
# time falls to the DST switch timespan. In this case, we need to
# move the time back by one day to ensure that the hour stays the same
# WARNING: this could not work if the DST change is on a monday
# as the realigned time would be moved to the previous week.
if realigned_time.hour != start_time.hour
time.add(:day, -1)
realigned_time = time.to_time
end
super step_time, realigned_time
end

Expand Down
16 changes: 0 additions & 16 deletions lib/ice_cube/time_util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -282,22 +282,6 @@ def to_time
TimeUtil.build_in_zone(parts, @base)
end

# This is used keep the correct hour within the interval during DST
# changes. It will use the time from the schedule start time to lock the
# hour.
def to_timezoneless_time
unwrapped_time = to_time
Time.new(
unwrapped_time.year,
unwrapped_time.month,
unwrapped_time.day,
@time.hour,
@time.min,
@time.sec,
unwrapped_time.utc_offset
)
end

# DST-safely add an interval of time to the wrapped time
def add(type, val)
type = :day if type == :wday
Expand Down
22 changes: 15 additions & 7 deletions spec/examples/schedule_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -391,18 +391,17 @@
Time.utc(2014, 1, 2, 0o1, 34, 56)]
end

context "Cairo timezone", system_time_zone: 'Africa/Cairo' do
context "Cairo timezone" do
require "active_support/time"

let(:schedule) do
IceCube::Schedule.from_yaml("---\n:start_time:\n :time: 2022-05-05 22:20:00.000000000 Z\n :zone: Africa/Cairo\n:end_time:\n :time: 2022-05-06 21:40:00.000000000 Z\n :zone: Africa/Cairo\n:rrules:\n- :validations:\n :day:\n - 5\n :rule_type: IceCube::WeeklyRule\n :interval: 1\n :week_start: 1\n:rtimes: []\n:extimes: []\n")
# IceCube::Schedule.new(ActiveSupport::TimeZone['Africa/Cairo'].parse("2022-05-05 00:20:00")).tap do |schedule|
# schedule.add_recurrence_rule IceCube::Rule.weekly.day(:friday)
# end
IceCube::Schedule.new(ActiveSupport::TimeZone['Africa/Cairo'].parse("2022-05-05 00:20:00")).tap do |schedule|
schedule.add_recurrence_rule IceCube::Rule.weekly.day(:friday)
end
end

it "has the correct start time" do
expect(schedule.start_time.iso8601).to eq("2022-05-06T00:20:00+02:00")
expect(schedule.start_time.iso8601).to eq("2022-05-05T00:20:00+02:00")
end

it "has the correct start time timezone" do
Expand All @@ -412,7 +411,7 @@
it "calculates the correct occurrences from 2024-04-24" do
occurrences = schedule.next_occurrences(3, Time.utc(2024, 4, 24, 12, 0, 0))
expect(occurrences.map(&:iso8601)).to eq([
"2024-04-26T00:20:00+03:00",
"2024-04-26T01:20:00+03:00",
"2024-05-03T00:20:00+03:00",
"2024-05-10T00:20:00+03:00",
])
Expand All @@ -426,6 +425,15 @@
"2024-05-03T00:20:00+03:00",
])
end

it "preserves the timezone for the next DST switch" do
occurrences = schedule.next_occurrences(28, Time.utc(2024, 4, 24, 12, 0, 0))
expect(occurrences.map(&:iso8601).last(3)).to eq([
"2024-10-18T00:20:00+03:00",
"2024-10-25T00:20:00+03:00",
"2024-11-01T00:20:00+02:00",
])
end
end
end

Expand Down
21 changes: 16 additions & 5 deletions spec/examples/weekly_rule_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ module IceCube
end

it "calculates the correct time from 2024-04-24 12:00:00 UTC" do
expect(rule.next_time(utc_tz.parse("2024-04-24 12:00:00"), start_time, nil).iso8601).to eq("2024-04-26T00:20:00+03:00")
expect(rule.next_time(utc_tz.parse("2024-04-24 12:00:00"), start_time, nil).iso8601).to eq("2024-04-26T01:20:00+03:00")
end

it "calculates the correct time from 2024-04-26 00:20:01 Africa/Cairo" do
Expand All @@ -412,7 +412,7 @@ module IceCube

describe :realign do
require "active_support/time"

let(:timezone_name) { "Africa/Cairo" }
let(:timezone) { ActiveSupport::TimeZone[timezone_name] }
let(:utc_tz) { ActiveSupport::TimeZone["UTC"] }
Expand All @@ -424,10 +424,10 @@ module IceCube
subject { rule.realign(time, start_time) }

it "realigns the start time to the correct time" do
expect(subject.iso8601).to eq("2024-04-26T00:20:00+03:00")
expect(subject.iso8601).to eq("2024-04-25T00:20:00+02:00")
end

context "Berlin timezone" do
context "Berlin timezone CET -> CEST " do
let(:recurrence_day) { :sunday }
let(:timezone_name) { "Europe/Berlin" }
let(:start_time) { timezone.parse("2024-03-24 02:30:00") }
Expand All @@ -439,7 +439,18 @@ module IceCube
# would result in faulty start times for the following
# occurrences (03:30 instead of 02:30)
it "realigns the start time to the correct time" do
expect(subject.iso8601).to eq("2024-03-31T02:30:00+02:00")
expect(subject.iso8601).to eq("2024-03-30T02:30:00+01:00")
end
end

context "Berlin timezone CEST -> CET " do
let(:recurrence_day) { :sunday }
let(:timezone_name) { "Europe/Berlin" }
let(:start_time) { timezone.parse("2023-10-22 02:30:00") }
let(:time) { timezone.parse("2023-10-24 02:30:00") }

it "realigns the start time to the correct time" do
expect(subject.iso8601).to eq("2023-10-29T02:30:00+02:00")
end
end
end
Expand Down

0 comments on commit d6b49d2

Please sign in to comment.