tzinfo 1.2.11 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.yardopts +3 -0
  4. data/CHANGES.md +469 -431
  5. data/LICENSE +13 -13
  6. data/README.md +368 -114
  7. data/lib/tzinfo/country.rb +131 -129
  8. data/lib/tzinfo/country_timezone.rb +70 -112
  9. data/lib/tzinfo/data_source.rb +389 -144
  10. data/lib/tzinfo/data_sources/constant_offset_data_timezone_info.rb +56 -0
  11. data/lib/tzinfo/data_sources/country_info.rb +42 -0
  12. data/lib/tzinfo/data_sources/data_timezone_info.rb +91 -0
  13. data/lib/tzinfo/data_sources/linked_timezone_info.rb +33 -0
  14. data/lib/tzinfo/data_sources/ruby_data_source.rb +141 -0
  15. data/lib/tzinfo/data_sources/timezone_info.rb +47 -0
  16. data/lib/tzinfo/data_sources/transitions_data_timezone_info.rb +214 -0
  17. data/lib/tzinfo/data_sources/zoneinfo_data_source.rb +573 -0
  18. data/lib/tzinfo/data_sources/zoneinfo_reader.rb +284 -0
  19. data/lib/tzinfo/data_sources.rb +8 -0
  20. data/lib/tzinfo/data_timezone.rb +33 -47
  21. data/lib/tzinfo/datetime_with_offset.rb +153 -0
  22. data/lib/tzinfo/format1/country_definer.rb +17 -0
  23. data/lib/tzinfo/format1/country_index_definition.rb +64 -0
  24. data/lib/tzinfo/format1/timezone_definer.rb +64 -0
  25. data/lib/tzinfo/format1/timezone_definition.rb +39 -0
  26. data/lib/tzinfo/format1/timezone_index_definition.rb +77 -0
  27. data/lib/tzinfo/format1.rb +10 -0
  28. data/lib/tzinfo/format2/country_definer.rb +68 -0
  29. data/lib/tzinfo/format2/country_index_definer.rb +68 -0
  30. data/lib/tzinfo/format2/country_index_definition.rb +46 -0
  31. data/lib/tzinfo/format2/timezone_definer.rb +94 -0
  32. data/lib/tzinfo/format2/timezone_definition.rb +73 -0
  33. data/lib/tzinfo/format2/timezone_index_definer.rb +45 -0
  34. data/lib/tzinfo/format2/timezone_index_definition.rb +55 -0
  35. data/lib/tzinfo/format2.rb +10 -0
  36. data/lib/tzinfo/info_timezone.rb +26 -21
  37. data/lib/tzinfo/linked_timezone.rb +33 -52
  38. data/lib/tzinfo/offset_timezone_period.rb +42 -0
  39. data/lib/tzinfo/string_deduper.rb +118 -0
  40. data/lib/tzinfo/time_with_offset.rb +128 -0
  41. data/lib/tzinfo/timestamp.rb +548 -0
  42. data/lib/tzinfo/timestamp_with_offset.rb +85 -0
  43. data/lib/tzinfo/timezone.rb +979 -502
  44. data/lib/tzinfo/timezone_offset.rb +84 -74
  45. data/lib/tzinfo/timezone_period.rb +151 -217
  46. data/lib/tzinfo/timezone_proxy.rb +70 -79
  47. data/lib/tzinfo/timezone_transition.rb +77 -109
  48. data/lib/tzinfo/transitions_timezone_period.rb +63 -0
  49. data/lib/tzinfo/version.rb +7 -0
  50. data/lib/tzinfo/with_offset.rb +61 -0
  51. data/lib/tzinfo.rb +60 -40
  52. data.tar.gz.sig +0 -0
  53. metadata +51 -115
  54. metadata.gz.sig +2 -3
  55. data/Rakefile +0 -107
  56. data/lib/tzinfo/annual_rules.rb +0 -51
  57. data/lib/tzinfo/country_index_definition.rb +0 -31
  58. data/lib/tzinfo/country_info.rb +0 -42
  59. data/lib/tzinfo/data_timezone_info.rb +0 -55
  60. data/lib/tzinfo/linked_timezone_info.rb +0 -26
  61. data/lib/tzinfo/offset_rationals.rb +0 -77
  62. data/lib/tzinfo/posix_time_zone_parser.rb +0 -136
  63. data/lib/tzinfo/ruby_core_support.rb +0 -176
  64. data/lib/tzinfo/ruby_country_info.rb +0 -74
  65. data/lib/tzinfo/ruby_data_source.rb +0 -136
  66. data/lib/tzinfo/time_or_datetime.rb +0 -351
  67. data/lib/tzinfo/timezone_definition.rb +0 -36
  68. data/lib/tzinfo/timezone_index_definition.rb +0 -54
  69. data/lib/tzinfo/timezone_info.rb +0 -30
  70. data/lib/tzinfo/timezone_transition_definition.rb +0 -104
  71. data/lib/tzinfo/transition_data_timezone_info.rb +0 -274
  72. data/lib/tzinfo/transition_rule.rb +0 -325
  73. data/lib/tzinfo/zoneinfo_country_info.rb +0 -37
  74. data/lib/tzinfo/zoneinfo_data_source.rb +0 -504
  75. data/lib/tzinfo/zoneinfo_timezone_info.rb +0 -516
  76. data/test/assets/payload.rb +0 -1
  77. data/test/tc_annual_rules.rb +0 -95
  78. data/test/tc_country.rb +0 -240
  79. data/test/tc_country_index_definition.rb +0 -69
  80. data/test/tc_country_info.rb +0 -16
  81. data/test/tc_country_timezone.rb +0 -173
  82. data/test/tc_data_source.rb +0 -218
  83. data/test/tc_data_timezone.rb +0 -99
  84. data/test/tc_data_timezone_info.rb +0 -18
  85. data/test/tc_info_timezone.rb +0 -34
  86. data/test/tc_linked_timezone.rb +0 -155
  87. data/test/tc_linked_timezone_info.rb +0 -23
  88. data/test/tc_offset_rationals.rb +0 -23
  89. data/test/tc_posix_time_zone_parser.rb +0 -261
  90. data/test/tc_ruby_core_support.rb +0 -168
  91. data/test/tc_ruby_country_info.rb +0 -110
  92. data/test/tc_ruby_data_source.rb +0 -175
  93. data/test/tc_time_or_datetime.rb +0 -674
  94. data/test/tc_timezone.rb +0 -1361
  95. data/test/tc_timezone_definition.rb +0 -113
  96. data/test/tc_timezone_index_definition.rb +0 -73
  97. data/test/tc_timezone_info.rb +0 -11
  98. data/test/tc_timezone_london.rb +0 -143
  99. data/test/tc_timezone_melbourne.rb +0 -142
  100. data/test/tc_timezone_new_york.rb +0 -142
  101. data/test/tc_timezone_offset.rb +0 -126
  102. data/test/tc_timezone_period.rb +0 -555
  103. data/test/tc_timezone_proxy.rb +0 -136
  104. data/test/tc_timezone_transition.rb +0 -366
  105. data/test/tc_timezone_transition_definition.rb +0 -295
  106. data/test/tc_timezone_utc.rb +0 -27
  107. data/test/tc_transition_data_timezone_info.rb +0 -433
  108. data/test/tc_transition_rule.rb +0 -663
  109. data/test/tc_zoneinfo_country_info.rb +0 -78
  110. data/test/tc_zoneinfo_data_source.rb +0 -1226
  111. data/test/tc_zoneinfo_timezone_info.rb +0 -2149
  112. data/test/test_utils.rb +0 -214
  113. data/test/ts_all.rb +0 -7
  114. data/test/ts_all_ruby.rb +0 -5
  115. data/test/ts_all_zoneinfo.rb +0 -9
  116. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +0 -89
  117. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +0 -327
  118. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +0 -230
  119. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +0 -19
  120. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +0 -21
  121. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +0 -21
  122. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +0 -21
  123. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +0 -273
  124. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +0 -198
  125. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +0 -333
  126. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +0 -277
  127. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +0 -235
  128. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +0 -16
  129. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +0 -940
  130. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +0 -609
  131. data/test/tzinfo-data/tzinfo/data/version.rb +0 -20
  132. data/test/tzinfo-data/tzinfo/data.rb +0 -8
  133. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  134. data/test/zoneinfo/America/New_York +0 -0
  135. data/test/zoneinfo/Australia/Melbourne +0 -0
  136. data/test/zoneinfo/EST +0 -0
  137. data/test/zoneinfo/Etc/UTC +0 -0
  138. data/test/zoneinfo/Europe/Amsterdam +0 -0
  139. data/test/zoneinfo/Europe/Andorra +0 -0
  140. data/test/zoneinfo/Europe/London +0 -0
  141. data/test/zoneinfo/Europe/Paris +0 -0
  142. data/test/zoneinfo/Europe/Prague +0 -0
  143. data/test/zoneinfo/Factory +0 -0
  144. data/test/zoneinfo/iso3166.tab +0 -274
  145. data/test/zoneinfo/leapseconds +0 -78
  146. data/test/zoneinfo/posix/Europe/London +0 -0
  147. data/test/zoneinfo/posixrules +0 -0
  148. data/test/zoneinfo/right/Europe/London +0 -0
  149. data/test/zoneinfo/zone.tab +0 -452
  150. data/test/zoneinfo/zone1970.tab +0 -384
  151. data/tzinfo.gemspec +0 -21
@@ -0,0 +1,42 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module TZInfo
5
+ module DataSources
6
+ # Represents a country and references to its time zones as returned by a
7
+ # {DataSource}.
8
+ class CountryInfo
9
+ # @return [String] the ISO 3166-1 alpha-2 country code.
10
+ attr_reader :code
11
+
12
+ # @return [String] the name of the country.
13
+ attr_reader :name
14
+
15
+ # @return [Array<CountryTimezone>] the time zones observed in the country.
16
+ attr_reader :zones
17
+
18
+ # Initializes a new {CountryInfo}. The passed in `code`, `name` and
19
+ # `zones` instances will be frozen.
20
+ #
21
+ # @param code [String] an ISO 3166-1 alpha-2 country code.
22
+ # @param name [String] the name of the country.
23
+ # @param zones [Array<CountryTimezone>] the time zones observed in the
24
+ # country.
25
+ # @raise [ArgumentError] if `code`, `name` or `zones` is `nil`.
26
+ def initialize(code, name, zones)
27
+ raise ArgumentError, 'code must be specified' unless code
28
+ raise ArgumentError, 'name must be specified' unless name
29
+ raise ArgumentError, 'zones must be specified' unless zones
30
+ @code = code.freeze
31
+ @name = name.freeze
32
+ @zones = zones.freeze
33
+ end
34
+
35
+ # @return [String] the internal object state as a programmer-readable
36
+ # `String`.
37
+ def inspect
38
+ "#<#{self.class}: #@code>"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,91 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module TZInfo
5
+ module DataSources
6
+ # The base class for time zones defined as either a series of transitions
7
+ # ({TransitionsDataTimezoneInfo}) or a constantly observed offset
8
+ # ({ConstantOffsetDataTimezoneInfo}).
9
+ #
10
+ # @abstract Data sources return instances of {DataTimezoneInfo} subclasses.
11
+ class DataTimezoneInfo < TimezoneInfo
12
+ # @param timestamp [Timestamp] a {Timestamp} with a specified
13
+ # {Timestamp#utc_offset utc_offset}.
14
+ # @return [TimezonePeriod] the {TimezonePeriod} observed at the time
15
+ # specified by `timestamp`.
16
+ # @raise [ArgumentError] may be raised if `timestamp` is `nil` or does not
17
+ # have a specified {Timestamp#utc_offset utc_offset}.
18
+ def period_for(timestamp)
19
+ raise_not_implemented('period_for')
20
+ end
21
+
22
+ # Returns an `Array` containing the {TimezonePeriod TimezonePeriods} that
23
+ # could be observed at the local time specified by `local_timestamp`. The
24
+ # results are are ordered by increasing UTC start date. An empty `Array`
25
+ # is returned if no periods are found for the given local time.
26
+ #
27
+ # @param local_timestamp [Timestamp] a {Timestamp} representing a local
28
+ # time - must have an unspecified {Timestamp#utc_offset utc_offset}.
29
+ # @return [Array<TimezonePeriod>] an `Array` containing the
30
+ # {TimezonePeriod TimezonePeriods} that could be observed at the local
31
+ # time specified by `local_timestamp`.
32
+ # @raise [ArgumentError] may be raised if `local_timestamp` is `nil`, or
33
+ # has a specified {Timestamp#utc_offset utc_offset}.
34
+ def periods_for_local(local_timestamp)
35
+ raise_not_implemented('periods_for_local')
36
+ end
37
+
38
+ # Returns an `Array` of {TimezoneTransition} instances representing the
39
+ # times where the UTC offset of the time zone changes.
40
+ #
41
+ # Transitions are returned up to a given {Timestamp} (`to_timestamp`).
42
+ #
43
+ # A from {Timestamp} may also be supplied using the `from_timestamp`
44
+ # parameter. If `from_timestamp` is specified, only transitions from that
45
+ # time onwards will be returned.
46
+ #
47
+ # Comparisons with `to_timestamp` are exclusive. Comparisons with
48
+ # `from_timestamp` are inclusive. If a transition falls precisely on
49
+ # `to_timestamp`, it will be excluded. If a transition falls on
50
+ # `from_timestamp`, it will be included.
51
+ #
52
+ # Transitions returned are ordered by when they occur, from earliest to
53
+ # latest.
54
+ #
55
+ # @param to_timestamp [Timestamp] a {Timestamp} with a specified
56
+ # {Timestamp#utc_offset utc_offset}. Transitions are returned if they
57
+ # occur before this time.
58
+ # @param from_timestamp [Timestamp] an optional {Timestamp} with a
59
+ # specified {Timestamp#utc_offset utc_offset}. If specified, transitions
60
+ # are returned if they occur at or after this time.
61
+ # @return [Array<TimezoneTransition>] an `Array` of {TimezoneTransition}
62
+ # instances representing the times where the UTC offset of the time zone
63
+ # changes.
64
+ # @raise [ArgumentError] may be raised if `to_timestamp` is `nil` or does
65
+ # not have a specified {Timestamp#utc_offset utc_offset}.
66
+ # @raise [ArgumentError] may be raised if `from_timestamp` is specified
67
+ # but does not have a specified {Timestamp#utc_offset utc_offset}.
68
+ # @raise [ArgumentError] may be raised if `from_timestamp` is specified
69
+ # but is not earlier than or at the same time as `to_timestamp`.
70
+ def transitions_up_to(to_timestamp, from_timestamp = nil)
71
+ raise_not_implemented('transitions_up_to')
72
+ end
73
+
74
+ # @return [DataTimezone] a new {DataTimezone} instance for the time zone
75
+ # represented by this {DataTimezoneInfo}.
76
+ def create_timezone
77
+ DataTimezone.new(self)
78
+ end
79
+
80
+ private
81
+
82
+ # Raises a {NotImplementedError} to indicate that the base class is
83
+ # incorrectly being used directly.
84
+ #
85
+ # raise [NotImplementedError] always.
86
+ def raise_not_implemented(method_name)
87
+ raise NotImplementedError, "Subclasses must override #{method_name}"
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+
3
+ module TZInfo
4
+ module DataSources
5
+ # Represents a time zone that is defined as a link to or alias of another
6
+ # zone.
7
+ class LinkedTimezoneInfo < TimezoneInfo
8
+ # @return [String] the identifier of the time zone that provides the data
9
+ # (that this zone links to or is an alias for).
10
+ attr_reader :link_to_identifier
11
+
12
+ # Initializes a new {LinkedTimezoneInfo}. The passed in `identifier` and
13
+ # `link_to_identifier` instances will be frozen.
14
+ #
15
+ # @param identifier [String] the identifier of the time zone.
16
+ # @param link_to_identifier [String] the identifier of the time zone that
17
+ # this zone link to.
18
+ # @raise [ArgumentError] if `identifier` or `link_to_identifier` are
19
+ # `nil`.
20
+ def initialize(identifier, link_to_identifier)
21
+ super(identifier)
22
+ raise ArgumentError, 'link_to_identifier must be specified' unless link_to_identifier
23
+ @link_to_identifier = link_to_identifier.freeze
24
+ end
25
+
26
+ # @return [LinkedTimezone] a new {LinkedTimezone} instance for the time
27
+ # zone represented by this {LinkedTimezoneInfo}.
28
+ def create_timezone
29
+ LinkedTimezone.new(self)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,141 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module TZInfo
5
+ module DataSources
6
+ # A {TZInfoDataNotFound} exception is raised if the tzinfo-data gem could
7
+ # not be found (i.e. `require 'tzinfo/data'` failed) when selecting the Ruby
8
+ # data source.
9
+ class TZInfoDataNotFound < StandardError
10
+ end
11
+
12
+ # A DataSource implementation that loads data from the set of Ruby modules
13
+ # included in the tzinfo-data gem.
14
+ #
15
+ # TZInfo will use {RubyDataSource} by default if the tzinfo-data gem
16
+ # is available on the load path. It can also be selected by calling
17
+ # {DataSource.set} as follows:
18
+ #
19
+ # TZInfo::DataSource.set(:ruby)
20
+ class RubyDataSource < DataSource
21
+ # (see DataSource#data_timezone_identifiers)
22
+ attr_reader :data_timezone_identifiers
23
+
24
+ # (see DataSource#linked_timezone_identifiers)
25
+ attr_reader :linked_timezone_identifiers
26
+
27
+ # (see DataSource#country_codes)
28
+ attr_reader :country_codes
29
+
30
+ # Initializes a new {RubyDataSource} instance.
31
+ #
32
+ # @raise [TZInfoDataNotFound] if the tzinfo-data gem could not be found
33
+ # (i.e. `require 'tzinfo/data'` failed).
34
+ def initialize
35
+ super
36
+
37
+ begin
38
+ require('tzinfo/data')
39
+ rescue LoadError
40
+ raise TZInfoDataNotFound, "The tzinfo-data gem could not be found (require 'tzinfo/data' failed)."
41
+ end
42
+
43
+ if TZInfo::Data.const_defined?(:LOCATION)
44
+ # Format 2
45
+ @base_path = File.join(TZInfo::Data::LOCATION, 'tzinfo', 'data')
46
+ else
47
+ # Format 1
48
+ data_file = File.join('', 'tzinfo', 'data.rb')
49
+ path = $".reverse_each.detect {|p| p.end_with?(data_file) }
50
+ if path
51
+ @base_path = File.join(File.dirname(path), 'data')
52
+ else
53
+ @base_path = 'tzinfo/data'
54
+ end
55
+ end
56
+
57
+ require_index('timezones')
58
+ require_index('countries')
59
+
60
+ @data_timezone_identifiers = Data::Indexes::Timezones.data_timezones
61
+ @linked_timezone_identifiers = Data::Indexes::Timezones.linked_timezones
62
+ @countries = Data::Indexes::Countries.countries
63
+ @country_codes = @countries.keys.sort!.freeze
64
+ end
65
+
66
+ # (see DataSource#to_s)
67
+ def to_s
68
+ "Ruby DataSource: #{version_info}"
69
+ end
70
+
71
+ # (see DataSource#inspect)
72
+ def inspect
73
+ "#<TZInfo::DataSources::RubyDataSource: #{version_info}>"
74
+ end
75
+
76
+ protected
77
+
78
+ # Returns a {TimezoneInfo} instance for the given time zone identifier.
79
+ # The result will either be a {ConstantOffsetDataTimezoneInfo}, a
80
+ # {TransitionsDataTimezoneInfo} or a {LinkedTimezoneInfo} depending on the
81
+ # type of time zone.
82
+ #
83
+ # @param identifier [String] A time zone identifier.
84
+ # @return [TimezoneInfo] a {TimezoneInfo} instance for the given time zone
85
+ # identifier.
86
+ # @raise [InvalidTimezoneIdentifier] if the time zone is not found or the
87
+ # identifier is invalid.
88
+ def load_timezone_info(identifier)
89
+ valid_identifier = validate_timezone_identifier(identifier)
90
+ split_identifier = valid_identifier.gsub(/-/, '__m__').gsub(/\+/, '__p__').split('/')
91
+
92
+ begin
93
+ require_definition(split_identifier)
94
+
95
+ m = Data::Definitions
96
+ split_identifier.each {|part| m = m.const_get(part) }
97
+ m.get
98
+ rescue LoadError, NameError => e
99
+ raise InvalidTimezoneIdentifier, "#{e.message.encode(Encoding::UTF_8)} (loading #{valid_identifier})"
100
+ end
101
+ end
102
+
103
+ # (see DataSource#load_country_info)
104
+ def load_country_info(code)
105
+ lookup_country_info(@countries, code)
106
+ end
107
+
108
+ private
109
+
110
+ # Requires a zone definition by its identifier (split on /).
111
+ #
112
+ # @param identifier [Array<string>] the component parts of a time zone
113
+ # identifier (split on /). This must have already been validated.
114
+ def require_definition(identifier)
115
+ require_data(*(['definitions'] + identifier))
116
+ end
117
+
118
+ # Requires an index by its name.
119
+ #
120
+ # @param name [String] an index name.
121
+ def require_index(name)
122
+ require_data(*['indexes', name])
123
+ end
124
+
125
+ # Requires a file from tzinfo/data.
126
+ #
127
+ # @param file [Array<String>] a relative path to a file to be required.
128
+ def require_data(*file)
129
+ require(File.join(@base_path, *file))
130
+ end
131
+
132
+ # @return [String] a `String` containing TZInfo::Data version infomation
133
+ # for inclusion in the #to_s and #inspect output.
134
+ def version_info
135
+ # The TZInfo::Data::VERSION constant is only available from v1.2014.8
136
+ # onwards.
137
+ "tzdb v#{TZInfo::Data::Version::TZDATA}#{TZInfo::Data.const_defined?(:VERSION) ? ", tzinfo-data v#{TZInfo::Data::VERSION}" : ''}"
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module TZInfo
5
+ module DataSources
6
+ # Represents a time zone defined by a data source.
7
+ #
8
+ # @abstract Data sources return instances of {TimezoneInfo} subclasses.
9
+ class TimezoneInfo
10
+ # @return [String] the identifier of the time zone.
11
+ attr_reader :identifier
12
+
13
+ # Initializes a new TimezoneInfo. The passed in `identifier` instance will
14
+ # be frozen.
15
+ #
16
+ # @param identifier [String] the identifier of the time zone.
17
+ # @raise [ArgumentError] if `identifier` is `nil`.
18
+ def initialize(identifier)
19
+ raise ArgumentError, 'identifier must be specified' unless identifier
20
+ @identifier = identifier.freeze
21
+ end
22
+
23
+ # @return [String] the internal object state as a programmer-readable
24
+ # `String`.
25
+ def inspect
26
+ "#<#{self.class}: #@identifier>"
27
+ end
28
+
29
+ # @return [Timezone] a new {Timezone} instance for the time zone
30
+ # represented by this {TimezoneInfo}.
31
+ def create_timezone
32
+ raise_not_implemented('create_timezone')
33
+ end
34
+
35
+ private
36
+
37
+ # Raises a {NotImplementedError}.
38
+ #
39
+ # @param method_name [String] the name of the method that must be
40
+ # overridden.
41
+ # @raise NotImplementedError always.
42
+ def raise_not_implemented(method_name)
43
+ raise NotImplementedError, "Subclasses must override #{method_name}"
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,214 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module TZInfo
5
+ module DataSources
6
+ # Represents a data time zone defined by a list of transitions that change
7
+ # the locally observed time.
8
+ class TransitionsDataTimezoneInfo < DataTimezoneInfo
9
+ # @return [Array<TimezoneTransition>] the transitions that define this
10
+ # time zone in order of ascending timestamp.
11
+ attr_reader :transitions
12
+
13
+ # Initializes a new {TransitionsDataTimezoneInfo}.
14
+ #
15
+ # The passed in `identifier` instance will be frozen. A reference to the
16
+ # passed in `Array` will be retained.
17
+ #
18
+ # The `transitions` `Array` must be sorted in order of ascending
19
+ # timestamp. Each transition must have a
20
+ # {TimezoneTransition#timestamp_value timestamp_value} that is greater
21
+ # than the {TimezoneTransition#timestamp_value timestamp_value} of the
22
+ # prior transition.
23
+ #
24
+ # @param identifier [String] the identifier of the time zone.
25
+ # @param transitions [Array<TimezoneTransitions>] an `Array` of
26
+ # transitions that each indicate when a change occurs in the locally
27
+ # observed time.
28
+ # @raise [ArgumentError] if `identifier` is `nil`.
29
+ # @raise [ArgumentError] if `transitions` is `nil`.
30
+ # @raise [ArgumentError] if `transitions` is an empty `Array`.
31
+ def initialize(identifier, transitions)
32
+ super(identifier)
33
+ raise ArgumentError, 'transitions must be specified' unless transitions
34
+ raise ArgumentError, 'transitions must not be an empty Array' if transitions.empty?
35
+ @transitions = transitions.freeze
36
+ end
37
+
38
+ # (see DataTimezoneInfo#period_for)
39
+ def period_for(timestamp)
40
+ raise ArgumentError, 'timestamp must be specified' unless timestamp
41
+ raise ArgumentError, 'timestamp must have a specified utc_offset' unless timestamp.utc_offset
42
+
43
+ timestamp_value = timestamp.value
44
+
45
+ index = find_minimum_transition {|t| t.timestamp_value >= timestamp_value }
46
+
47
+ if index
48
+ transition = @transitions[index]
49
+
50
+ if transition.timestamp_value == timestamp_value
51
+ # timestamp occurs within the second of the found transition, so is
52
+ # the transition that starts the period.
53
+ start_transition = transition
54
+ end_transition = @transitions[index + 1]
55
+ else
56
+ # timestamp occurs before the second of the found transition, so is
57
+ # the transition that ends the period.
58
+ start_transition = index == 0 ? nil : @transitions[index - 1]
59
+ end_transition = transition
60
+ end
61
+ else
62
+ start_transition = @transitions.last
63
+ end_transition = nil
64
+ end
65
+
66
+ TransitionsTimezonePeriod.new(start_transition, end_transition)
67
+ end
68
+
69
+ # (see DataTimezoneInfo#periods_for_local)
70
+ def periods_for_local(local_timestamp)
71
+ raise ArgumentError, 'local_timestamp must be specified' unless local_timestamp
72
+ raise ArgumentError, 'local_timestamp must have an unspecified utc_offset' if local_timestamp.utc_offset
73
+
74
+ local_timestamp_value = local_timestamp.value
75
+ latest_possible_utc_value = local_timestamp_value + 86400
76
+ earliest_possible_utc_value = local_timestamp_value - 86400
77
+
78
+ # Find the index of the first transition that occurs after a latest
79
+ # possible UTC representation of the local timestamp and then search
80
+ # backwards until an earliest possible UTC representation.
81
+
82
+ index = find_minimum_transition {|t| t.timestamp_value >= latest_possible_utc_value }
83
+
84
+ # No transitions after latest_possible_utc_value, set to max index + 1
85
+ # to search backwards including the period after the last transition
86
+ index = @transitions.length unless index
87
+
88
+ result = []
89
+
90
+ index.downto(0) do |i|
91
+ start_transition = i > 0 ? @transitions[i - 1] : nil
92
+ end_transition = @transitions[i]
93
+ offset = start_transition ? start_transition.offset : end_transition.previous_offset
94
+ utc_timestamp_value = local_timestamp_value - offset.observed_utc_offset
95
+
96
+ # It is not necessary to compare the sub-seconds because a timestamp
97
+ # is in the period if is >= the start transition (sub-seconds would
98
+ # make == become >) and if it is < the end transition (which
99
+ # sub-seconds cannot affect).
100
+ if (!start_transition || utc_timestamp_value >= start_transition.timestamp_value) && (!end_transition || utc_timestamp_value < end_transition.timestamp_value)
101
+ result << TransitionsTimezonePeriod.new(start_transition, end_transition)
102
+ elsif end_transition && end_transition.timestamp_value < earliest_possible_utc_value
103
+ break
104
+ end
105
+ end
106
+
107
+ result.reverse!
108
+ end
109
+
110
+ # (see DataTimezoneInfo#transitions_up_to)
111
+ def transitions_up_to(to_timestamp, from_timestamp = nil)
112
+ raise ArgumentError, 'to_timestamp must be specified' unless to_timestamp
113
+ raise ArgumentError, 'to_timestamp must have a specified utc_offset' unless to_timestamp.utc_offset
114
+
115
+ if from_timestamp
116
+ raise ArgumentError, 'from_timestamp must have a specified utc_offset' unless from_timestamp.utc_offset
117
+ raise ArgumentError, 'to_timestamp must be greater than from_timestamp' if to_timestamp <= from_timestamp
118
+ end
119
+
120
+ if from_timestamp
121
+ from_index = find_minimum_transition {|t| transition_on_or_after_timestamp?(t, from_timestamp) }
122
+ return [] unless from_index
123
+ else
124
+ from_index = 0
125
+ end
126
+
127
+ to_index = find_minimum_transition {|t| transition_on_or_after_timestamp?(t, to_timestamp) }
128
+
129
+ if to_index
130
+ return [] if to_index < 1
131
+ to_index -= 1
132
+ else
133
+ to_index = -1
134
+ end
135
+
136
+ @transitions[from_index..to_index]
137
+ end
138
+
139
+ private
140
+
141
+ # Array#bsearch_index was added in Ruby 2.3.0. Use bsearch_index to find
142
+ # transitions if it is available, otherwise use a Ruby implementation.
143
+ if [].respond_to?(:bsearch_index)
144
+ # Performs a binary search on {transitions} to find the index of the
145
+ # earliest transition satisfying a condition.
146
+ #
147
+ # @yield [transition] the caller will be yielded to to test the search
148
+ # condition.
149
+ # @yieldparam transition [TimezoneTransition] a {TimezoneTransition}
150
+ # instance from {transitions}.
151
+ # @yieldreturn [Boolean] `true` for the earliest transition that
152
+ # satisfies the condition and return `true` for all subsequent
153
+ # transitions. In all other cases, the result of the block must be
154
+ # `false`.
155
+ # @return [Integer] the index of the earliest transition safisfying
156
+ # the condition or `nil` if there are no such transitions.
157
+ #
158
+ # :nocov_no_array_bsearch_index:
159
+ def find_minimum_transition(&block)
160
+ @transitions.bsearch_index(&block)
161
+ end
162
+ # :nocov_no_array_bsearch_index:
163
+ else
164
+ # Performs a binary search on {transitions} to find the index of the
165
+ # earliest transition satisfying a condition.
166
+ #
167
+ # @yield [transition] the caller will be yielded to to test the search
168
+ # condition.
169
+ # @yieldparam transition [TimezoneTransition] a {TimezoneTransition}
170
+ # instance from {transitions}.
171
+ # @yieldreturn [Boolean] `true` for the earliest transition that
172
+ # satisfies the condition and return `true` for all subsequent
173
+ # transitions. In all other cases, the result of the block must be
174
+ # `false`.
175
+ # @return [Integer] the index of the earliest transition safisfying
176
+ # the condition or `nil` if there are no such transitions.
177
+ #
178
+ # :nocov_array_bsearch_index:
179
+ def find_minimum_transition
180
+ # A Ruby implementation of the find-minimum mode of Array#bsearch_index.
181
+ low = 0
182
+ high = @transitions.length
183
+ satisfied = false
184
+
185
+ while low < high do
186
+ mid = (low + high).div(2)
187
+ if yield @transitions[mid]
188
+ satisfied = true
189
+ high = mid
190
+ else
191
+ low = mid + 1
192
+ end
193
+ end
194
+
195
+ satisfied ? low : nil
196
+ end
197
+ # :nocov_array_bsearch_index:
198
+ end
199
+
200
+ # Determines if a transition occurs at or after a given {Timestamp},
201
+ # taking the {Timestamp#sub_second sub_second} into consideration.
202
+ #
203
+ # @param transition [TimezoneTransition] the transition to compare.
204
+ # @param timestamp [Timestamp] the timestamp to compare.
205
+ # @return [Boolean] `true` if `transition` occurs at or after `timestamp`,
206
+ # otherwise `false`.
207
+ def transition_on_or_after_timestamp?(transition, timestamp)
208
+ transition_timestamp_value = transition.timestamp_value
209
+ timestamp_value = timestamp.value
210
+ transition_timestamp_value > timestamp_value || transition_timestamp_value == timestamp_value && timestamp.sub_second == 0
211
+ end
212
+ end
213
+ end
214
+ end