2
# Copyright (c) 2005-2006 Philip Ross
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:
11
# The above copyright notice and this permission notice shall be included in all
12
# copies or substantial portions of the Software.
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
23
require 'tzinfo/offset_rationals'
24
require 'tzinfo/time_or_datetime'
27
# A period of time in a timezone where the same offset from UTC applies.
29
# All the methods that take times accept instances of Time, DateTime or
32
# The TimezoneTransitionInfo that defines the start of this TimezonePeriod
33
# (may be nil if unbounded).
34
attr_reader :start_transition
36
# The TimezoneTransitionInfo that defines the end of this TimezonePeriod
37
# (may be nil if unbounded).
38
attr_reader :end_transition
40
# The TimezoneOffsetInfo for this period.
43
# Initializes a new TimezonePeriod.
44
def initialize(start_transition, end_transition, offset = nil)
45
@start_transition = start_transition
46
@end_transition = end_transition
49
raise ArgumentError, 'Offset specified with transitions' if @start_transition || @end_transition
53
@offset = @start_transition.offset
55
@offset = @end_transition.previous_offset
57
raise ArgumentError, 'No offset specified and no transitions to determine it from'
61
@utc_total_offset_rational = nil
64
# Base offset of the timezone from UTC (seconds).
69
# Offset from the local time where daylight savings is in effect (seconds).
70
# E.g.: utc_offset could be -5 hours. Normally, std_offset would be 0.
71
# During daylight savings, std_offset would typically become +1 hours.
76
# The identifier of this period, e.g. "GMT" (Greenwich Mean Time) or "BST"
77
# (British Summer Time) for "Europe/London". The returned identifier is a
82
alias :zone_identifier :abbreviation
84
# Total offset from UTC (seconds). Equal to utc_offset + std_offset.
86
@offset.utc_total_offset
89
# Total offset from UTC (days). Result is a Rational.
90
def utc_total_offset_rational
91
unless @utc_total_offset_rational
92
@utc_total_offset_rational = OffsetRationals.rational_for_offset(utc_total_offset)
94
@utc_total_offset_rational
97
# The start time of the period in UTC as a DateTime. May be nil if unbounded.
99
@start_transition ? @start_transition.at.to_datetime : nil
102
# The end time of the period in UTC as a DateTime. May be nil if unbounded.
104
@end_transition ? @end_transition.at.to_datetime : nil
107
# The start time of the period in local time as a DateTime. May be nil if
110
@start_transition ? @start_transition.local_start.to_datetime : nil
113
# The end time of the period in local time as a DateTime. May be nil if
116
@end_transition ? @end_transition.local_end.to_datetime : nil
119
# true if daylight savings is in effect for this period; otherwise false.
124
# true if this period is valid for the given UTC DateTime; otherwise false.
125
def valid_for_utc?(utc)
126
utc_after_start?(utc) && utc_before_end?(utc)
129
# true if the given UTC DateTime is after the start of the period
130
# (inclusive); otherwise false.
131
def utc_after_start?(utc)
132
!@start_transition || @start_transition.at <= utc
135
# true if the given UTC DateTime is before the end of the period
136
# (exclusive); otherwise false.
137
def utc_before_end?(utc)
138
!@end_transition || @end_transition.at > utc
141
# true if this period is valid for the given local DateTime; otherwise false.
142
def valid_for_local?(local)
143
local_after_start?(local) && local_before_end?(local)
146
# true if the given local DateTime is after the start of the period
147
# (inclusive); otherwise false.
148
def local_after_start?(local)
149
!@start_transition || @start_transition.local_start <= local
152
# true if the given local DateTime is before the end of the period
153
# (exclusive); otherwise false.
154
def local_before_end?(local)
155
!@end_transition || @end_transition.local_end > local
158
# Converts a UTC DateTime to local time based on the offset of this period.
160
@offset.to_local(utc)
163
# Converts a local DateTime to UTC based on the offset of this period.
165
@offset.to_utc(local)
168
# Returns true if this TimezonePeriod is equal to p. This compares the
169
# start_transition, end_transition and offset using ==.
171
p.respond_to?(:start_transition) && p.respond_to?(:end_transition) &&
172
p.respond_to?(:offset) && start_transition == p.start_transition &&
173
end_transition == p.end_transition && offset == p.offset
176
# Returns true if this TimezonePeriods is equal to p. This compares the
177
# start_transition, end_transition and offset using eql?
179
p.respond_to?(:start_transition) && p.respond_to?(:end_transition) &&
180
p.respond_to?(:offset) && start_transition.eql?(p.start_transition) &&
181
end_transition.eql?(p.end_transition) && offset.eql?(p.offset)
184
# Returns a hash of this TimezonePeriod.
186
result = @start_transition.hash ^ @end_transition.hash
187
result ^= @offset.hash unless @start_transition || @end_transition
191
# Returns internal object state as a programmer-readable string.
193
result = "#<#{self.class}: #{@start_transition.inspect},#{@end_transition.inspect}"
194
result << ",#{@offset.inspect}>" unless @start_transition || @end_transition