tzinfo 1.2.5
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of tzinfo might be problematic. Click here for more details.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +3 -0
- data.tar.gz.sig +0 -0
- data/.yardopts +6 -0
- data/CHANGES.md +786 -0
- data/LICENSE +19 -0
- data/README.md +151 -0
- data/Rakefile +107 -0
- data/lib/tzinfo.rb +40 -0
- data/lib/tzinfo/country.rb +196 -0
- data/lib/tzinfo/country_index_definition.rb +31 -0
- data/lib/tzinfo/country_info.rb +42 -0
- data/lib/tzinfo/country_timezone.rb +135 -0
- data/lib/tzinfo/data_source.rb +190 -0
- data/lib/tzinfo/data_timezone.rb +58 -0
- data/lib/tzinfo/data_timezone_info.rb +55 -0
- data/lib/tzinfo/info_timezone.rb +30 -0
- data/lib/tzinfo/linked_timezone.rb +63 -0
- data/lib/tzinfo/linked_timezone_info.rb +26 -0
- data/lib/tzinfo/offset_rationals.rb +77 -0
- data/lib/tzinfo/ruby_core_support.rb +146 -0
- data/lib/tzinfo/ruby_country_info.rb +74 -0
- data/lib/tzinfo/ruby_data_source.rb +136 -0
- data/lib/tzinfo/time_or_datetime.rb +340 -0
- data/lib/tzinfo/timezone.rb +669 -0
- data/lib/tzinfo/timezone_definition.rb +36 -0
- data/lib/tzinfo/timezone_index_definition.rb +54 -0
- data/lib/tzinfo/timezone_info.rb +30 -0
- data/lib/tzinfo/timezone_offset.rb +101 -0
- data/lib/tzinfo/timezone_period.rb +245 -0
- data/lib/tzinfo/timezone_proxy.rb +105 -0
- data/lib/tzinfo/timezone_transition.rb +130 -0
- data/lib/tzinfo/timezone_transition_definition.rb +104 -0
- data/lib/tzinfo/transition_data_timezone_info.rb +274 -0
- data/lib/tzinfo/zoneinfo_country_info.rb +37 -0
- data/lib/tzinfo/zoneinfo_data_source.rb +488 -0
- data/lib/tzinfo/zoneinfo_timezone_info.rb +296 -0
- data/test/tc_country.rb +234 -0
- data/test/tc_country_index_definition.rb +69 -0
- data/test/tc_country_info.rb +16 -0
- data/test/tc_country_timezone.rb +173 -0
- data/test/tc_data_source.rb +218 -0
- data/test/tc_data_timezone.rb +99 -0
- data/test/tc_data_timezone_info.rb +18 -0
- data/test/tc_info_timezone.rb +34 -0
- data/test/tc_linked_timezone.rb +155 -0
- data/test/tc_linked_timezone_info.rb +23 -0
- data/test/tc_offset_rationals.rb +23 -0
- data/test/tc_ruby_core_support.rb +168 -0
- data/test/tc_ruby_country_info.rb +110 -0
- data/test/tc_ruby_data_source.rb +143 -0
- data/test/tc_time_or_datetime.rb +654 -0
- data/test/tc_timezone.rb +1350 -0
- data/test/tc_timezone_definition.rb +113 -0
- data/test/tc_timezone_index_definition.rb +73 -0
- data/test/tc_timezone_info.rb +11 -0
- data/test/tc_timezone_london.rb +143 -0
- data/test/tc_timezone_melbourne.rb +142 -0
- data/test/tc_timezone_new_york.rb +142 -0
- data/test/tc_timezone_offset.rb +126 -0
- data/test/tc_timezone_period.rb +555 -0
- data/test/tc_timezone_proxy.rb +136 -0
- data/test/tc_timezone_transition.rb +366 -0
- data/test/tc_timezone_transition_definition.rb +295 -0
- data/test/tc_timezone_utc.rb +27 -0
- data/test/tc_transition_data_timezone_info.rb +423 -0
- data/test/tc_zoneinfo_country_info.rb +78 -0
- data/test/tc_zoneinfo_data_source.rb +1195 -0
- data/test/tc_zoneinfo_timezone_info.rb +1232 -0
- data/test/test_utils.rb +163 -0
- data/test/ts_all.rb +7 -0
- data/test/ts_all_ruby.rb +5 -0
- data/test/ts_all_zoneinfo.rb +7 -0
- data/test/tzinfo-data/tzinfo/data.rb +8 -0
- data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +89 -0
- data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +315 -0
- data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +218 -0
- data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +19 -0
- data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +21 -0
- data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +21 -0
- data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +21 -0
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +261 -0
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +186 -0
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +321 -0
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +265 -0
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +220 -0
- data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +16 -0
- data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +927 -0
- data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +596 -0
- data/test/tzinfo-data/tzinfo/data/version.rb +14 -0
- data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
- data/test/zoneinfo/America/New_York +0 -0
- data/test/zoneinfo/Australia/Melbourne +0 -0
- data/test/zoneinfo/EST +0 -0
- data/test/zoneinfo/Etc/UTC +0 -0
- data/test/zoneinfo/Europe/Amsterdam +0 -0
- data/test/zoneinfo/Europe/Andorra +0 -0
- data/test/zoneinfo/Europe/London +0 -0
- data/test/zoneinfo/Europe/Paris +0 -0
- data/test/zoneinfo/Europe/Prague +0 -0
- data/test/zoneinfo/Factory +0 -0
- data/test/zoneinfo/iso3166.tab +275 -0
- data/test/zoneinfo/leapseconds +61 -0
- data/test/zoneinfo/posix/Europe/London +0 -0
- data/test/zoneinfo/posixrules +0 -0
- data/test/zoneinfo/right/Europe/London +0 -0
- data/test/zoneinfo/zone.tab +439 -0
- data/test/zoneinfo/zone1970.tab +369 -0
- data/tzinfo.gemspec +21 -0
- metadata +193 -0
- metadata.gz.sig +2 -0
@@ -0,0 +1,105 @@
|
|
1
|
+
module TZInfo
|
2
|
+
|
3
|
+
# A proxy class representing a timezone with a given identifier. TimezoneProxy
|
4
|
+
# inherits from Timezone and can be treated like any Timezone loaded with
|
5
|
+
# Timezone.get.
|
6
|
+
#
|
7
|
+
# The first time an attempt is made to access the data for the timezone, the
|
8
|
+
# real Timezone is loaded. If the proxy's identifier was not valid, then an
|
9
|
+
# exception will be raised at this point.
|
10
|
+
class TimezoneProxy < Timezone
|
11
|
+
# Construct a new TimezoneProxy for the given identifier. The identifier
|
12
|
+
# is not checked when constructing the proxy. It will be validated on the
|
13
|
+
# when the real Timezone is loaded.
|
14
|
+
def self.new(identifier)
|
15
|
+
# Need to override new to undo the behaviour introduced in Timezone#new.
|
16
|
+
tzp = super()
|
17
|
+
tzp.send(:setup, identifier)
|
18
|
+
tzp
|
19
|
+
end
|
20
|
+
|
21
|
+
# The identifier of the timezone, e.g. "Europe/Paris".
|
22
|
+
def identifier
|
23
|
+
@real_timezone ? @real_timezone.identifier : @identifier
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the TimezonePeriod for the given UTC time. utc can either be
|
27
|
+
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
|
28
|
+
# information in utc is ignored (it is treated as a UTC time).
|
29
|
+
def period_for_utc(utc)
|
30
|
+
real_timezone.period_for_utc(utc)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the set of TimezonePeriod instances that are valid for the given
|
34
|
+
# local time as an array. If you just want a single period, use
|
35
|
+
# period_for_local instead and specify how abiguities should be resolved.
|
36
|
+
# Returns an empty array if no periods are found for the given time.
|
37
|
+
def periods_for_local(local)
|
38
|
+
real_timezone.periods_for_local(local)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns an Array of TimezoneTransition instances representing the times
|
42
|
+
# where the UTC offset of the timezone changes.
|
43
|
+
#
|
44
|
+
# Transitions are returned up to a given date and time up to a given date
|
45
|
+
# and time (to).
|
46
|
+
#
|
47
|
+
# A from date and time may also be supplied using the from parameter. If
|
48
|
+
# from is not nil, only transitions from that date and time onwards will be
|
49
|
+
# returned.
|
50
|
+
#
|
51
|
+
# Comparisons with to are exclusive. Comparisons with from are inclusive.
|
52
|
+
# If a transition falls precisely on to, it will be excluded. If a
|
53
|
+
# transition falls on from, it will be included.
|
54
|
+
#
|
55
|
+
# Transitions returned are ordered by when they occur, from earliest to
|
56
|
+
# latest.
|
57
|
+
#
|
58
|
+
# to and from can be specified using either a Time, DateTime, Time or
|
59
|
+
# Timestamp.
|
60
|
+
#
|
61
|
+
# If from is specified and to is not greater than from, then an
|
62
|
+
# ArgumentError exception is raised.
|
63
|
+
#
|
64
|
+
# ArgumentError is raised if to is nil or of either to or from are
|
65
|
+
# Timestamps with unspecified offsets.
|
66
|
+
def transitions_up_to(to, from = nil)
|
67
|
+
real_timezone.transitions_up_to(to, from)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the canonical zone for this Timezone.
|
71
|
+
def canonical_zone
|
72
|
+
real_timezone.canonical_zone
|
73
|
+
end
|
74
|
+
|
75
|
+
# Dumps this TimezoneProxy for marshalling.
|
76
|
+
def _dump(limit)
|
77
|
+
identifier
|
78
|
+
end
|
79
|
+
|
80
|
+
# Loads a marshalled TimezoneProxy.
|
81
|
+
def self._load(data)
|
82
|
+
TimezoneProxy.new(data)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def setup(identifier)
|
87
|
+
@identifier = identifier
|
88
|
+
@real_timezone = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def real_timezone
|
92
|
+
# Thread-safety: It is possible that the value of @real_timezone may be
|
93
|
+
# calculated multiple times in concurrently executing threads. It is not
|
94
|
+
# worth the overhead of locking to ensure that @real_timezone is only
|
95
|
+
# calculated once.
|
96
|
+
unless @real_timezone
|
97
|
+
result = Timezone.get(@identifier)
|
98
|
+
return result if frozen?
|
99
|
+
@real_timezone = result
|
100
|
+
end
|
101
|
+
|
102
|
+
@real_timezone
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module TZInfo
|
2
|
+
# Represents a transition from one timezone offset to another at a particular
|
3
|
+
# date and time.
|
4
|
+
class TimezoneTransition
|
5
|
+
# The offset this transition changes to (a TimezoneOffset instance).
|
6
|
+
attr_reader :offset
|
7
|
+
|
8
|
+
# The offset this transition changes from (a TimezoneOffset instance).
|
9
|
+
attr_reader :previous_offset
|
10
|
+
|
11
|
+
# Initializes a new TimezoneTransition.
|
12
|
+
#
|
13
|
+
# TimezoneTransition instances should not normally be constructed manually.
|
14
|
+
def initialize(offset, previous_offset)
|
15
|
+
@offset = offset
|
16
|
+
@previous_offset = previous_offset
|
17
|
+
@local_end_at = nil
|
18
|
+
@local_start_at = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
# A TimeOrDateTime instance representing the UTC time when this transition
|
22
|
+
# occurs.
|
23
|
+
def at
|
24
|
+
raise_not_implemented('at')
|
25
|
+
end
|
26
|
+
|
27
|
+
# The UTC time when this transition occurs, returned as a DateTime instance.
|
28
|
+
def datetime
|
29
|
+
at.to_datetime
|
30
|
+
end
|
31
|
+
|
32
|
+
# The UTC time when this transition occurs, returned as a Time instance.
|
33
|
+
def time
|
34
|
+
at.to_time
|
35
|
+
end
|
36
|
+
|
37
|
+
# A TimeOrDateTime instance representing the local time when this transition
|
38
|
+
# causes the previous observance to end (calculated from at using
|
39
|
+
# previous_offset).
|
40
|
+
def local_end_at
|
41
|
+
# Thread-safety: It is possible that the value of @local_end_at may be
|
42
|
+
# calculated multiple times in concurrently executing threads. It is not
|
43
|
+
# worth the overhead of locking to ensure that @local_end_at is only
|
44
|
+
# calculated once.
|
45
|
+
|
46
|
+
unless @local_end_at
|
47
|
+
result = at.add_with_convert(@previous_offset.utc_total_offset)
|
48
|
+
return result if frozen?
|
49
|
+
@local_end_at = result
|
50
|
+
end
|
51
|
+
|
52
|
+
@local_end_at
|
53
|
+
end
|
54
|
+
|
55
|
+
# The local time when this transition causes the previous observance to end,
|
56
|
+
# returned as a DateTime instance.
|
57
|
+
def local_end
|
58
|
+
local_end_at.to_datetime
|
59
|
+
end
|
60
|
+
|
61
|
+
# The local time when this transition causes the previous observance to end,
|
62
|
+
# returned as a Time instance.
|
63
|
+
def local_end_time
|
64
|
+
local_end_at.to_time
|
65
|
+
end
|
66
|
+
|
67
|
+
# A TimeOrDateTime instance representing the local time when this transition
|
68
|
+
# causes the next observance to start (calculated from at using offset).
|
69
|
+
def local_start_at
|
70
|
+
# Thread-safety: It is possible that the value of @local_start_at may be
|
71
|
+
# calculated multiple times in concurrently executing threads. It is not
|
72
|
+
# worth the overhead of locking to ensure that @local_start_at is only
|
73
|
+
# calculated once.
|
74
|
+
|
75
|
+
unless @local_start_at
|
76
|
+
result = at.add_with_convert(@offset.utc_total_offset)
|
77
|
+
return result if frozen?
|
78
|
+
@local_start_at = result
|
79
|
+
end
|
80
|
+
|
81
|
+
@local_start_at
|
82
|
+
end
|
83
|
+
|
84
|
+
# The local time when this transition causes the next observance to start,
|
85
|
+
# returned as a DateTime instance.
|
86
|
+
def local_start
|
87
|
+
local_start_at.to_datetime
|
88
|
+
end
|
89
|
+
|
90
|
+
# The local time when this transition causes the next observance to start,
|
91
|
+
# returned as a Time instance.
|
92
|
+
def local_start_time
|
93
|
+
local_start_at.to_time
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns true if this TimezoneTransition is equal to the given
|
97
|
+
# TimezoneTransition. Two TimezoneTransition instances are
|
98
|
+
# considered to be equal by == if offset, previous_offset and at are all
|
99
|
+
# equal.
|
100
|
+
def ==(tti)
|
101
|
+
tti.kind_of?(TimezoneTransition) &&
|
102
|
+
offset == tti.offset && previous_offset == tti.previous_offset && at == tti.at
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns true if this TimezoneTransition is equal to the given
|
106
|
+
# TimezoneTransition. Two TimezoneTransition instances are
|
107
|
+
# considered to be equal by eql? if offset, previous_offset and at are all
|
108
|
+
# equal and the type used to define at in both instances is the same.
|
109
|
+
def eql?(tti)
|
110
|
+
tti.kind_of?(TimezoneTransition) &&
|
111
|
+
offset == tti.offset && previous_offset == tti.previous_offset && at.eql?(tti.at)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns a hash of this TimezoneTransition instance.
|
115
|
+
def hash
|
116
|
+
@offset.hash ^ @previous_offset.hash ^ at.hash
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns internal object state as a programmer-readable string.
|
120
|
+
def inspect
|
121
|
+
"#<#{self.class}: #{at.inspect},#{@offset.inspect}>"
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def raise_not_implemented(method_name)
|
127
|
+
raise NotImplementedError, "Subclasses must override #{method_name}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module TZInfo
|
2
|
+
# A TimezoneTransition defined by as integer timestamp, as a rational to
|
3
|
+
# create a DateTime or as both.
|
4
|
+
#
|
5
|
+
# @private
|
6
|
+
class TimezoneTransitionDefinition < TimezoneTransition #:nodoc:
|
7
|
+
# The numerator of the DateTime if the transition time is defined as a
|
8
|
+
# DateTime, otherwise the transition time as a timestamp.
|
9
|
+
attr_reader :numerator_or_time
|
10
|
+
protected :numerator_or_time
|
11
|
+
|
12
|
+
# Either the denominator of the DateTime if the transition time is defined
|
13
|
+
# as a DateTime, otherwise nil.
|
14
|
+
attr_reader :denominator
|
15
|
+
protected :denominator
|
16
|
+
|
17
|
+
# Creates a new TimezoneTransitionDefinition with the given offset,
|
18
|
+
# previous_offset (both TimezoneOffset instances) and UTC time.
|
19
|
+
#
|
20
|
+
# The time can be specified as a timestamp, as a rational to create a
|
21
|
+
# DateTime, or as both.
|
22
|
+
#
|
23
|
+
# If both a timestamp and rational are given, then the rational will only
|
24
|
+
# be used if the timestamp falls outside of the range of Time on the
|
25
|
+
# platform being used at runtime.
|
26
|
+
#
|
27
|
+
# DateTimes are created from the rational as follows:
|
28
|
+
#
|
29
|
+
# RubyCoreSupport.datetime_new!(RubyCoreSupport.rational_new!(numerator, denominator), 0, Date::ITALY)
|
30
|
+
#
|
31
|
+
# For performance reasons, the numerator and denominator must be specified
|
32
|
+
# in their lowest form.
|
33
|
+
def initialize(offset, previous_offset, numerator_or_timestamp, denominator_or_numerator = nil, denominator = nil)
|
34
|
+
super(offset, previous_offset)
|
35
|
+
|
36
|
+
if denominator
|
37
|
+
numerator = denominator_or_numerator
|
38
|
+
timestamp = numerator_or_timestamp
|
39
|
+
elsif denominator_or_numerator
|
40
|
+
numerator = numerator_or_timestamp
|
41
|
+
denominator = denominator_or_numerator
|
42
|
+
timestamp = nil
|
43
|
+
else
|
44
|
+
numerator = nil
|
45
|
+
denominator = nil
|
46
|
+
timestamp = numerator_or_timestamp
|
47
|
+
end
|
48
|
+
|
49
|
+
# Determine whether to use the timestamp or the numerator and denominator.
|
50
|
+
if numerator && (
|
51
|
+
!timestamp ||
|
52
|
+
(timestamp < 0 && !RubyCoreSupport.time_supports_negative) ||
|
53
|
+
((timestamp < -2147483648 || timestamp > 2147483647) && !RubyCoreSupport.time_supports_64bit)
|
54
|
+
)
|
55
|
+
|
56
|
+
@numerator_or_time = numerator
|
57
|
+
@denominator = denominator
|
58
|
+
else
|
59
|
+
@numerator_or_time = timestamp
|
60
|
+
@denominator = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
@at = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
# A TimeOrDateTime instance representing the UTC time when this transition
|
67
|
+
# occurs.
|
68
|
+
def at
|
69
|
+
# Thread-safety: It is possible that the value of @at may be calculated
|
70
|
+
# multiple times in concurrently executing threads. It is not worth the
|
71
|
+
# overhead of locking to ensure that @at is only calculated once.
|
72
|
+
|
73
|
+
unless @at
|
74
|
+
result = unless @denominator
|
75
|
+
TimeOrDateTime.new(@numerator_or_time)
|
76
|
+
else
|
77
|
+
r = RubyCoreSupport.rational_new!(@numerator_or_time, @denominator)
|
78
|
+
dt = RubyCoreSupport.datetime_new!(r, 0, Date::ITALY)
|
79
|
+
TimeOrDateTime.new(dt)
|
80
|
+
end
|
81
|
+
|
82
|
+
return result if frozen?
|
83
|
+
@at = result
|
84
|
+
end
|
85
|
+
|
86
|
+
@at
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns true if this TimezoneTransitionDefinition is equal to the given
|
90
|
+
# TimezoneTransitionDefinition. Two TimezoneTransitionDefinition instances
|
91
|
+
# are considered to be equal by eql? if offset, previous_offset,
|
92
|
+
# numerator_or_time and denominator are all equal.
|
93
|
+
def eql?(tti)
|
94
|
+
tti.kind_of?(TimezoneTransitionDefinition) &&
|
95
|
+
offset == tti.offset && previous_offset == tti.previous_offset &&
|
96
|
+
numerator_or_time == tti.numerator_or_time && denominator == tti.denominator
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns a hash of this TimezoneTransitionDefinition instance.
|
100
|
+
def hash
|
101
|
+
@offset.hash ^ @previous_offset.hash ^ @numerator_or_time.hash ^ @denominator.hash
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,274 @@
|
|
1
|
+
module TZInfo
|
2
|
+
# Raised if no offsets have been defined when calling period_for_utc or
|
3
|
+
# periods_for_local. Indicates an error in the timezone data.
|
4
|
+
class NoOffsetsDefined < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
# Represents a data timezone defined by a set of offsets and a set
|
8
|
+
# of transitions.
|
9
|
+
#
|
10
|
+
# @private
|
11
|
+
class TransitionDataTimezoneInfo < DataTimezoneInfo #:nodoc:
|
12
|
+
|
13
|
+
# Constructs a new TransitionDataTimezoneInfo with its identifier.
|
14
|
+
def initialize(identifier)
|
15
|
+
super(identifier)
|
16
|
+
@offsets = {}
|
17
|
+
@transitions = []
|
18
|
+
@previous_offset = nil
|
19
|
+
@transitions_index = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# Defines a offset. The id uniquely identifies this offset within the
|
23
|
+
# timezone. utc_offset and std_offset define the offset in seconds of
|
24
|
+
# standard time from UTC and daylight savings from standard time
|
25
|
+
# respectively. abbreviation describes the timezone offset (e.g. GMT, BST,
|
26
|
+
# EST or EDT).
|
27
|
+
#
|
28
|
+
# The first offset to be defined is treated as the offset that applies
|
29
|
+
# until the first transition. This will usually be in Local Mean Time (LMT).
|
30
|
+
#
|
31
|
+
# ArgumentError will be raised if the id is already defined.
|
32
|
+
def offset(id, utc_offset, std_offset, abbreviation)
|
33
|
+
raise ArgumentError, 'Offset already defined' if @offsets.has_key?(id)
|
34
|
+
|
35
|
+
offset = TimezoneOffset.new(utc_offset, std_offset, abbreviation)
|
36
|
+
@offsets[id] = offset
|
37
|
+
@previous_offset = offset unless @previous_offset
|
38
|
+
end
|
39
|
+
|
40
|
+
# Defines a transition. Transitions must be defined in chronological order.
|
41
|
+
# ArgumentError will be raised if a transition is added out of order.
|
42
|
+
# offset_id refers to an id defined with offset. ArgumentError will be
|
43
|
+
# raised if the offset_id cannot be found. numerator_or_time and
|
44
|
+
# denomiator specify the time the transition occurs as. See
|
45
|
+
# TimezoneTransition for more detail about specifying times.
|
46
|
+
def transition(year, month, offset_id, numerator_or_timestamp, denominator_or_numerator = nil, denominator = nil)
|
47
|
+
offset = @offsets[offset_id]
|
48
|
+
raise ArgumentError, 'Offset not found' unless offset
|
49
|
+
|
50
|
+
if @transitions_index
|
51
|
+
if year < @last_year || (year == @last_year && month < @last_month)
|
52
|
+
raise ArgumentError, 'Transitions must be increasing date order'
|
53
|
+
end
|
54
|
+
|
55
|
+
# Record the position of the first transition with this index.
|
56
|
+
index = transition_index(year, month)
|
57
|
+
@transitions_index[index] ||= @transitions.length
|
58
|
+
|
59
|
+
# Fill in any gaps
|
60
|
+
(index - 1).downto(0) do |i|
|
61
|
+
break if @transitions_index[i]
|
62
|
+
@transitions_index[i] = @transitions.length
|
63
|
+
end
|
64
|
+
else
|
65
|
+
@transitions_index = [@transitions.length]
|
66
|
+
@start_year = year
|
67
|
+
@start_month = month
|
68
|
+
end
|
69
|
+
|
70
|
+
@transitions << TimezoneTransitionDefinition.new(offset, @previous_offset,
|
71
|
+
numerator_or_timestamp, denominator_or_numerator, denominator)
|
72
|
+
@last_year = year
|
73
|
+
@last_month = month
|
74
|
+
@previous_offset = offset
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the TimezonePeriod for the given UTC time.
|
78
|
+
# Raises NoOffsetsDefined if no offsets have been defined.
|
79
|
+
def period_for_utc(utc)
|
80
|
+
unless @transitions.empty?
|
81
|
+
utc = TimeOrDateTime.wrap(utc)
|
82
|
+
index = transition_index(utc.year, utc.mon)
|
83
|
+
|
84
|
+
start_transition = nil
|
85
|
+
start = transition_before_end(index)
|
86
|
+
if start
|
87
|
+
start.downto(0) do |i|
|
88
|
+
if @transitions[i].at <= utc
|
89
|
+
start_transition = @transitions[i]
|
90
|
+
break
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end_transition = nil
|
96
|
+
start = transition_after_start(index)
|
97
|
+
if start
|
98
|
+
start.upto(@transitions.length - 1) do |i|
|
99
|
+
if @transitions[i].at > utc
|
100
|
+
end_transition = @transitions[i]
|
101
|
+
break
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if start_transition || end_transition
|
107
|
+
TimezonePeriod.new(start_transition, end_transition)
|
108
|
+
else
|
109
|
+
# Won't happen since there are transitions. Must always find one
|
110
|
+
# transition that is either >= or < the specified time.
|
111
|
+
raise 'No transitions found in search'
|
112
|
+
end
|
113
|
+
else
|
114
|
+
raise NoOffsetsDefined, 'No offsets have been defined' unless @previous_offset
|
115
|
+
TimezonePeriod.new(nil, nil, @previous_offset)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns the set of TimezonePeriods for the given local time as an array.
|
120
|
+
# Results returned are ordered by increasing UTC start date.
|
121
|
+
# Returns an empty array if no periods are found for the given time.
|
122
|
+
# Raises NoOffsetsDefined if no offsets have been defined.
|
123
|
+
def periods_for_local(local)
|
124
|
+
unless @transitions.empty?
|
125
|
+
local = TimeOrDateTime.wrap(local)
|
126
|
+
index = transition_index(local.year, local.mon)
|
127
|
+
|
128
|
+
result = []
|
129
|
+
|
130
|
+
start_index = transition_after_start(index - 1)
|
131
|
+
if start_index && @transitions[start_index].local_end_at > local
|
132
|
+
if start_index > 0
|
133
|
+
if @transitions[start_index - 1].local_start_at <= local
|
134
|
+
result << TimezonePeriod.new(@transitions[start_index - 1], @transitions[start_index])
|
135
|
+
end
|
136
|
+
else
|
137
|
+
result << TimezonePeriod.new(nil, @transitions[start_index])
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end_index = transition_before_end(index + 1)
|
142
|
+
|
143
|
+
if end_index
|
144
|
+
start_index = end_index unless start_index
|
145
|
+
|
146
|
+
start_index.upto(transition_before_end(index + 1)) do |i|
|
147
|
+
if @transitions[i].local_start_at <= local
|
148
|
+
if i + 1 < @transitions.length
|
149
|
+
if @transitions[i + 1].local_end_at > local
|
150
|
+
result << TimezonePeriod.new(@transitions[i], @transitions[i + 1])
|
151
|
+
end
|
152
|
+
else
|
153
|
+
result << TimezonePeriod.new(@transitions[i], nil)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
result
|
160
|
+
else
|
161
|
+
raise NoOffsetsDefined, 'No offsets have been defined' unless @previous_offset
|
162
|
+
[TimezonePeriod.new(nil, nil, @previous_offset)]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Returns an Array of TimezoneTransition instances representing the times
|
167
|
+
# where the UTC offset of the timezone changes.
|
168
|
+
#
|
169
|
+
# Transitions are returned up to a given date and time up to a given date
|
170
|
+
# and time, specified in UTC (utc_to).
|
171
|
+
#
|
172
|
+
# A from date and time may also be supplied using the utc_from parameter
|
173
|
+
# (also specified in UTC). If utc_from is not nil, only transitions from
|
174
|
+
# that date and time onwards will be returned.
|
175
|
+
#
|
176
|
+
# Comparisons with utc_to are exclusive. Comparisons with utc_from are
|
177
|
+
# inclusive. If a transition falls precisely on utc_to, it will be excluded.
|
178
|
+
# If a transition falls on utc_from, it will be included.
|
179
|
+
#
|
180
|
+
# Transitions returned are ordered by when they occur, from earliest to
|
181
|
+
# latest.
|
182
|
+
#
|
183
|
+
# utc_to and utc_from can be specified using either DateTime, Time or
|
184
|
+
# integer timestamps (Time.to_i).
|
185
|
+
#
|
186
|
+
# If utc_from is specified and utc_to is not greater than utc_from, then
|
187
|
+
# transitions_up_to raises an ArgumentError exception.
|
188
|
+
def transitions_up_to(utc_to, utc_from = nil)
|
189
|
+
utc_to = TimeOrDateTime.wrap(utc_to)
|
190
|
+
utc_from = utc_from ? TimeOrDateTime.wrap(utc_from) : nil
|
191
|
+
|
192
|
+
if utc_from && utc_to <= utc_from
|
193
|
+
raise ArgumentError, 'utc_to must be greater than utc_from'
|
194
|
+
end
|
195
|
+
|
196
|
+
unless @transitions.empty?
|
197
|
+
if utc_from
|
198
|
+
from = transition_after_start(transition_index(utc_from.year, utc_from.mon))
|
199
|
+
|
200
|
+
if from
|
201
|
+
while from < @transitions.length && @transitions[from].at < utc_from
|
202
|
+
from += 1
|
203
|
+
end
|
204
|
+
|
205
|
+
if from >= @transitions.length
|
206
|
+
return []
|
207
|
+
end
|
208
|
+
else
|
209
|
+
# utc_from is later than last transition.
|
210
|
+
return []
|
211
|
+
end
|
212
|
+
else
|
213
|
+
from = 0
|
214
|
+
end
|
215
|
+
|
216
|
+
to = transition_before_end(transition_index(utc_to.year, utc_to.mon))
|
217
|
+
|
218
|
+
if to
|
219
|
+
while to >= 0 && @transitions[to].at >= utc_to
|
220
|
+
to -= 1
|
221
|
+
end
|
222
|
+
|
223
|
+
if to < 0
|
224
|
+
return []
|
225
|
+
end
|
226
|
+
else
|
227
|
+
# utc_to is earlier than first transition.
|
228
|
+
return []
|
229
|
+
end
|
230
|
+
|
231
|
+
@transitions[from..to]
|
232
|
+
else
|
233
|
+
[]
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
private
|
238
|
+
# Returns the index into the @transitions_index array for a given year
|
239
|
+
# and month.
|
240
|
+
def transition_index(year, month)
|
241
|
+
index = (year - @start_year) * 2
|
242
|
+
index += 1 if month > 6
|
243
|
+
index -= 1 if @start_month > 6
|
244
|
+
index
|
245
|
+
end
|
246
|
+
|
247
|
+
# Returns the index into @transitions of the first transition that occurs
|
248
|
+
# on or after the start of the given index into @transitions_index.
|
249
|
+
# Returns nil if there are no such transitions.
|
250
|
+
def transition_after_start(index)
|
251
|
+
if index >= @transitions_index.length
|
252
|
+
nil
|
253
|
+
else
|
254
|
+
index = 0 if index < 0
|
255
|
+
@transitions_index[index]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Returns the index into @transitions of the first transition that occurs
|
260
|
+
# before the end of the given index into @transitions_index.
|
261
|
+
# Returns nil if there are no such transitions.
|
262
|
+
def transition_before_end(index)
|
263
|
+
index = index + 1
|
264
|
+
|
265
|
+
if index <= 0
|
266
|
+
nil
|
267
|
+
elsif index >= @transitions_index.length
|
268
|
+
@transitions.length - 1
|
269
|
+
else
|
270
|
+
@transitions_index[index] - 1
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|