Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Time arithmetic accounts for Daylight Savings transition hour gain/loss #391

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions lib/chronic/repeaters/repeater_day_portion.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'active_support/time'

module Chronic
class RepeaterDayPortion < Repeater #:nodoc:
PORTIONS = {
Expand Down Expand Up @@ -31,23 +33,23 @@ def next(pointer)
if now_seconds < @range.begin
case pointer
when :future
range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin
range_start = adjust_with_offset(@now, 0, @range)
when :past
range_start = Chronic.construct(@now.year, @now.month, @now.day - 1) + @range.begin
range_start = adjust_with_offset(@now, -1, @range)
end
elsif now_seconds > @range.end
case pointer
when :future
range_start = Chronic.construct(@now.year, @now.month, @now.day + 1) + @range.begin
range_start = adjust_with_offset(@now, +1, @range)
when :past
range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin
range_start = adjust_with_offset(@now, 0, @range)
end
else
case pointer
when :future
range_start = Chronic.construct(@now.year, @now.month, @now.day + 1) + @range.begin
range_start = adjust_with_offset(@now, +1, @range)
when :past
range_start = Chronic.construct(@now.year, @now.month, @now.day - 1) + @range.begin
range_start = adjust_with_offset(@now, -1, @range)
end
end
offset = (@range.end - @range.begin)
Expand All @@ -71,7 +73,7 @@ def next(pointer)
def this(context = :future)
super

range_start = Chronic.construct(@now.year, @now.month, @now.day) + @range.begin
range_start = adjust_with_offset(@now, 0, @range)
range_end = construct_date_from_reference_and_offset(range_start)
@current_span = Span.new(range_start, range_end)
end
Expand Down Expand Up @@ -105,5 +107,13 @@ def construct_date_from_reference_and_offset(reference, offset = nil)
hour_hand = (elapsed_seconds_for_range - minute_hand - second_hand) / (60 * 60) + reference.hour % 24
Chronic.construct(reference.year, reference.month, reference.day, hour_hand, minute_hand, second_hand)
end

# Uses Time#utc_offset to account for time gain/loss across Daylight Savings Time change boundaries
def adjust_with_offset(now, adjustment = 0, range)
initial = Chronic.construct(now.year, now.month, now.day) + adjustment.days
final = initial + range.begin

final + initial.utc_offset.seconds - final.utc_offset.seconds
end
end
end
8 changes: 8 additions & 0 deletions test/test_parsing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ def test_handle_generic
assert_equal Time.new(Time.now.year, Time.now.month, 28), time
end

def test_handle_daylight_savings_transitions
time = parse_now("3/10/2019 12:35:04 PM")
assert_equal Time.local(2019, 3, 10, 12, 35, 04), time

time = parse_now("11/3/2019 11:35:04 PM")
assert_equal Time.local(2019, 11, 3, 23, 35, 04), time
end

def test_handle_rmn_sd
time = parse_now("aug 3")
assert_equal Time.local(2007, 8, 3, 12), time
Expand Down
13 changes: 13 additions & 0 deletions test/test_repeater_day_portion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ def test_pm_future_with_daylight_savings_time_boundary

assert_equal Time.local(2012, 11, 4, 12), next_next_time.begin
assert_equal Time.local(2012, 11, 4, 23, 59, 59), next_next_time.end

@now = Time.local(2012,11,3,12,40,12)
day_portion = Chronic::RepeaterDayPortion.new(:pm)
day_portion.start = @now

next_time = day_portion.next(:future)
assert_equal Time.local(2012, 11, 4, 12), next_time.begin
assert_equal Time.local(2012, 11, 4, 23, 59, 59), next_time.end

next_next_time = day_portion.next(:future)

assert_equal Time.local(2012, 11, 5, 12), next_next_time.begin
assert_equal Time.local(2012, 11, 5, 23, 59, 59), next_next_time.end
end

def test_morning_future
Expand Down