~ubuntu-branches/ubuntu/trusty/python-tz/trusty-updates

« back to all changes in this revision

Viewing changes to pytz/tzinfo.py

  • Committer: Bazaar Package Importer
  • Author(s): Brian Sutherland
  • Date: 2005-02-08 02:14:33 UTC
  • Revision ID: james.westby@ubuntu.com-20050208021433-t6c9teignwsm2ywo
Tags: upstream-2005a
ImportĀ upstreamĀ versionĀ 2005a

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
'''$Id: tzinfo.py,v 1.6 2004/07/24 21:21:28 zenzen Exp $'''
 
3
 
 
4
__rcs_id__  = '$Id: tzinfo.py,v 1.6 2004/07/24 21:21:28 zenzen Exp $'
 
5
__version__ = '$Revision: 1.6 $'[11:-2]
 
6
 
 
7
from datetime import datetime, timedelta, tzinfo
 
8
from bisect import bisect_right
 
9
from sets import Set
 
10
 
 
11
_timedelta_cache = {}
 
12
def memorized_timedelta(seconds):
 
13
    '''Create only one instance of each distinct timedelta'''
 
14
    try:
 
15
        return _timedelta_cache[seconds]
 
16
    except KeyError:
 
17
        delta = timedelta(seconds=seconds)
 
18
        _timedelta_cache[seconds] = delta
 
19
        return delta
 
20
 
 
21
_datetime_cache = {}
 
22
def memorized_datetime(*args):
 
23
    '''Create only one instance of each distinct datetime'''
 
24
    try:
 
25
        return _datetime_cache[args]
 
26
    except KeyError:
 
27
        dt = datetime(*args)
 
28
        _datetime_cache[args] = dt
 
29
        return dt
 
30
 
 
31
_ttinfo_cache = {}
 
32
def memorized_ttinfo(*args):
 
33
    '''Create only one instance of each distinct tuple'''
 
34
    try:
 
35
        return _ttinfo_cache[args]
 
36
    except KeyError:
 
37
        ttinfo = (
 
38
                memorized_timedelta(args[0]),
 
39
                memorized_timedelta(args[1]),
 
40
                args[2]
 
41
                )
 
42
        _ttinfo_cache[args] = ttinfo
 
43
        return ttinfo
 
44
 
 
45
_notime = memorized_timedelta(0)
 
46
 
 
47
class BaseTzInfo(tzinfo):
 
48
    # Overridden in subclass
 
49
    _utcoffset = None
 
50
    _tzname = None
 
51
    _zone = None
 
52
 
 
53
    def __str__(self):
 
54
        return self._zone
 
55
    
 
56
 
 
57
class StaticTzInfo(BaseTzInfo):
 
58
    '''A timezone that has a constant offset from UTC
 
59
 
 
60
    These timezones are rare, as most regions have changed their
 
61
    offset from UTC at some point in their history
 
62
 
 
63
    '''
 
64
    def fromutc(self, dt):
 
65
        '''See datetime.tzinfo.fromutc'''
 
66
        return (dt + self._utcoffset).replace(tzinfo=self)
 
67
    
 
68
    def utcoffset(self,dt):
 
69
        '''See datetime.tzinfo.utcoffset'''
 
70
        return self._utcoffset
 
71
 
 
72
    def dst(self,dt):
 
73
        '''See datetime.tzinfo.dst'''
 
74
        return _notime
 
75
 
 
76
    def tzname(self,dt):
 
77
        '''See datetime.tzinfo.tzname'''
 
78
        return self._tzname
 
79
 
 
80
    def localize(self, dt, is_dst=False):
 
81
        '''Convert naive time to local time'''
 
82
        if dt.tzinfo is not None:
 
83
            raise ValueError, 'Not naive datetime (tzinfo is already set)'
 
84
        return dt.replace(tzinfo=self)
 
85
 
 
86
    def normalize(self, dt, is_dst=False):
 
87
        '''Correct the timezone information on the given datetime'''
 
88
        if dt.tzinfo is None:
 
89
            raise ValueError, 'Naive time - no tzinfo set'
 
90
        return dt.replace(tzinfo=self)
 
91
 
 
92
    def __repr__(self):
 
93
        return '<StaticTzInfo %r>' % (self._zone,)
 
94
 
 
95
 
 
96
class DstTzInfo(BaseTzInfo):
 
97
    '''A timezone that has a variable offset from UTC
 
98
   
 
99
    The offset might change if daylight savings time comes into effect,
 
100
    or at a point in history when the region decides to change their 
 
101
    timezone definition. 
 
102
 
 
103
    '''
 
104
    # Overridden in subclass
 
105
    _utc_transition_times = None # Sorted list of DST transition times in UTC
 
106
    _transition_info = None # [(utcoffset, dstoffset, tzname)] corresponding
 
107
                            # to _utc_transition_times entries
 
108
    _zone = None
 
109
 
 
110
    # Set in __init__
 
111
    _tzinfos = None
 
112
    _dst = None # DST offset
 
113
 
 
114
    def __init__(self, _inf=None, _tzinfos=None):
 
115
        if _inf:
 
116
            self._tzinfos = _tzinfos
 
117
            self._utcoffset, self._dst, self._tzname = _inf
 
118
        else:
 
119
            _tzinfos = {}
 
120
            self._tzinfos = _tzinfos
 
121
            self._utcoffset, self._dst, self._tzname = self._transition_info[0]
 
122
            _tzinfos[self._transition_info[0]] = self
 
123
            for inf in self._transition_info[1:]:
 
124
                if not _tzinfos.has_key(inf):
 
125
                    _tzinfos[inf] = self.__class__(inf, _tzinfos)
 
126
 
 
127
    def fromutc(self, dt):
 
128
        '''See datetime.tzinfo.fromutc'''
 
129
        dt = dt.replace(tzinfo=None)
 
130
        idx = max(0, bisect_right(self._utc_transition_times, dt) - 1)
 
131
        inf = self._transition_info[idx]
 
132
        return (dt + inf[0]).replace(tzinfo=self._tzinfos[inf])
 
133
 
 
134
    def normalize(self, dt):
 
135
        '''Correct the timezone information on the given datetime
 
136
 
 
137
        If date arithmetic crosses DST boundaries, the tzinfo
 
138
        is not magically adjusted. This method normalizes the
 
139
        tzinfo to the correct one.
 
140
 
 
141
        To test, first we need to do some setup
 
142
 
 
143
        >>> from pytz import timezone
 
144
        >>> utc = timezone('UTC')
 
145
        >>> eastern = timezone('US/Eastern')
 
146
        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
 
147
 
 
148
        We next create a datetime right on an end-of-DST transition point,
 
149
        the instant when the wallclocks are wound back one hour.
 
150
 
 
151
        >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)
 
152
        >>> loc_dt = utc_dt.astimezone(eastern)
 
153
        >>> loc_dt.strftime(fmt)
 
154
        '2002-10-27 01:00:00 EST (-0500)'
 
155
 
 
156
        Now, if we subtract a few minutes from it, note that the timezone
 
157
        information has not changed.
 
158
 
 
159
        >>> before = loc_dt - timedelta(minutes=10)
 
160
        >>> before.strftime(fmt)
 
161
        '2002-10-27 00:50:00 EST (-0500)'
 
162
 
 
163
        But we can fix that by calling the normalize method
 
164
 
 
165
        >>> before = eastern.normalize(before)
 
166
        >>> before.strftime(fmt)
 
167
        '2002-10-27 01:50:00 EDT (-0400)'
 
168
 
 
169
        '''
 
170
        if dt.tzinfo is None:
 
171
            raise ValueError, 'Naive time - no tzinfo set'
 
172
 
 
173
        # Convert dt in localtime to UTC
 
174
        offset = dt.tzinfo._utcoffset
 
175
        dt = dt.replace(tzinfo=None)
 
176
        dt = dt - offset
 
177
        # convert it back, and return it
 
178
        return self.fromutc(dt)
 
179
 
 
180
    def localize(self, dt, is_dst=False):
 
181
        '''Convert naive time to local time.
 
182
        
 
183
        This method should be used to construct localtimes, rather
 
184
        than passing a tzinfo argument to a datetime constructor.
 
185
 
 
186
        is_dst is used to determine the correct timezone in the ambigous
 
187
        period at the end of daylight savings time.
 
188
        
 
189
        >>> from pytz import timezone
 
190
        >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)'
 
191
        >>> amdam = timezone('Europe/Amsterdam')
 
192
        >>> dt  = datetime(2004, 10, 31, 2, 0, 0)
 
193
        >>> loc_dt1 = amdam.localize(dt, is_dst=True)
 
194
        >>> loc_dt2 = amdam.localize(dt, is_dst=False)
 
195
        >>> loc_dt1.strftime(fmt)
 
196
        '2004-10-31 02:00:00 CEST (+0200)'
 
197
        >>> loc_dt2.strftime(fmt)
 
198
        '2004-10-31 02:00:00 CET (+0100)'
 
199
        >>> str(loc_dt2 - loc_dt1)
 
200
        '1:00:00'
 
201
 
 
202
        Use is_dst=None to raise an AmbiguousTimeError for ambiguous
 
203
        times at the end of daylight savings
 
204
 
 
205
        >>> try:
 
206
        ...     loc_dt1 = amdam.localize(dt, is_dst=None)
 
207
        ... except AmbiguousTimeError:
 
208
        ...     print 'Oops'
 
209
        Oops
 
210
 
 
211
        >>> loc_dt1 = amdam.localize(dt, is_dst=None)
 
212
        Traceback (most recent call last):
 
213
            [...]
 
214
        AmbiguousTimeError: 2004-10-31 02:00:00
 
215
 
 
216
        is_dst defaults to False
 
217
        
 
218
        >>> amdam.localize(dt) == amdam.localize(dt, False)
 
219
        True
 
220
 
 
221
        '''
 
222
        if dt.tzinfo is not None:
 
223
            raise ValueError, 'Not naive datetime (tzinfo is already set)'
 
224
 
 
225
        # Find the possibly correct timezones. We probably just have one,
 
226
        # but we might end up with two if we are in the end-of-DST
 
227
        # transition period. Or possibly more in some particularly confused
 
228
        # location...
 
229
        possible_loc_dt = Set()
 
230
        for tzinfo in self._tzinfos.values():
 
231
            loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo))
 
232
            if loc_dt.replace(tzinfo=None) == dt:
 
233
                possible_loc_dt.add(loc_dt)
 
234
 
 
235
        if len(possible_loc_dt) == 1:
 
236
            return possible_loc_dt.pop()
 
237
 
 
238
        # If told to be strict, raise an exception since we have an
 
239
        # ambiguous case
 
240
        if is_dst is None:
 
241
            raise AmbiguousTimeError(dt)
 
242
 
 
243
        # Filter out the possiblilities that don't match the requested
 
244
        # is_dst
 
245
        filtered_possible_loc_dt = [
 
246
            p for p in possible_loc_dt
 
247
                if bool(p.tzinfo._dst) == is_dst
 
248
            ]
 
249
 
 
250
        # Hopefully we only have one possibility left. Return it.
 
251
        if len(filtered_possible_loc_dt) == 1:
 
252
            return filtered_possible_loc_dt[0]
 
253
 
 
254
        if len(filtered_possible_loc_dt) == 0:
 
255
            filtered_possible_loc_dt = list(possible_loc_dt)
 
256
 
 
257
        # If we get this far, we have in a wierd timezone transition
 
258
        # where the clocks have been wound back but is_dst is the same
 
259
        # in both (eg. Europe/Warsaw 1915 when they switched to CET).
 
260
        # At this point, we just have to guess unless we allow more
 
261
        # hints to be passed in (such as the UTC offset or abbreviation),
 
262
        # but that is just getting silly.
 
263
        #
 
264
        # Choose the earliest (by UTC) applicable timezone.
 
265
        def mycmp(a,b):
 
266
            return cmp(
 
267
                    a.replace(tzinfo=None) - a.tzinfo._utcoffset,
 
268
                    b.replace(tzinfo=None) - b.tzinfo._utcoffset,
 
269
                    )
 
270
        filtered_possible_loc_dt.sort(mycmp)
 
271
        return filtered_possible_loc_dt[0]
 
272
        
 
273
    def utcoffset(self, dt):
 
274
        '''See datetime.tzinfo.utcoffset'''
 
275
        return self._utcoffset
 
276
 
 
277
    def dst(self, dt):
 
278
        '''See datetime.tzinfo.dst'''
 
279
        return self._dst
 
280
 
 
281
    def tzname(self, dt):
 
282
        '''See datetime.tzinfo.tzname'''
 
283
        return self._tzname
 
284
 
 
285
    def __repr__(self):
 
286
        if self._dst:
 
287
            dst = 'DST'
 
288
        else:
 
289
            dst = 'STD'
 
290
        if self._utcoffset > _notime:
 
291
            return '<DstTzInfo %r %s+%s %s>' % (
 
292
                    self._zone, self._tzname, self._utcoffset, dst
 
293
                )
 
294
        else:
 
295
            return '<DstTzInfo %r %s%s %s>' % (
 
296
                    self._zone, self._tzname, self._utcoffset, dst
 
297
                )
 
298
 
 
299
class AmbiguousTimeError(Exception):
 
300
    '''Exception raised when attempting to create an ambiguous wallclock time.
 
301
 
 
302
    At the end of a DST transition period, a particular wallclock time will
 
303
    occur twice (once before the clocks are set back, once after). Both
 
304
    possibilities may be correct, unless further information is supplied.
 
305
 
 
306
    See DstTzInfo.normalize() for more info
 
307
 
 
308
    '''
 
309
       
 
310