tzinfo 1.2.9 → 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -3
  3. data.tar.gz.sig +0 -0
  4. data/.yardopts +3 -0
  5. data/CHANGES.md +535 -386
  6. data/LICENSE +12 -12
  7. data/README.md +368 -114
  8. data/lib/tzinfo.rb +70 -40
  9. data/lib/tzinfo/annual_rules.rb +32 -12
  10. data/lib/tzinfo/country.rb +141 -129
  11. data/lib/tzinfo/country_timezone.rb +70 -112
  12. data/lib/tzinfo/data_source.rb +389 -144
  13. data/lib/tzinfo/data_sources.rb +8 -0
  14. data/lib/tzinfo/data_sources/constant_offset_data_timezone_info.rb +56 -0
  15. data/lib/tzinfo/data_sources/country_info.rb +42 -0
  16. data/lib/tzinfo/data_sources/data_timezone_info.rb +91 -0
  17. data/lib/tzinfo/data_sources/linked_timezone_info.rb +33 -0
  18. data/lib/tzinfo/data_sources/posix_time_zone_parser.rb +181 -0
  19. data/lib/tzinfo/data_sources/ruby_data_source.rb +145 -0
  20. data/lib/tzinfo/data_sources/timezone_info.rb +47 -0
  21. data/lib/tzinfo/data_sources/transitions_data_timezone_info.rb +214 -0
  22. data/lib/tzinfo/data_sources/zoneinfo_data_source.rb +580 -0
  23. data/lib/tzinfo/data_sources/zoneinfo_reader.rb +486 -0
  24. data/lib/tzinfo/data_timezone.rb +33 -47
  25. data/lib/tzinfo/datetime_with_offset.rb +153 -0
  26. data/lib/tzinfo/format1.rb +10 -0
  27. data/lib/tzinfo/format1/country_definer.rb +17 -0
  28. data/lib/tzinfo/format1/country_index_definition.rb +64 -0
  29. data/lib/tzinfo/format1/timezone_definer.rb +64 -0
  30. data/lib/tzinfo/format1/timezone_definition.rb +39 -0
  31. data/lib/tzinfo/format1/timezone_index_definition.rb +77 -0
  32. data/lib/tzinfo/format2.rb +10 -0
  33. data/lib/tzinfo/format2/country_definer.rb +68 -0
  34. data/lib/tzinfo/format2/country_index_definer.rb +68 -0
  35. data/lib/tzinfo/format2/country_index_definition.rb +46 -0
  36. data/lib/tzinfo/format2/timezone_definer.rb +94 -0
  37. data/lib/tzinfo/format2/timezone_definition.rb +73 -0
  38. data/lib/tzinfo/format2/timezone_index_definer.rb +45 -0
  39. data/lib/tzinfo/format2/timezone_index_definition.rb +55 -0
  40. data/lib/tzinfo/info_timezone.rb +26 -21
  41. data/lib/tzinfo/linked_timezone.rb +33 -52
  42. data/lib/tzinfo/offset_timezone_period.rb +42 -0
  43. data/lib/tzinfo/string_deduper.rb +118 -0
  44. data/lib/tzinfo/time_with_offset.rb +154 -0
  45. data/lib/tzinfo/timestamp.rb +548 -0
  46. data/lib/tzinfo/timestamp_with_offset.rb +85 -0
  47. data/lib/tzinfo/timezone.rb +989 -502
  48. data/lib/tzinfo/timezone_offset.rb +84 -74
  49. data/lib/tzinfo/timezone_period.rb +151 -217
  50. data/lib/tzinfo/timezone_proxy.rb +70 -79
  51. data/lib/tzinfo/timezone_transition.rb +77 -109
  52. data/lib/tzinfo/transition_rule.rb +207 -77
  53. data/lib/tzinfo/transitions_timezone_period.rb +63 -0
  54. data/lib/tzinfo/untaint_ext.rb +18 -0
  55. data/lib/tzinfo/version.rb +7 -0
  56. data/lib/tzinfo/with_offset.rb +61 -0
  57. metadata +49 -103
  58. metadata.gz.sig +0 -0
  59. data/Rakefile +0 -107
  60. data/lib/tzinfo/country_index_definition.rb +0 -31
  61. data/lib/tzinfo/country_info.rb +0 -42
  62. data/lib/tzinfo/data_timezone_info.rb +0 -55
  63. data/lib/tzinfo/linked_timezone_info.rb +0 -26
  64. data/lib/tzinfo/offset_rationals.rb +0 -77
  65. data/lib/tzinfo/posix_time_zone_parser.rb +0 -136
  66. data/lib/tzinfo/ruby_core_support.rb +0 -169
  67. data/lib/tzinfo/ruby_country_info.rb +0 -74
  68. data/lib/tzinfo/ruby_data_source.rb +0 -140
  69. data/lib/tzinfo/time_or_datetime.rb +0 -351
  70. data/lib/tzinfo/timezone_definition.rb +0 -36
  71. data/lib/tzinfo/timezone_index_definition.rb +0 -54
  72. data/lib/tzinfo/timezone_info.rb +0 -30
  73. data/lib/tzinfo/timezone_transition_definition.rb +0 -104
  74. data/lib/tzinfo/transition_data_timezone_info.rb +0 -274
  75. data/lib/tzinfo/zoneinfo_country_info.rb +0 -37
  76. data/lib/tzinfo/zoneinfo_data_source.rb +0 -497
  77. data/lib/tzinfo/zoneinfo_timezone_info.rb +0 -520
  78. data/test/tc_annual_rules.rb +0 -95
  79. data/test/tc_country.rb +0 -238
  80. data/test/tc_country_index_definition.rb +0 -69
  81. data/test/tc_country_info.rb +0 -16
  82. data/test/tc_country_timezone.rb +0 -173
  83. data/test/tc_data_source.rb +0 -218
  84. data/test/tc_data_timezone.rb +0 -99
  85. data/test/tc_data_timezone_info.rb +0 -18
  86. data/test/tc_info_timezone.rb +0 -34
  87. data/test/tc_linked_timezone.rb +0 -155
  88. data/test/tc_linked_timezone_info.rb +0 -23
  89. data/test/tc_offset_rationals.rb +0 -23
  90. data/test/tc_posix_time_zone_parser.rb +0 -261
  91. data/test/tc_ruby_core_support.rb +0 -168
  92. data/test/tc_ruby_country_info.rb +0 -110
  93. data/test/tc_ruby_data_source.rb +0 -167
  94. data/test/tc_time_or_datetime.rb +0 -674
  95. data/test/tc_timezone.rb +0 -1361
  96. data/test/tc_timezone_definition.rb +0 -113
  97. data/test/tc_timezone_index_definition.rb +0 -73
  98. data/test/tc_timezone_info.rb +0 -11
  99. data/test/tc_timezone_london.rb +0 -143
  100. data/test/tc_timezone_melbourne.rb +0 -142
  101. data/test/tc_timezone_new_york.rb +0 -142
  102. data/test/tc_timezone_offset.rb +0 -126
  103. data/test/tc_timezone_period.rb +0 -555
  104. data/test/tc_timezone_proxy.rb +0 -136
  105. data/test/tc_timezone_transition.rb +0 -366
  106. data/test/tc_timezone_transition_definition.rb +0 -295
  107. data/test/tc_timezone_utc.rb +0 -27
  108. data/test/tc_transition_data_timezone_info.rb +0 -433
  109. data/test/tc_transition_rule.rb +0 -663
  110. data/test/tc_zoneinfo_country_info.rb +0 -78
  111. data/test/tc_zoneinfo_data_source.rb +0 -1204
  112. data/test/tc_zoneinfo_timezone_info.rb +0 -2153
  113. data/test/test_utils.rb +0 -192
  114. data/test/ts_all.rb +0 -7
  115. data/test/ts_all_ruby.rb +0 -5
  116. data/test/ts_all_zoneinfo.rb +0 -9
  117. data/test/tzinfo-data/tzinfo/data.rb +0 -8
  118. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +0 -89
  119. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +0 -327
  120. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +0 -230
  121. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +0 -19
  122. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +0 -21
  123. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +0 -21
  124. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +0 -21
  125. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +0 -273
  126. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +0 -198
  127. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +0 -333
  128. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +0 -277
  129. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +0 -235
  130. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +0 -16
  131. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +0 -940
  132. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +0 -609
  133. data/test/tzinfo-data/tzinfo/data/version.rb +0 -20
  134. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  135. data/test/zoneinfo/America/New_York +0 -0
  136. data/test/zoneinfo/Australia/Melbourne +0 -0
  137. data/test/zoneinfo/EST +0 -0
  138. data/test/zoneinfo/Etc/UTC +0 -0
  139. data/test/zoneinfo/Europe/Amsterdam +0 -0
  140. data/test/zoneinfo/Europe/Andorra +0 -0
  141. data/test/zoneinfo/Europe/London +0 -0
  142. data/test/zoneinfo/Europe/Paris +0 -0
  143. data/test/zoneinfo/Europe/Prague +0 -0
  144. data/test/zoneinfo/Factory +0 -0
  145. data/test/zoneinfo/iso3166.tab +0 -274
  146. data/test/zoneinfo/leapseconds +0 -78
  147. data/test/zoneinfo/posix/Europe/London +0 -0
  148. data/test/zoneinfo/posixrules +0 -0
  149. data/test/zoneinfo/right/Europe/London +0 -0
  150. data/test/zoneinfo/zone.tab +0 -452
  151. data/test/zoneinfo/zone1970.tab +0 -384
  152. data/tzinfo.gemspec +0 -21
@@ -0,0 +1,8 @@
1
+ # encoding: UTF-8
2
+
3
+ module TZInfo
4
+ # {DataSource} implementations and classes used by {DataSource}
5
+ # implementations.
6
+ module DataSources
7
+ end
8
+ end
@@ -0,0 +1,56 @@
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 constantly observed offset.
7
+ class ConstantOffsetDataTimezoneInfo < DataTimezoneInfo
8
+ # @return [TimezoneOffset] the offset that is constantly observed.
9
+ attr_reader :constant_offset
10
+
11
+ # Initializes a new {ConstantOffsetDataTimezoneInfo}.
12
+ #
13
+ # The passed in `identifier` instance will be frozen. A reference to the
14
+ # passed in {TimezoneOffset} will be retained.
15
+ #
16
+ # @param identifier [String] the identifier of the time zone.
17
+ # @param constant_offset [TimezoneOffset] the constantly observed offset.
18
+ # @raise [ArgumentError] if `identifier` or `constant_offset` is `nil`.
19
+ def initialize(identifier, constant_offset)
20
+ super(identifier)
21
+ raise ArgumentError, 'constant_offset must be specified' unless constant_offset
22
+ @constant_offset = constant_offset
23
+ end
24
+
25
+ # @param timestamp [Timestamp] ignored.
26
+ # @return [TimezonePeriod] an unbounded {TimezonePeriod} for the time
27
+ # zone's constantly observed offset.
28
+ def period_for(timestamp)
29
+ constant_period
30
+ end
31
+
32
+ # @param local_timestamp [Timestamp] ignored.
33
+ # @return [Array<TimezonePeriod>] an `Array` containing a single unbounded
34
+ # {TimezonePeriod} for the time zone's constantly observed offset.
35
+ def periods_for_local(local_timestamp)
36
+ [constant_period]
37
+ end
38
+
39
+ # @param to_timestamp [Timestamp] ignored.
40
+ # @param from_timestamp [Timestamp] ignored.
41
+ # @return [Array] an empty `Array`, since there are no transitions in time
42
+ # zones that observe a constant offset.
43
+ def transitions_up_to(to_timestamp, from_timestamp = nil)
44
+ []
45
+ end
46
+
47
+ private
48
+
49
+ # @return [TimezonePeriod] an unbounded {TimezonePeriod} with the constant
50
+ # offset of this timezone.
51
+ def constant_period
52
+ OffsetTimezonePeriod.new(@constant_offset)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -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,181 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'strscan'
5
+
6
+ module TZInfo
7
+ # Use send as a workaround for erroneous 'wrong number of arguments' errors
8
+ # with JRuby 9.0.5.0 when calling methods with Java implementations. See #114.
9
+ send(:using, UntaintExt) if TZInfo.const_defined?(:UntaintExt)
10
+
11
+ module DataSources
12
+ # An {InvalidPosixTimeZone} exception is raised if an invalid POSIX-style
13
+ # time zone string is encountered.
14
+ #
15
+ # @private
16
+ class InvalidPosixTimeZone < StandardError #:nodoc:
17
+ end
18
+ private_constant :InvalidPosixTimeZone
19
+
20
+ # A parser for POSIX-style TZ strings used in zoneinfo files and specified
21
+ # by tzfile.5 and tzset.3.
22
+ #
23
+ # @private
24
+ class PosixTimeZoneParser #:nodoc:
25
+ # Initializes a new {PosixTimeZoneParser}.
26
+ #
27
+ # @param string_deduper [StringDeduper] a {StringDeduper} instance to use
28
+ # to dedupe abbreviations.
29
+ def initialize(string_deduper)
30
+ @string_deduper = string_deduper
31
+ end
32
+
33
+ # Parses a POSIX-style TZ string.
34
+ #
35
+ # @param tz_string [String] the string to parse.
36
+ # @return [Object] either a {TimezoneOffset} for a constantly applied
37
+ # offset or an {AnnualRules} instance representing the rules.
38
+ # @raise [InvalidPosixTimeZone] if `tz_string` is not a `String`.
39
+ # @raise [InvalidPosixTimeZone] if `tz_string` is is not valid.
40
+ def parse(tz_string)
41
+ raise InvalidPosixTimeZone unless tz_string.kind_of?(String)
42
+ return nil if tz_string.empty?
43
+
44
+ s = StringScanner.new(tz_string)
45
+ check_scan(s, /([^-+,\d<][^-+,\d]*) | <([^>]+)>/x)
46
+ std_abbrev = @string_deduper.dedupe((s[1] || s[2]).untaint)
47
+ check_scan(s, /([-+]?\d+)(?::(\d+)(?::(\d+))?)?/)
48
+ std_offset = get_offset_from_hms(s[1], s[2], s[3])
49
+
50
+ if s.scan(/([^-+,\d<][^-+,\d]*) | <([^>]+)>/x)
51
+ dst_abbrev = @string_deduper.dedupe((s[1] || s[2]).untaint)
52
+
53
+ if s.scan(/([-+]?\d+)(?::(\d+)(?::(\d+))?)?/)
54
+ dst_offset = get_offset_from_hms(s[1], s[2], s[3])
55
+ else
56
+ # POSIX is negative for ahead of UTC.
57
+ dst_offset = std_offset - 3600
58
+ end
59
+
60
+ dst_difference = std_offset - dst_offset
61
+
62
+ start_rule = parse_rule(s, 'start')
63
+ end_rule = parse_rule(s, 'end')
64
+
65
+ raise InvalidPosixTimeZone, "Expected the end of a POSIX-style time zone string but found '#{s.rest}'." if s.rest?
66
+
67
+ if start_rule.is_always_first_day_of_year? && start_rule.transition_at == 0 &&
68
+ end_rule.is_always_last_day_of_year? && end_rule.transition_at == 86400 + dst_difference
69
+ # Constant daylight savings time.
70
+ # POSIX is negative for ahead of UTC.
71
+ TimezoneOffset.new(-std_offset, dst_difference, dst_abbrev)
72
+ else
73
+ AnnualRules.new(
74
+ TimezoneOffset.new(-std_offset, 0, std_abbrev),
75
+ TimezoneOffset.new(-std_offset, dst_difference, dst_abbrev),
76
+ start_rule,
77
+ end_rule)
78
+ end
79
+ elsif !s.rest?
80
+ # Constant standard time.
81
+ # POSIX is negative for ahead of UTC.
82
+ TimezoneOffset.new(-std_offset, 0, std_abbrev)
83
+ else
84
+ raise InvalidPosixTimeZone, "Expected the end of a POSIX-style time zone string but found '#{s.rest}'."
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ # Parses a rule.
91
+ #
92
+ # @param s [StringScanner] the `StringScanner` to read the rule from.
93
+ # @param type [String] the type of rule (either `'start'` or `'end'`).
94
+ # @raise [InvalidPosixTimeZone] if the rule is not valid.
95
+ # @return [TransitionRule] the parsed rule.
96
+ def parse_rule(s, type)
97
+ check_scan(s, /,(?: (?: J(\d+) ) | (\d+) | (?: M(\d+)\.(\d)\.(\d) ) )/x)
98
+ julian_day_of_year = s[1]
99
+ absolute_day_of_year = s[2]
100
+ month = s[3]
101
+ week = s[4]
102
+ day_of_week = s[5]
103
+
104
+ if s.scan(/\//)
105
+ check_scan(s, /([-+]?\d+)(?::(\d+)(?::(\d+))?)?/)
106
+ transition_at = get_seconds_after_midnight_from_hms(s[1], s[2], s[3])
107
+ else
108
+ transition_at = 7200
109
+ end
110
+
111
+ begin
112
+ if julian_day_of_year
113
+ JulianDayOfYearTransitionRule.new(julian_day_of_year.to_i, transition_at)
114
+ elsif absolute_day_of_year
115
+ AbsoluteDayOfYearTransitionRule.new(absolute_day_of_year.to_i, transition_at)
116
+ elsif week == '5'
117
+ LastDayOfMonthTransitionRule.new(month.to_i, day_of_week.to_i, transition_at)
118
+ else
119
+ DayOfMonthTransitionRule.new(month.to_i, week.to_i, day_of_week.to_i, transition_at)
120
+ end
121
+ rescue ArgumentError => e
122
+ raise InvalidPosixTimeZone, "Invalid #{type} rule in POSIX-style time zone string: #{e}"
123
+ end
124
+ end
125
+
126
+ # Returns an offset in seconds from hh:mm:ss values. The value can be
127
+ # negative. -02:33:12 would represent 2 hours, 33 minutes and 12 seconds
128
+ # ahead of UTC.
129
+ #
130
+ # @param h [String] the hours.
131
+ # @param m [String] the minutes.
132
+ # @param s [String] the seconds.
133
+ # @return [Integer] the offset.
134
+ # @raise [InvalidPosixTimeZone] if the mm and ss values are greater than
135
+ # 59.
136
+ def get_offset_from_hms(h, m, s)
137
+ h = h.to_i
138
+ m = m.to_i
139
+ s = s.to_i
140
+ raise InvalidPosixTimeZone, "Invalid minute #{m} in offset for POSIX-style time zone string." if m > 59
141
+ raise InvalidPosixTimeZone, "Invalid second #{s} in offset for POSIX-style time zone string." if s > 59
142
+ magnitude = (h.abs * 60 + m) * 60 + s
143
+ h < 0 ? -magnitude : magnitude
144
+ end
145
+
146
+ # Returns the seconds from midnight from hh:mm:ss values. Hours can exceed
147
+ # 24 for a time on the following day. Hours can be negative to subtract
148
+ # hours from midnight on the given day. -02:33:12 represents 22:33:12 on
149
+ # the prior day.
150
+ #
151
+ # @param h [String] the hour.
152
+ # @param m [String] the minutes past the hour.
153
+ # @param s [String] the seconds past the minute.
154
+ # @return [Integer] the number of seconds after midnight.
155
+ # @raise [InvalidPosixTimeZone] if the mm and ss values are greater than
156
+ # 59.
157
+ def get_seconds_after_midnight_from_hms(h, m, s)
158
+ h = h.to_i
159
+ m = m.to_i
160
+ s = s.to_i
161
+ raise InvalidPosixTimeZone, "Invalid minute #{m} in time for POSIX-style time zone string." if m > 59
162
+ raise InvalidPosixTimeZone, "Invalid second #{s} in time for POSIX-style time zone string." if s > 59
163
+ (h * 3600) + m * 60 + s
164
+ end
165
+
166
+ # Scans for a pattern and raises an exception if the pattern does not
167
+ # match the input.
168
+ #
169
+ # @param s [StringScanner] the `StringScanner` to scan.
170
+ # @param pattern [Regexp] the pattern to match.
171
+ # @return [String] the result of the scan.
172
+ # @raise [InvalidPosixTimeZone] if the pattern does not match the input.
173
+ def check_scan(s, pattern)
174
+ result = s.scan(pattern)
175
+ raise InvalidPosixTimeZone, "Expected '#{s.rest}' to match #{pattern} in POSIX-style time zone string." unless result
176
+ result
177
+ end
178
+ end
179
+ private_constant :PosixTimeZoneParser
180
+ end
181
+ end
@@ -0,0 +1,145 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module TZInfo
5
+ # Use send as a workaround for erroneous 'wrong number of arguments' errors
6
+ # with JRuby 9.0.5.0 when calling methods with Java implementations. See #114.
7
+ send(:using, UntaintExt) if TZInfo.const_defined?(:UntaintExt)
8
+
9
+ module DataSources
10
+ # A {TZInfoDataNotFound} exception is raised if the tzinfo-data gem could
11
+ # not be found (i.e. `require 'tzinfo/data'` failed) when selecting the Ruby
12
+ # data source.
13
+ class TZInfoDataNotFound < StandardError
14
+ end
15
+
16
+ # A DataSource implementation that loads data from the set of Ruby modules
17
+ # included in the tzinfo-data gem.
18
+ #
19
+ # TZInfo will use {RubyDataSource} by default if the tzinfo-data gem
20
+ # is available on the load path. It can also be selected by calling
21
+ # {DataSource.set} as follows:
22
+ #
23
+ # TZInfo::DataSource.set(:ruby)
24
+ class RubyDataSource < DataSource
25
+ # (see DataSource#data_timezone_identifiers)
26
+ attr_reader :data_timezone_identifiers
27
+
28
+ # (see DataSource#linked_timezone_identifiers)
29
+ attr_reader :linked_timezone_identifiers
30
+
31
+ # (see DataSource#country_codes)
32
+ attr_reader :country_codes
33
+
34
+ # Initializes a new {RubyDataSource} instance.
35
+ #
36
+ # @raise [TZInfoDataNotFound] if the tzinfo-data gem could not be found
37
+ # (i.e. `require 'tzinfo/data'` failed).
38
+ def initialize
39
+ super
40
+
41
+ begin
42
+ require('tzinfo/data')
43
+ rescue LoadError
44
+ raise TZInfoDataNotFound, "The tzinfo-data gem could not be found (require 'tzinfo/data' failed)."
45
+ end
46
+
47
+ if TZInfo::Data.const_defined?(:LOCATION)
48
+ # Format 2
49
+ @base_path = File.join(TZInfo::Data::LOCATION, 'tzinfo', 'data')
50
+ else
51
+ # Format 1
52
+ data_file = File.join('', 'tzinfo', 'data.rb')
53
+ path = $".reverse_each.detect {|p| p.end_with?(data_file) }
54
+ if path
55
+ @base_path = File.join(File.dirname(path), 'data').untaint
56
+ else
57
+ @base_path = 'tzinfo/data'
58
+ end
59
+ end
60
+
61
+ require_index('timezones')
62
+ require_index('countries')
63
+
64
+ @data_timezone_identifiers = Data::Indexes::Timezones.data_timezones
65
+ @linked_timezone_identifiers = Data::Indexes::Timezones.linked_timezones
66
+ @countries = Data::Indexes::Countries.countries
67
+ @country_codes = @countries.keys.sort!.freeze
68
+ end
69
+
70
+ # (see DataSource#to_s)
71
+ def to_s
72
+ "Ruby DataSource: #{version_info}"
73
+ end
74
+
75
+ # (see DataSource#inspect)
76
+ def inspect
77
+ "#<TZInfo::DataSources::RubyDataSource: #{version_info}>"
78
+ end
79
+
80
+ protected
81
+
82
+ # Returns a {TimezoneInfo} instance for the given time zone identifier.
83
+ # The result will either be a {ConstantOffsetDataTimezoneInfo}, a
84
+ # {TransitionsDataTimezoneInfo} or a {LinkedTimezoneInfo} depending on the
85
+ # type of time zone.
86
+ #
87
+ # @param identifier [String] A time zone identifier.
88
+ # @return [TimezoneInfo] a {TimezoneInfo} instance for the given time zone
89
+ # identifier.
90
+ # @raise [InvalidTimezoneIdentifier] if the time zone is not found or the
91
+ # identifier is invalid.
92
+ def load_timezone_info(identifier)
93
+ valid_identifier = validate_timezone_identifier(identifier)
94
+ split_identifier = valid_identifier.gsub(/-/, '__m__').gsub(/\+/, '__p__').split('/')
95
+
96
+ begin
97
+ require_definition(split_identifier)
98
+
99
+ m = Data::Definitions
100
+ split_identifier.each {|part| m = m.const_get(part) }
101
+ m.get
102
+ rescue LoadError, NameError => e
103
+ raise InvalidTimezoneIdentifier, "#{e.message.encode(Encoding::UTF_8)} (loading #{valid_identifier})"
104
+ end
105
+ end
106
+
107
+ # (see DataSource#load_country_info)
108
+ def load_country_info(code)
109
+ lookup_country_info(@countries, code)
110
+ end
111
+
112
+ private
113
+
114
+ # Requires a zone definition by its identifier (split on /).
115
+ #
116
+ # @param identifier [Array<string>] the component parts of a time zone
117
+ # identifier (split on /). This must have already been validated.
118
+ def require_definition(identifier)
119
+ require_data(*(['definitions'] + identifier))
120
+ end
121
+
122
+ # Requires an index by its name.
123
+ #
124
+ # @param name [String] an index name.
125
+ def require_index(name)
126
+ require_data(*['indexes', name])
127
+ end
128
+
129
+ # Requires a file from tzinfo/data.
130
+ #
131
+ # @param file [Array<String>] a relative path to a file to be required.
132
+ def require_data(*file)
133
+ require(File.join(@base_path, *file))
134
+ end
135
+
136
+ # @return [String] a `String` containing TZInfo::Data version infomation
137
+ # for inclusion in the #to_s and #inspect output.
138
+ def version_info
139
+ # The TZInfo::Data::VERSION constant is only available from v1.2014.8
140
+ # onwards.
141
+ "tzdb v#{TZInfo::Data::Version::TZDATA}#{TZInfo::Data.const_defined?(:VERSION) ? ", tzinfo-data v#{TZInfo::Data::VERSION}" : ''}"
142
+ end
143
+ end
144
+ end
145
+ end