File: C:/Ruby27-x64/lib/ruby/gems/2.7.0/gems/tzinfo-1.2.11/lib/tzinfo/transition_data_timezone_info.rb
module TZInfo
# Raised if no offsets have been defined when calling period_for_utc or
# periods_for_local. Indicates an error in the timezone data.
class NoOffsetsDefined < StandardError
end
# Represents a data timezone defined by a set of offsets and a set
# of transitions.
#
# @private
class TransitionDataTimezoneInfo < DataTimezoneInfo #:nodoc:
# Constructs a new TransitionDataTimezoneInfo with its identifier.
def initialize(identifier)
super(identifier)
@offsets = {}
@transitions = []
@previous_offset = nil
@transitions_index = nil
end
# Defines a offset. The id uniquely identifies this offset within the
# timezone. utc_offset and std_offset define the offset in seconds of
# standard time from UTC and daylight savings from standard time
# respectively. abbreviation describes the timezone offset (e.g. GMT, BST,
# EST or EDT).
#
# The first offset to be defined is treated as the offset that applies
# until the first transition. This will usually be in Local Mean Time (LMT).
#
# ArgumentError will be raised if the id is already defined.
def offset(id, utc_offset, std_offset, abbreviation)
raise ArgumentError, 'Offset already defined' if @offsets.has_key?(id)
offset = TimezoneOffset.new(utc_offset, std_offset, abbreviation)
@offsets[id] = offset
@previous_offset = offset unless @previous_offset
end
# Defines a transition. Transitions must be defined in chronological order.
# ArgumentError will be raised if a transition is added out of order.
# offset_id refers to an id defined with offset. ArgumentError will be
# raised if the offset_id cannot be found. numerator_or_time and
# denomiator specify the time the transition occurs as. See
# TimezoneTransition for more detail about specifying times.
def transition(year, month, offset_id, numerator_or_timestamp, denominator_or_numerator = nil, denominator = nil)
offset = @offsets[offset_id]
raise ArgumentError, 'Offset not found' unless offset
if @transitions_index
if year < @last_year || (year == @last_year && month < @last_month)
raise ArgumentError, 'Transitions must be increasing date order'
end
# Record the position of the first transition with this index.
index = transition_index(year, month)
@transitions_index[index] ||= @transitions.length
# Fill in any gaps
(index - 1).downto(0) do |i|
break if @transitions_index[i]
@transitions_index[i] = @transitions.length
end
else
@transitions_index = [@transitions.length]
@start_year = year
@start_month = month
end
@transitions << TimezoneTransitionDefinition.new(offset, @previous_offset,
numerator_or_timestamp, denominator_or_numerator, denominator)
@last_year = year
@last_month = month
@previous_offset = offset
end
# Returns the TimezonePeriod for the given UTC time.
# Raises NoOffsetsDefined if no offsets have been defined.
def period_for_utc(utc)
unless @transitions.empty?
utc = TimeOrDateTime.wrap(utc)
index = transition_index(utc.year, utc.mon)
start_transition = nil
start = transition_before_end(index)
if start
start.downto(0) do |i|
if @transitions[i].at <= utc
start_transition = @transitions[i]
break
end
end
end
end_transition = nil
start = transition_after_start(index)
if start
start.upto(@transitions.length - 1) do |i|
if @transitions[i].at > utc
end_transition = @transitions[i]
break
end
end
end
if start_transition || end_transition
TimezonePeriod.new(start_transition, end_transition)
else
# Won't happen since there are transitions. Must always find one
# transition that is either >= or < the specified time.
raise 'No transitions found in search'
end
else
raise NoOffsetsDefined, 'No offsets have been defined' unless @previous_offset
TimezonePeriod.new(nil, nil, @previous_offset)
end
end
# Returns the set of TimezonePeriods for the given local time as an array.
# Results returned are ordered by increasing UTC start date.
# Returns an empty array if no periods are found for the given time.
# Raises NoOffsetsDefined if no offsets have been defined.
def periods_for_local(local)
unless @transitions.empty?
local = TimeOrDateTime.wrap(local)
index = transition_index(local.year, local.mon)
result = []
start_index = transition_after_start(index - 1)
if start_index && @transitions[start_index].local_end_at > local
if start_index > 0
if @transitions[start_index - 1].local_start_at <= local
result << TimezonePeriod.new(@transitions[start_index - 1], @transitions[start_index])
end
else
result << TimezonePeriod.new(nil, @transitions[start_index])
end
end
end_index = transition_before_end(index + 1)
if end_index
start_index = end_index unless start_index
start_index.upto(transition_before_end(index + 1)) do |i|
if @transitions[i].local_start_at <= local
if i + 1 < @transitions.length
if @transitions[i + 1].local_end_at > local
result << TimezonePeriod.new(@transitions[i], @transitions[i + 1])
end
else
result << TimezonePeriod.new(@transitions[i], nil)
end
end
end
end
result
else
raise NoOffsetsDefined, 'No offsets have been defined' unless @previous_offset
[TimezonePeriod.new(nil, nil, @previous_offset)]
end
end
# Returns an Array of TimezoneTransition instances representing the times
# where the UTC offset of the timezone changes.
#
# Transitions are returned up to a given date and time up to a given date
# and time, specified in UTC (utc_to).
#
# A from date and time may also be supplied using the utc_from parameter
# (also specified in UTC). If utc_from is not nil, only transitions from
# that date and time onwards will be returned.
#
# Comparisons with utc_to are exclusive. Comparisons with utc_from are
# inclusive. If a transition falls precisely on utc_to, it will be excluded.
# If a transition falls on utc_from, it will be included.
#
# Transitions returned are ordered by when they occur, from earliest to
# latest.
#
# utc_to and utc_from can be specified using either DateTime, Time or
# integer timestamps (Time.to_i).
#
# If utc_from is specified and utc_to is not greater than utc_from, then
# transitions_up_to raises an ArgumentError exception.
def transitions_up_to(utc_to, utc_from = nil)
utc_to = TimeOrDateTime.wrap(utc_to)
utc_from = utc_from ? TimeOrDateTime.wrap(utc_from) : nil
if utc_from && utc_to <= utc_from
raise ArgumentError, 'utc_to must be greater than utc_from'
end
unless @transitions.empty?
if utc_from
from = transition_after_start(transition_index(utc_from.year, utc_from.mon))
if from
while from < @transitions.length && @transitions[from].at < utc_from
from += 1
end
if from >= @transitions.length
return []
end
else
# utc_from is later than last transition.
return []
end
else
from = 0
end
to = transition_before_end(transition_index(utc_to.year, utc_to.mon))
if to
while to >= 0 && @transitions[to].at >= utc_to
to -= 1
end
if to < 0
return []
end
else
# utc_to is earlier than first transition.
return []
end
@transitions[from..to]
else
[]
end
end
private
# Returns the index into the @transitions_index array for a given year
# and month.
def transition_index(year, month)
index = (year - @start_year) * 2
index += 1 if month > 6
index -= 1 if @start_month > 6
index
end
# Returns the index into @transitions of the first transition that occurs
# on or after the start of the given index into @transitions_index.
# Returns nil if there are no such transitions.
def transition_after_start(index)
if index >= @transitions_index.length
nil
else
index = 0 if index < 0
@transitions_index[index]
end
end
# Returns the index into @transitions of the first transition that occurs
# before the end of the given index into @transitions_index.
# Returns nil if there are no such transitions.
def transition_before_end(index)
index = index + 1
if index <= 0
nil
elsif index >= @transitions_index.length
@transitions.length - 1
else
@transitions_index[index] - 1
end
end
end
end