File: timezone.rb

package info (click to toggle)
mhc 1.1.1-2
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 2,320 kB
  • ctags: 3,529
  • sloc: ruby: 12,404; lisp: 7,448; makefile: 70; sh: 69
file content (197 lines) | stat: -rw-r--r-- 7,285 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
module RiCal
  class Component
    #- ©2009 Rick DeNatale, All rights reserved. Refer to the file README.txt for the license
    #
    # An Timezone (VTIMEZONE) calendar component describes a timezone used within the calendar.
    # A Timezone has two or more TimezonePeriod subcomponents which describe the transitions between
    # standard and daylight saving time.
    #
    # to see the property accessing methods for this class see the RiCal::Properties::Timezone module
    class Timezone < Component
      
      autoload :TimezonePeriod, "ri_cal/component/timezone/timezone_period.rb"
      autoload :StandardPeriod, "ri_cal/component/timezone/standard_period.rb"
      autoload :DaylightPeriod, "ri_cal/component/timezone/daylight_period.rb"
      
      include RiCal::Properties::Timezone

        # The identifier of the timezone, e.g. "Europe/Paris".
        def identifier
          tzid
        end

        # An alias for identifier.
        def name
          # Don't use alias, as identifier gets overridden.
          identifier
        end
        
        def rational_utc_offset(local) #:nodoc:
          # 86400 is the number of seconds in a day
          RiCal.RationalOffset[period_for_local(local, true).utc_total_offset]
        end

        # Returns the TimezonePeriod for the given UTC time. utc can either be a DateTime,
        # Time or integer timestamp (Time.to_i). Any timezone information in utc is ignored (it is treated as a UTC time).
        def period_for_utc(time)
          last_period(last_before_utc(standard, time), last_before_utc(daylight, time))
        end

        # Returns the set of TimezonePeriod instances that are valid for the given
        # local time as an array. If you just want a single period, use
        # period_for_local instead and specify how ambiguities should be resolved.
        # Returns an empty array if no periods are found for the given time.
        def periods_for_local(local)
          local = local.to_ri_cal_date_time_value
          candidate_standard = last_before_local(standard, local)
          candidate_daylight = last_before_local(daylight, local)
          if candidate_daylight && candidate_daylight.swallows_local?(local, candidate_standard)
            []  # Invalid local time
          elsif candidate_standard
            if candidate_daylight
              if candidate_daylight.dtstart > candidate_standard.dtstart
                [candidate_daylight]
              elsif candidate_standard.ambiguous_local?(local)
                [candidate_daylight, candidate_standard]
              else
                [candidate_standard].compact
              end
            else
              [candidate_standard].compact
            end
          end
        end


        # Returns the TimezonePeriod for the given local time. local can either be
        # a DateTime, Time or integer timestamp (Time.to_i). Any timezone
        # information in local is ignored (it is treated as a time in the current
        # timezone).
        #
        # Warning: There are local times that have no equivalent UTC times (e.g.
        # in the transition from standard time to daylight savings time). There are
        # also local times that have more than one UTC equivalent (e.g. in the
        # transition from daylight savings time to standard time).
        #
        # In the first case (no equivalent UTC time), a PeriodNotFound exception
        # will be raised.
        #
        # In the second case (more than one equivalent UTC time), an AmbiguousTime
        # exception will be raised unless the optional dst parameter or block
        # handles the ambiguity.
        #
        # If the ambiguity is due to a transition from daylight savings time to
        # standard time, the dst parameter can be used to select whether the
        # daylight savings time or local time is used. For example,
        #
        #   Timezone.get('America/New_York').period_for_local(DateTime.new(2004,10,31,1,30,0))
        #
        # would raise an AmbiguousTime exception.
        #
        # Specifying dst=true would the daylight savings period from April to
        # October 2004. Specifying dst=false would return the standard period
        # from October 2004 to April 2005.
        #
        # If the dst parameter does not resolve the ambiguity, and a block is
        # specified, it is called. The block must take a single parameter - an
        # array of the periods that need to be resolved. The block can select and
        # return a single period or return nil or an empty array
        # to cause an AmbiguousTime exception to be raised.
        #
        # TODO: need to check for ambiguity
        def period_for_local(local, dst=nil)
          results = periods_for_local(local)

          if results.empty?
            raise TZInfo::PeriodNotFound
          elsif results.size < 2
            results.first
          else
            # ambiguous result try to resolve

            unless dst.nil?
              matches = results.find_all {|period| period.dst? == dst}
              results = matches unless matches.empty?
            end

            if results.size < 2
              results.first
            else
              # still ambiguous, try the block

              if block_given?
                results = yield results
              end

              if results.is_a?(TimezonePeriod)
                results
              elsif results && results.size == 1
                results.first
              else
                raise TZInfo::AmbiguousTime, "#{local} is an ambiguous local time."
              end
            end
          end
        end

        # convert time from utc time to this time zone
        def utc_to_local(time)
          time = time.to_ri_cal_date_time_value
          converted = time + period_for_utc(time).tzoffsetto_property
          converted.tzid = identifier
          converted
        end

        # convert time from this time zone to utc time
        def local_to_utc(time)
          time = time.to_ri_cal_date_time_value
          period = period_for_local(time)
          converted = time - period.tzoffsetto_property
          converted.tzid = "UTC"
          converted
        end
      end

      def self.entity_name #:nodoc:
        "VTIMEZONE"
      end

      def standard #:nodoc:
        @subcomponents["STANDARD"]
      end

      def daylight #:nodoc:
        @subcomponents["DAYLIGHT"]
      end

      def last_period(standard, daylight) #:nodoc:
        if standard
          if daylight
            standard.dtstart > daylight.dtstart ? standard : daylight
          else
            standard
          end
        else
          daylight
        end
      end

      def last_before_utc(period_array, time) #:nodoc:
        candidates = period_array.map {|period|
          period.last_before_utc(time)
        }
        result = candidates.max {|a, b| a.dtstart_property <=> b.dtstart_property}
        result
      end

      def last_before_local(period_array, time) #:nodoc:
        candidates = period_array.map {|period|
          period.last_before_local(time)
        }
        result = candidates.max {|a, b| a.dtstart_property <=> b.dtstart_property}
        result
      end
  end
end