tzinfo 1.0.1 → 1.1.0

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 (56) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.yardopts +6 -0
  5. data/{CHANGES → CHANGES.md} +121 -50
  6. data/{README → README.md} +48 -34
  7. data/Rakefile +45 -22
  8. data/lib/tzinfo.rb +7 -2
  9. data/lib/tzinfo/country.rb +23 -1
  10. data/lib/tzinfo/country_index_definition.rb +6 -1
  11. data/lib/tzinfo/country_timezone.rb +9 -1
  12. data/lib/tzinfo/data_source.rb +26 -5
  13. data/lib/tzinfo/data_timezone.rb +30 -2
  14. data/lib/tzinfo/data_timezone_info.rb +26 -0
  15. data/lib/tzinfo/info_timezone.rb +3 -1
  16. data/lib/tzinfo/linked_timezone.rb +30 -1
  17. data/lib/tzinfo/offset_rationals.rb +5 -3
  18. data/lib/tzinfo/ruby_core_support.rb +2 -0
  19. data/lib/tzinfo/ruby_country_info.rb +14 -0
  20. data/lib/tzinfo/ruby_data_source.rb +5 -0
  21. data/lib/tzinfo/time_or_datetime.rb +16 -1
  22. data/lib/tzinfo/timezone.rb +107 -5
  23. data/lib/tzinfo/timezone_definition.rb +5 -1
  24. data/lib/tzinfo/timezone_index_definition.rb +7 -1
  25. data/lib/tzinfo/{timezone_offset_info.rb → timezone_offset.rb} +12 -10
  26. data/lib/tzinfo/timezone_period.rb +19 -15
  27. data/lib/tzinfo/timezone_proxy.rb +5 -1
  28. data/lib/tzinfo/timezone_transition.rb +136 -0
  29. data/lib/tzinfo/{timezone_transition_info.rb → timezone_transition_definition.rb} +21 -57
  30. data/lib/tzinfo/transition_data_timezone_info.rb +81 -8
  31. data/lib/tzinfo/zoneinfo_country_info.rb +7 -0
  32. data/lib/tzinfo/zoneinfo_data_source.rb +104 -57
  33. data/lib/tzinfo/zoneinfo_timezone_info.rb +18 -10
  34. data/test/tc_country.rb +39 -1
  35. data/test/tc_data_timezone.rb +28 -5
  36. data/test/tc_linked_timezone.rb +24 -3
  37. data/test/tc_ruby_data_source.rb +22 -2
  38. data/test/tc_time_or_datetime.rb +3 -3
  39. data/test/tc_timezone.rb +364 -117
  40. data/test/tc_timezone_london.rb +25 -0
  41. data/test/tc_timezone_melbourne.rb +24 -0
  42. data/test/tc_timezone_new_york.rb +24 -0
  43. data/test/{tc_timezone_offset_info.rb → tc_timezone_offset.rb} +27 -27
  44. data/test/tc_timezone_period.rb +113 -90
  45. data/test/tc_timezone_transition.rb +374 -0
  46. data/test/tc_timezone_transition_definition.rb +306 -0
  47. data/test/tc_transition_data_timezone_info.rb +143 -43
  48. data/test/tc_zoneinfo_data_source.rb +69 -5
  49. data/test/tc_zoneinfo_timezone_info.rb +63 -20
  50. data/test/test_utils.rb +27 -7
  51. data/tzinfo.gemspec +21 -0
  52. metadata +61 -38
  53. metadata.gz.sig +3 -0
  54. data/test/tc_timezone_transition_info.rb +0 -471
  55. data/test/zoneinfo/UTC +0 -0
  56. data/test/zoneinfo/localtime +0 -0
@@ -36,6 +36,32 @@ module TZInfo
36
36
  raise NotImplementedError, 'Subclasses must override periods_for_local'
37
37
  end
38
38
 
39
+ # Returns an Array of TimezoneTransition instances representing the times
40
+ # where the UTC offset of the timezone changes.
41
+ #
42
+ # Transitions are returned up to a given date and time up to a given date
43
+ # and time, specified in UTC (utc_to).
44
+ #
45
+ # A from date and time may also be supplied using the utc_from parameter
46
+ # (also specified in UTC). If utc_from is not nil, only transitions from
47
+ # that date and time onwards will be returned.
48
+ #
49
+ # Comparisons with utc_to are exclusive. Comparisons with utc_from are
50
+ # inclusive. If a transition falls precisely on utc_to, it will be excluded.
51
+ # If a transition falls on utc_from, it will be included.
52
+ #
53
+ # Transitions returned are ordered by when they occur, from earliest to
54
+ # latest.
55
+ #
56
+ # utc_to and utc_from can be specified using either DateTime, Time or
57
+ # integer timestamps (Time.to_i).
58
+ #
59
+ # If utc_from is specified and utc_to is not greater than utc_from, then
60
+ # transitions_up_to raises an ArgumentError exception.
61
+ def transitions_up_to(utc_to, utc_from = nil)
62
+ raise NotImplementedError, 'Subclasses must override transitions_up_to'
63
+ end
64
+
39
65
  # Constructs a Timezone instance for the timezone represented by this
40
66
  # DataTimezoneInfo.
41
67
  def create_timezone
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006-2010 Philip Ross
2
+ # Copyright (c) 2006-2013 Philip Ross
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -23,6 +23,8 @@
23
23
  module TZInfo
24
24
 
25
25
  # A Timezone based on a TimezoneInfo.
26
+ #
27
+ # @private
26
28
  class InfoTimezone < Timezone #:nodoc:
27
29
 
28
30
  # Constructs a new InfoTimezone with a TimezoneInfo instance.
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006-2010 Philip Ross
2
+ # Copyright (c) 2006-2013 Philip Ross
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -22,6 +22,9 @@
22
22
 
23
23
  module TZInfo
24
24
 
25
+ # A Timezone based on a LinkedTimezoneInfo.
26
+ #
27
+ # @private
25
28
  class LinkedTimezone < InfoTimezone #:nodoc:
26
29
  # Returns the TimezonePeriod for the given UTC time. utc can either be
27
30
  # a DateTime, Time or integer timestamp (Time.to_i). Any timezone
@@ -40,6 +43,32 @@ module TZInfo
40
43
  @linked_timezone.periods_for_local(local)
41
44
  end
42
45
 
46
+ # Returns an Array of TimezoneTransition instances representing the times
47
+ # where the UTC offset of the timezone changes.
48
+ #
49
+ # Transitions are returned up to a given date and time up to a given date
50
+ # and time, specified in UTC (utc_to).
51
+ #
52
+ # A from date and time may also be supplied using the utc_from parameter
53
+ # (also specified in UTC). If utc_from is not nil, only transitions from
54
+ # that date and time onwards will be returned.
55
+ #
56
+ # Comparisons with utc_to are exclusive. Comparisons with utc_from are
57
+ # inclusive. If a transition falls precisely on utc_to, it will be excluded.
58
+ # If a transition falls on utc_from, it will be included.
59
+ #
60
+ # Transitions returned are ordered by when they occur, from earliest to
61
+ # latest.
62
+ #
63
+ # utc_to and utc_from can be specified using either DateTime, Time or
64
+ # integer timestamps (Time.to_i).
65
+ #
66
+ # If utc_from is specified and utc_to is not greater than utc_from, then
67
+ # transitions_up_to raises an ArgumentError exception.
68
+ def transitions_up_to(utc_to, utc_from = nil)
69
+ @linked_timezone.transitions_up_to(utc_to, utc_from)
70
+ end
71
+
43
72
  protected
44
73
  def setup(info)
45
74
  super(info)
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006-2010 Philip Ross
2
+ # Copyright (c) 2006-2013 Philip Ross
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -26,7 +26,9 @@ module TZInfo
26
26
 
27
27
  # Provides a method for getting Rationals for a timezone offset in seconds.
28
28
  # Pre-reduced rationals are returned for all the half-hour intervals between
29
- # -14 and +14 hours to avoid having to call gcd at runtime.
29
+ # -14 and +14 hours to avoid having to call gcd at runtime.
30
+ #
31
+ # @private
30
32
  module OffsetRationals #:nodoc:
31
33
  @@rational_cache = {
32
34
  -50400 => RubyCoreSupport.rational_new!(-7,12),
@@ -85,7 +87,7 @@ module TZInfo
85
87
  45000 => RubyCoreSupport.rational_new!(25,48),
86
88
  46800 => RubyCoreSupport.rational_new!(13,24),
87
89
  48600 => RubyCoreSupport.rational_new!(9,16),
88
- 50400 => RubyCoreSupport.rational_new!(7,12)}
90
+ 50400 => RubyCoreSupport.rational_new!(7,12)}.freeze
89
91
 
90
92
  # Returns a Rational expressing the fraction of a day that offset in
91
93
  # seconds represents (i.e. equivalent to Rational(offset, 86400)).
@@ -26,6 +26,8 @@ require 'rational' unless defined?(Rational)
26
26
  module TZInfo
27
27
 
28
28
  # Methods to support different versions of Ruby.
29
+ #
30
+ # @private
29
31
  module RubyCoreSupport #:nodoc:
30
32
 
31
33
  # Use Rational.new! for performance reasons in Ruby 1.8.
@@ -22,6 +22,8 @@
22
22
 
23
23
  module TZInfo
24
24
  # Represents information about a country returned by RubyDataSource.
25
+ #
26
+ # @private
25
27
  class RubyCountryInfo < CountryInfo #:nodoc:
26
28
  # Constructs a new CountryInfo with an ISO 3166 country code, name and
27
29
  # block. The block will be evaluated to obtain the timezones for the
@@ -36,6 +38,11 @@ module TZInfo
36
38
  # Returns a frozen array of all the zone identifiers for the country. These
37
39
  # are in the order they were added using the timezone method.
38
40
  def zone_identifiers
41
+ # Thread-safey: It is possible that the value of @zone_identifiers may be
42
+ # calculated multiple times in concurrently executing threads. It is not
43
+ # worth the overhead of locking to ensure that @zone_identifiers is only
44
+ # calculated once.
45
+
39
46
  unless @zone_identifiers
40
47
  @zone_identifiers = zones.collect {|zone| zone.identifier}.freeze
41
48
  end
@@ -47,6 +54,11 @@ module TZInfo
47
54
  # CountryTimezone instances. These are in the order they were added using
48
55
  # the timezone method.
49
56
  def zones
57
+ # Thread-safey: It is possible that the value of @zones may be
58
+ # calculated multiple times in concurrently executing threads. It is not
59
+ # worth the overhead of locking to ensure that @zones is only
60
+ # calculated once.
61
+
50
62
  unless @zones
51
63
  zones = Zones.new
52
64
  @block.call(zones) if @block
@@ -59,6 +71,8 @@ module TZInfo
59
71
 
60
72
  # An instance of the Zones class is passed to the block used to define
61
73
  # timezones.
74
+ #
75
+ # @private
62
76
  class Zones #:nodoc:
63
77
  attr_reader :list
64
78
 
@@ -44,7 +44,12 @@ module TZInfo
44
44
  raise InvalidTimezoneIdentifier, 'Invalid identifier' if identifier !~ /^[A-Za-z0-9\+\-_]+(\/[A-Za-z0-9\+\-_]+)*$/
45
45
 
46
46
  identifier = identifier.gsub(/-/, '__m__').gsub(/\+/, '__p__')
47
+
48
+ # Untaint identifier after it has been reassigned to a new string. We
49
+ # don't want to modify the original identifier. identifier may also be
50
+ # frozen and therefore cannot be untainted.
47
51
  identifier.untaint
52
+
48
53
  identifier = identifier.split('/')
49
54
  begin
50
55
  require_definition(identifier)
@@ -27,7 +27,7 @@ require 'time'
27
27
  module TZInfo
28
28
  # Used by TZInfo internally to represent either a Time, DateTime or
29
29
  # an Integer timestamp (seconds since 1970-01-01 00:00:00).
30
- class TimeOrDateTime #:nodoc:
30
+ class TimeOrDateTime
31
31
  include Comparable
32
32
 
33
33
  # Constructs a new TimeOrDateTime. timeOrDateTime can be a Time, DateTime
@@ -70,6 +70,11 @@ module TZInfo
70
70
  # When converting from a DateTime, the result is truncated to microsecond
71
71
  # precision.
72
72
  def to_time
73
+ # Thread-safey: It is possible that the value of @time may be
74
+ # calculated multiple times in concurrently executing threads. It is not
75
+ # worth the overhead of locking to ensure that @time is only
76
+ # calculated once.
77
+
73
78
  unless @time
74
79
  if @timestamp
75
80
  @time = Time.at(@timestamp).utc
@@ -86,6 +91,11 @@ module TZInfo
86
91
  # When converting from a Time, the result is truncated to microsecond
87
92
  # precision.
88
93
  def to_datetime
94
+ # Thread-safey: It is possible that the value of @datetime may be
95
+ # calculated multiple times in concurrently executing threads. It is not
96
+ # worth the overhead of locking to ensure that @datetime is only
97
+ # calculated once.
98
+
89
99
  unless @datetime
90
100
  # Avoid using Rational unless necessary.
91
101
  u = usec
@@ -98,6 +108,11 @@ module TZInfo
98
108
 
99
109
  # Returns the time as an integer timestamp.
100
110
  def to_i
111
+ # Thread-safey: It is possible that the value of @timestamp may be
112
+ # calculated multiple times in concurrently executing threads. It is not
113
+ # worth the overhead of locking to ensure that @timestamp is only
114
+ # calculated once.
115
+
101
116
  unless @timestamp
102
117
  @timestamp = to_time.to_i
103
118
  end
@@ -21,6 +21,8 @@
21
21
  #++
22
22
 
23
23
  require 'date'
24
+ require 'set'
25
+ require 'thread_safe'
24
26
 
25
27
  module TZInfo
26
28
  # AmbiguousTime is raised to indicates that a specified time in a local
@@ -47,8 +49,8 @@ module TZInfo
47
49
  class UnknownTimezone < StandardError
48
50
  end
49
51
 
50
- # Timezone is the base class of all timezones. It provides a factory method
51
- # get to access timezones by identifier. Once a specific Timezone has been
52
+ # Timezone is the base class of all timezones. It provides a factory method,
53
+ # 'get', to access timezones by identifier. Once a specific Timezone has been
52
54
  # retrieved, DateTimes, Times and timestamps can be converted between the UTC
53
55
  # and the local time for the zone. For example:
54
56
  #
@@ -59,15 +61,23 @@ module TZInfo
59
61
  #
60
62
  # Each time conversion method returns an object of the same type it was
61
63
  # passed.
64
+ #
65
+ # The Timezone class is thread-safe. It is safe to use class and instance
66
+ # methods of Timezone in concurrently executing threads. Instances of Timezone
67
+ # can be shared across thread boundaries.
62
68
  class Timezone
63
69
  include Comparable
64
70
 
65
71
  # Cache of loaded zones by identifier to avoid using require if a zone
66
72
  # has already been loaded.
67
- @@loaded_zones = {}
73
+ #
74
+ # @!visibility private
75
+ @@loaded_zones = nil
68
76
 
69
77
  # Default value of the dst parameter of the local_to_utc and
70
78
  # period_for_local methods.
79
+ #
80
+ # @!visibility private
71
81
  @@default_dst = nil
72
82
 
73
83
  # Sets the default value of the optional dst parameter of the
@@ -93,7 +103,14 @@ module TZInfo
93
103
  def self.get(identifier)
94
104
  instance = @@loaded_zones[identifier]
95
105
 
96
- unless instance
106
+ unless instance
107
+ # Thread-safety: It is possible that multiple equivalent Timezone
108
+ # instances could be created here in concurrently executing threads.
109
+ # The consequences of this are that the data may be loaded more than
110
+ # once (depending on the data source) and memoized calculations could
111
+ # be discarded. The performance benefit of ensuring that only a single
112
+ # instance is created is unlikely to be worth the overhead of only
113
+ # allowing one Timezone to be loaded at a time.
97
114
  info = data_source.load_timezone_info(identifier)
98
115
  instance = info.create_timezone
99
116
  @@loaded_zones[instance.identifier] = instance
@@ -284,6 +301,32 @@ module TZInfo
284
301
  raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
285
302
  end
286
303
 
304
+ # Returns an Array of TimezoneTransition instances representing the times
305
+ # where the UTC offset of the timezone changes.
306
+ #
307
+ # Transitions are returned up to a given date and time up to a given date
308
+ # and time, specified in UTC (utc_to).
309
+ #
310
+ # A from date and time may also be supplied using the utc_from parameter
311
+ # (also specified in UTC). If utc_from is not nil, only transitions from
312
+ # that date and time onwards will be returned.
313
+ #
314
+ # Comparisons with utc_to are exclusive. Comparisons with utc_from are
315
+ # inclusive. If a transition falls precisely on utc_to, it will be excluded.
316
+ # If a transition falls on utc_from, it will be included.
317
+ #
318
+ # Transitions returned are ordered by when they occur, from earliest to
319
+ # latest.
320
+ #
321
+ # utc_to and utc_from can be specified using either DateTime, Time or
322
+ # integer timestamps (Time.to_i).
323
+ #
324
+ # If utc_from is specified and utc_to is not greater than utc_from, then
325
+ # transitions_up_to raises an ArgumentError exception.
326
+ def transitions_up_to(utc_to, utc_from = nil)
327
+ raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
328
+ end
329
+
287
330
  # Returns the TimezonePeriod for the given local time. local can either be
288
331
  # a DateTime, Time or integer timestamp (Time.to_i). Any timezone
289
332
  # information in local is ignored (it is treated as a time in the current
@@ -418,6 +461,59 @@ module TZInfo
418
461
  }
419
462
  end
420
463
 
464
+ # Returns information about offsets used by the Timezone up to a given
465
+ # date and time, specified using UTC (utc_to). The information is returned
466
+ # as an Array of TimezoneOffset instances.
467
+ #
468
+ # A from date and time may also be supplied using the utc_from parameter
469
+ # (also specified in UTC). If utc_from is not nil, only offsets used from
470
+ # that date and time forward will be returned.
471
+ #
472
+ # Comparisons with utc_to are exclusive. Comparisons with utc_from are
473
+ # inclusive.
474
+ #
475
+ # Offsets may be returned in any order.
476
+ #
477
+ # utc_to and utc_from can be specified using either DateTime, Time or
478
+ # integer timestamps (Time.to_i).
479
+ #
480
+ # If utc_from is specified and utc_to is not greater than utc_from, then
481
+ # offsets_up_to raises an ArgumentError exception.
482
+ def offsets_up_to(utc_to, utc_from = nil)
483
+ utc_to = TimeOrDateTime.wrap(utc_to)
484
+ transitions = transitions_up_to(utc_to, utc_from)
485
+
486
+ if transitions.empty?
487
+ # No transitions in the range, find the period that covers it.
488
+
489
+ if utc_from
490
+ # Use the from date as it is inclusive.
491
+ period = period_for_utc(utc_from)
492
+ else
493
+ # utc_to is exclusive, so this can't be used with period_for_utc.
494
+ # However, any time earlier than utc_to can be used.
495
+
496
+ # Subtract 1 hour (since this is one of the cached OffsetRationals).
497
+ # Use add_with_convert so that conversion to DateTime is performed if
498
+ # required.
499
+ period = period_for_utc(utc_to.add_with_convert(-3600))
500
+ end
501
+
502
+ [period.offset]
503
+ else
504
+ result = Set.new
505
+
506
+ first = transitions.first
507
+ result << first.previous_offset unless utc_from && first.at == utc_from
508
+
509
+ transitions.each do |t|
510
+ result << t.offset
511
+ end
512
+
513
+ result.to_a
514
+ end
515
+ end
516
+
421
517
  # Returns the current time in the timezone as a Time.
422
518
  def now
423
519
  utc_to_local(Time.now.utc)
@@ -487,7 +583,13 @@ module TZInfo
487
583
  Timezone.get(data)
488
584
  end
489
585
 
490
- private
586
+ private
587
+ # Initializes @@loaded_zones.
588
+ def self.init_loaded_zones
589
+ @@loaded_zones = ThreadSafe::Cache.new
590
+ end
591
+ init_loaded_zones
592
+
491
593
  # Returns an array of proxies corresponding to the given array of
492
594
  # identifiers.
493
595
  def self.get_proxies(identifiers)
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006-2010 Philip Ross
2
+ # Copyright (c) 2006-2013 Philip Ross
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -24,6 +24,8 @@ module TZInfo
24
24
 
25
25
  # TimezoneDefinition is included into Timezone definition modules.
26
26
  # TimezoneDefinition provides the methods for defining timezones.
27
+ #
28
+ # @private
27
29
  module TimezoneDefinition #:nodoc:
28
30
  # Add class methods to the includee.
29
31
  def self.append_features(base)
@@ -32,6 +34,8 @@ module TZInfo
32
34
  end
33
35
 
34
36
  # Class methods for inclusion.
37
+ #
38
+ # @private
35
39
  module ClassMethods #:nodoc:
36
40
  # Returns and yields a TransitionDataTimezoneInfo object to define a
37
41
  # timezone.
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2006 Philip Ross
2
+ # Copyright (c) 2006-2013 Philip Ross
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +23,10 @@
23
23
  module TZInfo
24
24
  # The timezone index file includes TimezoneIndexDefinition which provides
25
25
  # methods used to define timezones in the index.
26
+ #
27
+ # @private
26
28
  module TimezoneIndexDefinition #:nodoc:
29
+ # Add class methods to the includee and initialize class instance variables.
27
30
  def self.append_features(base)
28
31
  super
29
32
  base.extend(ClassMethods)
@@ -34,6 +37,9 @@ module TZInfo
34
37
  end
35
38
  end
36
39
 
40
+ # Class methods for inclusion.
41
+ #
42
+ # @private
37
43
  module ClassMethods #:nodoc:
38
44
  # Defines a timezone based on data.
39
45
  def timezone(identifier)