Froze rails gems
[depot.git] / vendor / rails / activesupport / lib / active_support / vendor / tzinfo-0.3.12 / tzinfo / data_timezone_info.rb
1 #--
2 # Copyright (c) 2006 Philip Ross
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # of this software and associated documentation files (the "Software"), to deal
6 # in the Software without restriction, including without limitation the rights
7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
10 #
11 # The above copyright notice and this permission notice shall be included in all
12 # copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 # THE SOFTWARE.
21 #++
22
23 require 'tzinfo/time_or_datetime'
24 require 'tzinfo/timezone_info'
25 require 'tzinfo/timezone_offset_info'
26 require 'tzinfo/timezone_period'
27 require 'tzinfo/timezone_transition_info'
28
29 module TZInfo
30 # Thrown if no offsets have been defined when calling period_for_utc or
31 # periods_for_local. Indicates an error in the timezone data.
32 class NoOffsetsDefined < StandardError
33 end
34
35 # Represents a (non-linked) timezone defined in a data module.
36 class DataTimezoneInfo < TimezoneInfo #:nodoc:
37
38 # Constructs a new TimezoneInfo with its identifier.
39 def initialize(identifier)
40 super(identifier)
41 @offsets = {}
42 @transitions = []
43 @previous_offset = nil
44 @transitions_index = nil
45 end
46
47 # Defines a offset. The id uniquely identifies this offset within the
48 # timezone. utc_offset and std_offset define the offset in seconds of
49 # standard time from UTC and daylight savings from standard time
50 # respectively. abbreviation describes the timezone offset (e.g. GMT, BST,
51 # EST or EDT).
52 #
53 # The first offset to be defined is treated as the offset that applies
54 # until the first transition. This will usually be in Local Mean Time (LMT).
55 #
56 # ArgumentError will be raised if the id is already defined.
57 def offset(id, utc_offset, std_offset, abbreviation)
58 raise ArgumentError, 'Offset already defined' if @offsets.has_key?(id)
59
60 offset = TimezoneOffsetInfo.new(utc_offset, std_offset, abbreviation)
61 @offsets[id] = offset
62 @previous_offset = offset unless @previous_offset
63 end
64
65 # Defines a transition. Transitions must be defined in chronological order.
66 # ArgumentError will be raised if a transition is added out of order.
67 # offset_id refers to an id defined with offset. ArgumentError will be
68 # raised if the offset_id cannot be found. numerator_or_time and
69 # denomiator specify the time the transition occurs as. See
70 # TimezoneTransitionInfo for more detail about specifying times.
71 def transition(year, month, offset_id, numerator_or_time, denominator = nil)
72 offset = @offsets[offset_id]
73 raise ArgumentError, 'Offset not found' unless offset
74
75 if @transitions_index
76 if year < @last_year || (year == @last_year && month < @last_month)
77 raise ArgumentError, 'Transitions must be increasing date order'
78 end
79
80 # Record the position of the first transition with this index.
81 index = transition_index(year, month)
82 @transitions_index[index] ||= @transitions.length
83
84 # Fill in any gaps
85 (index - 1).downto(0) do |i|
86 break if @transitions_index[i]
87 @transitions_index[i] = @transitions.length
88 end
89 else
90 @transitions_index = [@transitions.length]
91 @start_year = year
92 @start_month = month
93 end
94
95 @transitions << TimezoneTransitionInfo.new(offset, @previous_offset,
96 numerator_or_time, denominator)
97 @last_year = year
98 @last_month = month
99 @previous_offset = offset
100 end
101
102 # Returns the TimezonePeriod for the given UTC time.
103 # Raises NoOffsetsDefined if no offsets have been defined.
104 def period_for_utc(utc)
105 unless @transitions.empty?
106 utc = TimeOrDateTime.wrap(utc)
107 index = transition_index(utc.year, utc.mon)
108
109 start_transition = nil
110 start = transition_before_end(index)
111 if start
112 start.downto(0) do |i|
113 if @transitions[i].at <= utc
114 start_transition = @transitions[i]
115 break
116 end
117 end
118 end
119
120 end_transition = nil
121 start = transition_after_start(index)
122 if start
123 start.upto(@transitions.length - 1) do |i|
124 if @transitions[i].at > utc
125 end_transition = @transitions[i]
126 break
127 end
128 end
129 end
130
131 if start_transition || end_transition
132 TimezonePeriod.new(start_transition, end_transition)
133 else
134 # Won't happen since there are transitions. Must always find one
135 # transition that is either >= or < the specified time.
136 raise 'No transitions found in search'
137 end
138 else
139 raise NoOffsetsDefined, 'No offsets have been defined' unless @previous_offset
140 TimezonePeriod.new(nil, nil, @previous_offset)
141 end
142 end
143
144 # Returns the set of TimezonePeriods for the given local time as an array.
145 # Results returned are ordered by increasing UTC start date.
146 # Returns an empty array if no periods are found for the given time.
147 # Raises NoOffsetsDefined if no offsets have been defined.
148 def periods_for_local(local)
149 unless @transitions.empty?
150 local = TimeOrDateTime.wrap(local)
151 index = transition_index(local.year, local.mon)
152
153 result = []
154
155 start_index = transition_after_start(index - 1)
156 if start_index && @transitions[start_index].local_end > local
157 if start_index > 0
158 if @transitions[start_index - 1].local_start <= local
159 result << TimezonePeriod.new(@transitions[start_index - 1], @transitions[start_index])
160 end
161 else
162 result << TimezonePeriod.new(nil, @transitions[start_index])
163 end
164 end
165
166 end_index = transition_before_end(index + 1)
167
168 if end_index
169 start_index = end_index unless start_index
170
171 start_index.upto(transition_before_end(index + 1)) do |i|
172 if @transitions[i].local_start <= local
173 if i + 1 < @transitions.length
174 if @transitions[i + 1].local_end > local
175 result << TimezonePeriod.new(@transitions[i], @transitions[i + 1])
176 end
177 else
178 result << TimezonePeriod.new(@transitions[i], nil)
179 end
180 end
181 end
182 end
183
184 result
185 else
186 raise NoOffsetsDefined, 'No offsets have been defined' unless @previous_offset
187 [TimezonePeriod.new(nil, nil, @previous_offset)]
188 end
189 end
190
191 private
192 # Returns the index into the @transitions_index array for a given year
193 # and month.
194 def transition_index(year, month)
195 index = (year - @start_year) * 2
196 index += 1 if month > 6
197 index -= 1 if @start_month > 6
198 index
199 end
200
201 # Returns the index into @transitions of the first transition that occurs
202 # on or after the start of the given index into @transitions_index.
203 # Returns nil if there are no such transitions.
204 def transition_after_start(index)
205 if index >= @transitions_index.length
206 nil
207 else
208 index = 0 if index < 0
209 @transitions_index[index]
210 end
211 end
212
213 # Returns the index into @transitions of the first transition that occurs
214 # before the end of the given index into @transitions_index.
215 # Returns nil if there are no such transitions.
216 def transition_before_end(index)
217 index = index + 1
218
219 if index <= 0
220 nil
221 elsif index >= @transitions_index.length
222 @transitions.length - 1
223 else
224 @transitions_index[index] - 1
225 end
226 end
227 end
228 end