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.

Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +3 -0
  3. data.tar.gz.sig +0 -0
  4. data/.yardopts +6 -0
  5. data/CHANGES.md +786 -0
  6. data/LICENSE +19 -0
  7. data/README.md +151 -0
  8. data/Rakefile +107 -0
  9. data/lib/tzinfo.rb +40 -0
  10. data/lib/tzinfo/country.rb +196 -0
  11. data/lib/tzinfo/country_index_definition.rb +31 -0
  12. data/lib/tzinfo/country_info.rb +42 -0
  13. data/lib/tzinfo/country_timezone.rb +135 -0
  14. data/lib/tzinfo/data_source.rb +190 -0
  15. data/lib/tzinfo/data_timezone.rb +58 -0
  16. data/lib/tzinfo/data_timezone_info.rb +55 -0
  17. data/lib/tzinfo/info_timezone.rb +30 -0
  18. data/lib/tzinfo/linked_timezone.rb +63 -0
  19. data/lib/tzinfo/linked_timezone_info.rb +26 -0
  20. data/lib/tzinfo/offset_rationals.rb +77 -0
  21. data/lib/tzinfo/ruby_core_support.rb +146 -0
  22. data/lib/tzinfo/ruby_country_info.rb +74 -0
  23. data/lib/tzinfo/ruby_data_source.rb +136 -0
  24. data/lib/tzinfo/time_or_datetime.rb +340 -0
  25. data/lib/tzinfo/timezone.rb +669 -0
  26. data/lib/tzinfo/timezone_definition.rb +36 -0
  27. data/lib/tzinfo/timezone_index_definition.rb +54 -0
  28. data/lib/tzinfo/timezone_info.rb +30 -0
  29. data/lib/tzinfo/timezone_offset.rb +101 -0
  30. data/lib/tzinfo/timezone_period.rb +245 -0
  31. data/lib/tzinfo/timezone_proxy.rb +105 -0
  32. data/lib/tzinfo/timezone_transition.rb +130 -0
  33. data/lib/tzinfo/timezone_transition_definition.rb +104 -0
  34. data/lib/tzinfo/transition_data_timezone_info.rb +274 -0
  35. data/lib/tzinfo/zoneinfo_country_info.rb +37 -0
  36. data/lib/tzinfo/zoneinfo_data_source.rb +488 -0
  37. data/lib/tzinfo/zoneinfo_timezone_info.rb +296 -0
  38. data/test/tc_country.rb +234 -0
  39. data/test/tc_country_index_definition.rb +69 -0
  40. data/test/tc_country_info.rb +16 -0
  41. data/test/tc_country_timezone.rb +173 -0
  42. data/test/tc_data_source.rb +218 -0
  43. data/test/tc_data_timezone.rb +99 -0
  44. data/test/tc_data_timezone_info.rb +18 -0
  45. data/test/tc_info_timezone.rb +34 -0
  46. data/test/tc_linked_timezone.rb +155 -0
  47. data/test/tc_linked_timezone_info.rb +23 -0
  48. data/test/tc_offset_rationals.rb +23 -0
  49. data/test/tc_ruby_core_support.rb +168 -0
  50. data/test/tc_ruby_country_info.rb +110 -0
  51. data/test/tc_ruby_data_source.rb +143 -0
  52. data/test/tc_time_or_datetime.rb +654 -0
  53. data/test/tc_timezone.rb +1350 -0
  54. data/test/tc_timezone_definition.rb +113 -0
  55. data/test/tc_timezone_index_definition.rb +73 -0
  56. data/test/tc_timezone_info.rb +11 -0
  57. data/test/tc_timezone_london.rb +143 -0
  58. data/test/tc_timezone_melbourne.rb +142 -0
  59. data/test/tc_timezone_new_york.rb +142 -0
  60. data/test/tc_timezone_offset.rb +126 -0
  61. data/test/tc_timezone_period.rb +555 -0
  62. data/test/tc_timezone_proxy.rb +136 -0
  63. data/test/tc_timezone_transition.rb +366 -0
  64. data/test/tc_timezone_transition_definition.rb +295 -0
  65. data/test/tc_timezone_utc.rb +27 -0
  66. data/test/tc_transition_data_timezone_info.rb +423 -0
  67. data/test/tc_zoneinfo_country_info.rb +78 -0
  68. data/test/tc_zoneinfo_data_source.rb +1195 -0
  69. data/test/tc_zoneinfo_timezone_info.rb +1232 -0
  70. data/test/test_utils.rb +163 -0
  71. data/test/ts_all.rb +7 -0
  72. data/test/ts_all_ruby.rb +5 -0
  73. data/test/ts_all_zoneinfo.rb +7 -0
  74. data/test/tzinfo-data/tzinfo/data.rb +8 -0
  75. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +89 -0
  76. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +315 -0
  77. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +218 -0
  78. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +19 -0
  79. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +21 -0
  80. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +21 -0
  81. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +21 -0
  82. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +261 -0
  83. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +186 -0
  84. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +321 -0
  85. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +265 -0
  86. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +220 -0
  87. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +16 -0
  88. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +927 -0
  89. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +596 -0
  90. data/test/tzinfo-data/tzinfo/data/version.rb +14 -0
  91. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  92. data/test/zoneinfo/America/New_York +0 -0
  93. data/test/zoneinfo/Australia/Melbourne +0 -0
  94. data/test/zoneinfo/EST +0 -0
  95. data/test/zoneinfo/Etc/UTC +0 -0
  96. data/test/zoneinfo/Europe/Amsterdam +0 -0
  97. data/test/zoneinfo/Europe/Andorra +0 -0
  98. data/test/zoneinfo/Europe/London +0 -0
  99. data/test/zoneinfo/Europe/Paris +0 -0
  100. data/test/zoneinfo/Europe/Prague +0 -0
  101. data/test/zoneinfo/Factory +0 -0
  102. data/test/zoneinfo/iso3166.tab +275 -0
  103. data/test/zoneinfo/leapseconds +61 -0
  104. data/test/zoneinfo/posix/Europe/London +0 -0
  105. data/test/zoneinfo/posixrules +0 -0
  106. data/test/zoneinfo/right/Europe/London +0 -0
  107. data/test/zoneinfo/zone.tab +439 -0
  108. data/test/zoneinfo/zone1970.tab +369 -0
  109. data/tzinfo.gemspec +21 -0
  110. metadata +193 -0
  111. 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