Skip to content

Commit

Permalink
Add convenience DateTime and Time methods to TimezoneTransition.
Browse files Browse the repository at this point in the history
- Add datetime and time methods returning the value of at.
- Rename local_start and local_end to local_start_at and local_end_at.
- Introduce new local_start and local_end methods returning DateTime and
  add local_start_time and local_end_time methods returning Time
  (naming consistent with TimezonePeriod).
  • Loading branch information
philr committed Aug 26, 2013
1 parent 4b564a6 commit 7b3e4c1
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 46 deletions.
12 changes: 6 additions & 6 deletions lib/tzinfo/timezone_period.rb
Expand Up @@ -121,25 +121,25 @@ def utc_end_time
# The start time of the period in local time as a DateTime. May be nil if
# unbounded.
def local_start
@start_transition ? @start_transition.local_start.to_datetime : nil
@start_transition ? @start_transition.local_start_at.to_datetime : nil
end

# The start time of the period in local time as a Time. May be nil if
# unbounded.
def local_start_time
@start_transition ? @start_transition.local_start.to_time : nil
@start_transition ? @start_transition.local_start_at.to_time : nil
end

# The end time of the period in local time as a DateTime. May be nil if
# unbounded.
def local_end
@end_transition ? @end_transition.local_end.to_datetime : nil
@end_transition ? @end_transition.local_end_at.to_datetime : nil
end

# The end time of the period in local time as a Time. May be nil if
# unbounded.
def local_end_time
@end_transition ? @end_transition.local_end.to_time : nil
@end_transition ? @end_transition.local_end_at.to_time : nil
end

# true if daylight savings is in effect for this period; otherwise false.
Expand Down Expand Up @@ -172,13 +172,13 @@ def valid_for_local?(local)
# true if the given local DateTime is after the start of the period
# (inclusive); otherwise false.
def local_after_start?(local)
!@start_transition || @start_transition.local_start <= local
!@start_transition || @start_transition.local_start_at <= local
end

# true if the given local DateTime is before the end of the period
# (exclusive); otherwise false.
def local_before_end?(local)
!@end_transition || @end_transition.local_end > local
!@end_transition || @end_transition.local_end_at > local
end

# Converts a UTC DateTime to local time based on the offset of this period.
Expand Down
59 changes: 47 additions & 12 deletions lib/tzinfo/timezone_transition.rb
Expand Up @@ -35,8 +35,8 @@ class TimezoneTransition
def initialize(offset, previous_offset)
@offset = offset
@previous_offset = previous_offset
@local_end = nil
@local_start = nil
@local_end_at = nil
@local_start_at = nil
end

# A TimeOrDateTime instance representing the UTC time when this transition
Expand All @@ -45,29 +45,64 @@ def at
raise NotImplementedError, 'Subclasses must override at'
end

# The UTC time when this transition occurs, returned as a DateTime instance.
def datetime
at.to_datetime
end


# The UTC time when this transition occurs, returned as a Time instance.
def time
at.to_time
end

# A TimeOrDateTime instance representing the local time when this transition
# causes the previous observance to end (calculated from at using
# previous_offset).
def local_end
# Thread-safey: It is possible that the value of @local_end may be
def local_end_at
# Thread-safey: It is possible that the value of @local_end_at may be
# calculated multiple times in concurrently executing threads. It is not
# worth the overhead of locking to ensure that @local_end is only
# worth the overhead of locking to ensure that @local_end_at is only
# calculated once.

@local_end = at.add_with_convert(@previous_offset.utc_total_offset) unless @local_end
@local_end
@local_end_at = at.add_with_convert(@previous_offset.utc_total_offset) unless @local_end_at
@local_end_at
end

# The local time when this transition causes the previous observance to end,
# returned as a DateTime instance.
def local_end
local_end_at.to_datetime
end

# The local time when this transition causes the previous observance to end,
# returned as a Time instance.
def local_end_time
local_end_at.to_time
end

# A TimeOrDateTime instance representing the local time when this transition
# causes the next observance to start (calculated from at using offset).
def local_start
# Thread-safey: It is possible that the value of @local_start may be
def local_start_at
# Thread-safey: It is possible that the value of @local_start_at may be
# calculated multiple times in concurrently executing threads. It is not
# worth the overhead of locking to ensure that @local_start is only
# worth the overhead of locking to ensure that @local_start_at is only
# calculated once.

@local_start = at.add_with_convert(@offset.utc_total_offset) unless @local_start
@local_start
@local_start_at = at.add_with_convert(@offset.utc_total_offset) unless @local_start_at
@local_start_at
end

# The local time when this transition causes the next observance to start,
# returned as a DateTime instance.
def local_start
local_start_at.to_datetime
end

# The local time when this transition causes the next observance to start,
# returned as a Time instance.
def local_start_time
local_start_at.to_time
end

# Returns true if this TimezoneTransition is equal to the given
Expand Down
8 changes: 4 additions & 4 deletions lib/tzinfo/transition_data_timezone_info.rb
Expand Up @@ -148,9 +148,9 @@ def periods_for_local(local)
result = []

start_index = transition_after_start(index - 1)
if start_index && @transitions[start_index].local_end > local
if start_index && @transitions[start_index].local_end_at > local
if start_index > 0
if @transitions[start_index - 1].local_start <= local
if @transitions[start_index - 1].local_start_at <= local
result << TimezonePeriod.new(@transitions[start_index - 1], @transitions[start_index])
end
else
Expand All @@ -164,9 +164,9 @@ def periods_for_local(local)
start_index = end_index unless start_index

start_index.upto(transition_before_end(index + 1)) do |i|
if @transitions[i].local_start <= local
if @transitions[i].local_start_at <= local
if i + 1 < @transitions.length
if @transitions[i + 1].local_end > local
if @transitions[i + 1].local_end_at > local
result << TimezonePeriod.new(@transitions[i], @transitions[i + 1])
end
else
Expand Down
126 changes: 102 additions & 24 deletions test/tc_timezone_transition.rb
Expand Up @@ -52,6 +52,45 @@ def test_previous_offset
assert_equal(TimezoneOffset.new(3600, 0, :TST), t.previous_offset)
end

def test_datetime
t1 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), 1148949080)
t2 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), DateTime.new(2006, 5, 30, 0, 31, 20))
t3 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), Time.utc(2006, 5, 30, 0, 31, 20))

assert_equal(DateTime.new(2006, 5, 30, 0, 31, 20), t1.datetime)
assert_equal(DateTime.new(2006, 5, 30, 0, 31, 20), t2.datetime)
assert_equal(DateTime.new(2006, 5, 30, 0, 31, 20), t3.datetime)
end

def test_time
t1 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), 1148949080)
t2 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), DateTime.new(2006, 5, 30, 0, 31, 20))
t3 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), Time.utc(2006, 5, 30, 0, 31, 20))

assert_equal(Time.utc(2006, 5, 30, 0, 31, 20), t1.time)
assert_equal(Time.utc(2006, 5, 30, 0, 31, 20), t2.time)
assert_equal(Time.utc(2006, 5, 30, 0, 31, 20), t3.time)
end

def test_local_end_at
t1 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), 1148949080)
t2 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), DateTime.new(2006, 5, 30, 0, 31, 20))
t3 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), Time.utc(2006, 5, 30, 0, 31, 20))

assert(TimeOrDateTime.new(1148952680).eql?(t1.local_end_at))
assert(TimeOrDateTime.new(DateTime.new(2006, 5, 30, 1, 31, 20)).eql?(t2.local_end_at))
assert(TimeOrDateTime.new(Time.utc(2006, 5, 30, 1, 31, 20)).eql?(t3.local_end_at))
end

def test_local_end
t1 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), 1148949080)
Expand All @@ -60,9 +99,35 @@ def test_local_end
t3 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), Time.utc(2006, 5, 30, 0, 31, 20))

assert(TimeOrDateTime.new(1148952680).eql?(t1.local_end))
assert(TimeOrDateTime.new(DateTime.new(2006, 5, 30, 1, 31, 20)).eql?(t2.local_end))
assert(TimeOrDateTime.new(Time.utc(2006, 5, 30, 1, 31, 20)).eql?(t3.local_end))
assert_equal(DateTime.new(2006, 5, 30, 1, 31, 20), t1.local_end)
assert_equal(DateTime.new(2006, 5, 30, 1, 31, 20), t2.local_end)
assert_equal(DateTime.new(2006, 5, 30, 1, 31, 20), t3.local_end)
end

def test_local_end_time
t1 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), 1148949080)
t2 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), DateTime.new(2006, 5, 30, 0, 31, 20))
t3 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), Time.utc(2006, 5, 30, 0, 31, 20))

assert_equal(Time.utc(2006, 5, 30, 1, 31, 20), t1.local_end_time)
assert_equal(Time.utc(2006, 5, 30, 1, 31, 20), t2.local_end_time)
assert_equal(Time.utc(2006, 5, 30, 1, 31, 20), t3.local_end_time)
end

def test_local_start_at
t1 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), 1148949080)
t2 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), DateTime.new(2006, 5, 30, 0, 31, 20))
t3 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), Time.utc(2006, 5, 30, 0, 31, 20))

assert(TimeOrDateTime.new(1148956280).eql?(t1.local_start_at))
assert(TimeOrDateTime.new(DateTime.new(2006, 5, 30, 2, 31, 20)).eql?(t2.local_start_at))
assert(TimeOrDateTime.new(Time.utc(2006, 5, 30, 2, 31, 20)).eql?(t3.local_start_at))
end

def test_local_start
Expand All @@ -73,76 +138,89 @@ def test_local_start
t3 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), Time.utc(2006, 5, 30, 0, 31, 20))

assert(TimeOrDateTime.new(1148956280).eql?(t1.local_start))
assert(TimeOrDateTime.new(DateTime.new(2006, 5, 30, 2, 31, 20)).eql?(t2.local_start))
assert(TimeOrDateTime.new(Time.utc(2006, 5, 30, 2, 31, 20)).eql?(t3.local_start))
assert_equal(DateTime.new(2006, 5, 30, 2, 31, 20), t1.local_start)
assert_equal(DateTime.new(2006, 5, 30, 2, 31, 20), t2.local_start)
assert_equal(DateTime.new(2006, 5, 30, 2, 31, 20), t3.local_start)
end

def test_local_start_time
t1 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), 1148949080)
t2 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), DateTime.new(2006, 5, 30, 0, 31, 20))
t3 = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), Time.utc(2006, 5, 30, 0, 31, 20))

assert_equal(Time.utc(2006, 5, 30, 2, 31, 20), t1.local_start_time)
assert_equal(Time.utc(2006, 5, 30, 2, 31, 20), t2.local_start_time)
assert_equal(Time.utc(2006, 5, 30, 2, 31, 20), t3.local_start_time)
end

if RubyCoreSupport.time_supports_negative
def test_local_end_before_negative_32bit
def test_local_end_at_before_negative_32bit
t = TestTimezoneTransition.new(TimezoneOffset.new(-7200, 3600, :TDT),
TimezoneOffset.new(-7200, 0, :TST), -2147482800)

if RubyCoreSupport.time_supports_64bit
assert(TimeOrDateTime.new(-2147490000).eql?(t.local_end))
assert(TimeOrDateTime.new(-2147490000).eql?(t.local_end_at))
else
assert(TimeOrDateTime.new(DateTime.new(1901, 12, 13, 19, 0, 0)).eql?(t.local_end))
assert(TimeOrDateTime.new(DateTime.new(1901, 12, 13, 19, 0, 0)).eql?(t.local_end_at))
end
end

def test_local_start_before_negative_32bit
def test_local_start_at_before_negative_32bit
t = TestTimezoneTransition.new(TimezoneOffset.new(-7200, 3600, :TDT),
TimezoneOffset.new(-7200, 0, :TST), -2147482800)

if RubyCoreSupport.time_supports_64bit
assert(TimeOrDateTime.new(-2147486400).eql?(t.local_start))
assert(TimeOrDateTime.new(-2147486400).eql?(t.local_start_at))
else
assert(TimeOrDateTime.new(DateTime.new(1901, 12, 13, 20, 0, 0)).eql?(t.local_start))
assert(TimeOrDateTime.new(DateTime.new(1901, 12, 13, 20, 0, 0)).eql?(t.local_start_at))
end
end
end

def test_local_end_before_epoch
def test_local_end_at_before_epoch
t = TestTimezoneTransition.new(TimezoneOffset.new(-7200, 3600, :TDT),
TimezoneOffset.new(-7200, 0, :TST), 1800)

if RubyCoreSupport.time_supports_negative
assert(TimeOrDateTime.new(-5400).eql?(t.local_end))
assert(TimeOrDateTime.new(-5400).eql?(t.local_end_at))
else
assert(TimeOrDateTime.new(DateTime.new(1969, 12, 31, 22, 30, 0)).eql?(t.local_end))
assert(TimeOrDateTime.new(DateTime.new(1969, 12, 31, 22, 30, 0)).eql?(t.local_end_at))
end
end

def test_local_start_before_epoch
def test_local_start_at_before_epoch
t = TestTimezoneTransition.new(TimezoneOffset.new(-7200, 3600, :TDT),
TimezoneOffset.new(-7200, 0, :TST), 1800)

if RubyCoreSupport.time_supports_negative
assert(TimeOrDateTime.new(-1800).eql?(t.local_start))
assert(TimeOrDateTime.new(-1800).eql?(t.local_start_at))
else
assert(TimeOrDateTime.new(DateTime.new(1969, 12, 31, 23, 30, 0)).eql?(t.local_start))
assert(TimeOrDateTime.new(DateTime.new(1969, 12, 31, 23, 30, 0)).eql?(t.local_start_at))
end
end

def test_local_end_after_32bit
def test_local_end_at_after_32bit
t = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), 2147482800)

if RubyCoreSupport.time_supports_64bit
assert(TimeOrDateTime.new(2147486400).eql?(t.local_end))
assert(TimeOrDateTime.new(2147486400).eql?(t.local_end_at))
else
assert(TimeOrDateTime.new(DateTime.new(2038, 1, 19, 4, 0, 0)).eql?(t.local_end))
assert(TimeOrDateTime.new(DateTime.new(2038, 1, 19, 4, 0, 0)).eql?(t.local_end_at))
end
end

def test_local_start_after_32bit
def test_local_start_at_after_32bit
t = TestTimezoneTransition.new(TimezoneOffset.new(3600, 3600, :TDT),
TimezoneOffset.new(3600, 0, :TST), 2147482800)

if RubyCoreSupport.time_supports_64bit
assert(TimeOrDateTime.new(2147490000).eql?(t.local_start))
assert(TimeOrDateTime.new(2147490000).eql?(t.local_start_at))
else
assert(TimeOrDateTime.new(DateTime.new(2038, 1, 19, 5, 0, 0)).eql?(t.local_start))
assert(TimeOrDateTime.new(DateTime.new(2038, 1, 19, 5, 0, 0)).eql?(t.local_start_at))
end
end

Expand Down

0 comments on commit 7b3e4c1

Please sign in to comment.