Skip to content

Commit

Permalink
Fix Daylight Savings parsing
Browse files Browse the repository at this point in the history
Previously the offset change for Daylight Savings was unnecessarily
added all the time, resulting in this discrepancy when the local time
was set to UTC-7:

```
Time.parse('2020-11-01 00:00:00')
=> 2020-11-01 00:00:00 -0700
Chronic.parse('2020-11-01 00:00:00')
=> 2020-11-01 01:00:00 -0700
```

Each time we add an offset to a base time, we need to check whether this
crosses Daylight Savings boundaries. Only if it does should we adjust
the time by the offset.

Closes mojombo#147
  • Loading branch information
stanhu committed Nov 23, 2019
1 parent 2b1eae7 commit d143986
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 8 deletions.
39 changes: 32 additions & 7 deletions lib/chronic/repeaters/repeater_time.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,27 +81,32 @@ def next(pointer)
yesterday_midnight = midnight - full_day
tomorrow_midnight = midnight + full_day

offset_fix = midnight.gmt_offset - tomorrow_midnight.gmt_offset
tomorrow_midnight += offset_fix

catch :done do
if pointer == :future
if @type.ambiguous?
[midnight + @type.time + offset_fix, midnight + half_day + @type.time + offset_fix, tomorrow_midnight + @type.time].each do |t|
[midnight, midnight + half_day, tomorrow_midnight].each do |base_time|
base_time = adjust_daylight_savings_offset(midnight, base_time)
t = adjust_daylight_savings_offset(base_time, base_time + @type.time)
(@current_time = t; throw :done) if t >= @now
end
else
[midnight + @type.time + offset_fix, tomorrow_midnight + @type.time].each do |t|
[midnight, tomorrow_midnight].each do |base_time|
base_time = adjust_daylight_savings_offset(midnight, base_time)
t = adjust_daylight_savings_offset(base_time, base_time + @type.time)
(@current_time = t; throw :done) if t >= @now
end
end
else # pointer == :past
if @type.ambiguous?
[midnight + half_day + @type.time + offset_fix, midnight + @type.time + offset_fix, yesterday_midnight + @type.time + half_day].each do |t|
[midnight + half_day, midnight, yesterday_midnight + half_day].each do |base_time|
base_time = adjust_daylight_savings_offset(midnight, base_time)
t = adjust_daylight_savings_offset(base_time, base_time + @type.time)
(@current_time = t; throw :done) if t <= @now
end
else
[midnight + @type.time + offset_fix, yesterday_midnight + @type.time].each do |t|
[midnight, yesterday_midnight].each do |base_time|
base_time = adjust_daylight_savings_offset(midnight, base_time)
t = adjust_daylight_savings_offset(base_time, base_time + @type.time)
(@current_time = t; throw :done) if t <= @now
end
end
Expand All @@ -119,6 +124,26 @@ def next(pointer)
Span.new(@current_time, @current_time + width)
end

# Every time a time crosses a Daylight Savings interval we must adjust the
# current time by that amount. For example, if we take midnight of Daylight
# Savings and only add an hour, the offset does not change:
#
# Time.parse('2008-03-09 00:00')
# => 2008-03-09 00:00:00 -0800
# Time.parse('2008-03-09 00:00') + (60 * 60)
# => 2008-03-09 01:00:00 -0800
#
# However, if we add 2 hours, we notice the time advances to 03:00 instead of 02:00:
#
# Time.parse('2008-03-09 00:00') + (60 * 60 * 2)
# => 2008-03-09 03:00:00 -0700
#
# Since we gained an hour and we actually want 02:00, we subtract an hour.
def adjust_daylight_savings_offset(base_time, current_time)
offset_fix = base_time.gmt_offset - current_time.gmt_offset
current_time + offset_fix
end

def this(context = :future)
super

Expand Down
6 changes: 5 additions & 1 deletion test/test_daylight_savings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ def test_end_past
t = Chronic::RepeaterTime.new('1300')
t.start = @end_daylight_savings
assert_equal Time.local(2008, 11, 1, 13), t.next(:past).begin

# unambiguous - resolve to yesterday
t = Chronic::RepeaterTime.new('1300')
t.start = @end_daylight_savings + 60 * 60 * 24 # Advance one day
assert_equal Time.local(2008, 11, 2, 13), t.next(:past).begin
end

def test_end_future
Expand Down Expand Up @@ -114,5 +119,4 @@ def test_end_future
t.start = @end_daylight_savings
assert_equal Time.local(2008, 11, 3, 4), t.next(:future).begin
end

end
4 changes: 4 additions & 0 deletions test/test_parsing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ def test_handle_generic

assert_nil Chronic.parse("1/1/32.1")

time = Chronic.parse("2020-11-01 00:00:00")
time2 = Time.parse("2020-11-01 00:00:00")
assert_in_delta time, time2, 0.000001

time = Chronic.parse("28th", {:guess => :begin})
assert_equal Time.new(Time.now.year, Time.now.month, 28), time
end
Expand Down

0 comments on commit d143986

Please sign in to comment.