comparison roundup/date.py @ 3619:df7bff6f8a2f

full timezone support (based on patch [SF#1465296])
author Alexander Smishlajev <a1s@users.sourceforge.net>
date Sat, 06 May 2006 16:43:29 +0000
parents afda59d5d546
children 06d7816976bc
comparison
equal deleted inserted replaced
3618:b31a2e35be80 3619:df7bff6f8a2f
13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" 14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, 15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
17 # 17 #
18 # $Id: date.py,v 1.86 2006-04-27 05:15:16 richard Exp $ 18 # $Id: date.py,v 1.87 2006-05-06 16:43:29 a1s Exp $
19 19
20 """Date, time and time interval handling. 20 """Date, time and time interval handling.
21 """ 21 """
22 __docformat__ = 'restructuredtext' 22 __docformat__ = 'restructuredtext'
23 23
24 import time, re, calendar 24 import calendar
25 import i18n 25 import datetime
26 import time
27 import re
26 28
27 try: 29 try:
28 import datetime 30 import pytz
29 have_datetime = 1 31 except ImportError:
30 except: 32 pytz = None
31 have_datetime = 0 33
34 from roundup import i18n
32 35
33 def _add_granularity(src, order, value = 1): 36 def _add_granularity(src, order, value = 1):
34 '''Increment first non-None value in src dictionary ordered by 'order' 37 '''Increment first non-None value in src dictionary ordered by 'order'
35 parameter 38 parameter
36 ''' 39 '''
49 (?P<o>[\d\smywd\-+]+)? # offset 52 (?P<o>[\d\smywd\-+]+)? # offset
50 $''', re.VERBOSE) 53 $''', re.VERBOSE)
51 serialised_date_re = re.compile(r''' 54 serialised_date_re = re.compile(r'''
52 (\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d?(\.\d+)?) 55 (\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d?(\.\d+)?)
53 ''', re.VERBOSE) 56 ''', re.VERBOSE)
57
58 _timedelta0 = datetime.timedelta(0)
59
60 # load UTC tzinfo
61 if pytz:
62 UTC = pytz.utc
63 else:
64 # fallback implementation from Python Library Reference
65
66 class _UTC(datetime.tzinfo):
67
68 """Universal Coordinated Time zoneinfo"""
69
70 def utcoffset(self, dt):
71 return _timedelta0
72
73 def tzname(self, dt):
74 return "UTC"
75
76 def dst(self, dt):
77 return _timedelta0
78
79 def __repr__(self):
80 return "<UTC>"
81
82 # pytz adjustments interface
83 # Note: pytz verifies that dt is naive datetime for localize()
84 # and not naive datetime for normalize().
85 # In this implementation, we don't care.
86
87 def normalize(self, dt, is_dst=False):
88 return dt.replace(tzinfo=self)
89
90 def localize(self, dt, is_dst=False):
91 return dt.replace(tzinfo=self)
92
93 UTC = _UTC()
94
95 # integral hours offsets were available in Roundup versions prior to 1.1.3
96 # and still are supported as a fallback if pytz module is not installed
97 class SimpleTimezone(datetime.tzinfo):
98
99 """Simple zoneinfo with fixed numeric offset and no daylight savings"""
100
101 def __init__(self, offset=0, name=None):
102 super(SimpleTimezone, self).__init__()
103 self.offset = offset
104 if name:
105 self.name = name
106 else:
107 self.name = "Etc/GMT%+d" % self.offset
108
109 def utcoffset(self, dt):
110 return datetime.timedelta(hours=self.offset)
111
112 def tzname(self, dt):
113 return self.name
114
115 def dst(self, dt):
116 return _timedelta0
117
118 def __repr__(self):
119 return "<%s: %s>" % (self.__class__.__name__, self.name)
120
121 # pytz adjustments interface
122
123 def normalize(self, dt):
124 return dt.replace(tzinfo=self)
125
126 def localize(self, dt, is_dst=False):
127 return dt.replace(tzinfo=self)
128
129 # simple timezones with fixed offset
130 _tzoffsets = dict(GMT=0, UCT=0, EST=5, MST=7, HST=10)
131
132 def get_timezone(tz):
133 # if tz is None, return None (will result in naive datetimes)
134 # XXX should we return UTC for None?
135 if tz is None:
136 return None
137 # try integer offset first for backward compatibility
138 try:
139 utcoffset = int(tz)
140 except (TypeError, ValueError):
141 pass
142 else:
143 if utcoffset == 0:
144 return UTC
145 else:
146 return SimpleTimezone(utcoffset)
147 # tz is a timezone name
148 if pytz:
149 return pytz.timezone(tz)
150 elif tz == "UTC":
151 return UTC
152 elif tz in _tzoffsets:
153 return SimpleTimezone(_tzoffsets[tz], tz)
154 else:
155 raise KeyError, tz
156
157 def _utc_to_local(y,m,d,H,M,S,tz):
158 TZ = get_timezone(tz)
159 frac = S - int(S)
160 dt = datetime.datetime(y, m, d, H, M, int(S), tzinfo=UTC)
161 y,m,d,H,M,S = dt.astimezone(TZ).timetuple()[:6]
162 S = S + frac
163 return (y,m,d,H,M,S)
164
165 def _local_to_utc(y,m,d,H,M,S,tz):
166 TZ = get_timezone(tz)
167 dt = datetime.datetime(y,m,d,H,M,int(S))
168 y,m,d,H,M,S = TZ.localize(dt).utctimetuple()[:6]
169 return (y,m,d,H,M,S)
54 170
55 class Date: 171 class Date:
56 ''' 172 '''
57 As strings, date-and-time stamps are specified with the date in 173 As strings, date-and-time stamps are specified with the date in
58 international standard format (yyyy-mm-dd) joined to the time 174 international standard format (yyyy-mm-dd) joined to the time
142 """ 258 """
143 self.setTranslator(translator) 259 self.setTranslator(translator)
144 if type(spec) == type(''): 260 if type(spec) == type(''):
145 self.set(spec, offset=offset, add_granularity=add_granularity) 261 self.set(spec, offset=offset, add_granularity=add_granularity)
146 return 262 return
147 elif have_datetime and isinstance(spec, datetime.datetime): 263 elif isinstance(spec, datetime.datetime):
148 # Python 2.3+ datetime object 264 # Python 2.3+ datetime object
149 y,m,d,H,M,S,x,x,x = spec.timetuple() 265 y,m,d,H,M,S,x,x,x = spec.timetuple()
150 if y < 1970: raise ValueError, 'year must be > 1970' 266 if y < 1970: raise ValueError, 'year must be > 1970'
151 S += spec.microsecond/1000000. 267 S += spec.microsecond/1000000.
152 spec = (y,m,d,H,M,S,x,x,x) 268 spec = (y,m,d,H,M,S,x,x,x)
156 spec = spec.get_tuple() 272 spec = spec.get_tuple()
157 try: 273 try:
158 y,m,d,H,M,S,x,x,x = spec 274 y,m,d,H,M,S,x,x,x = spec
159 if y < 1970: raise ValueError, 'year must be > 1970' 275 if y < 1970: raise ValueError, 'year must be > 1970'
160 frac = S - int(S) 276 frac = S - int(S)
161 ts = calendar.timegm((y,m,d,H+offset,M,S,0,0,0))
162 self.year, self.month, self.day, self.hour, self.minute, \ 277 self.year, self.month, self.day, self.hour, self.minute, \
163 self.second, x, x, x = time.gmtime(ts) 278 self.second = _local_to_utc(y, m, d, H, M, S, offset)
164 # we lost the fractional part 279 # we lost the fractional part
165 self.second = self.second + frac 280 self.second = self.second + frac
166 except: 281 except:
167 raise ValueError, 'Unknown spec %r' % (spec,) 282 raise ValueError, 'Unknown spec %r' % (spec,)
168 283
196 ts = time.time() 311 ts = time.time()
197 frac = ts - int(ts) 312 frac = ts - int(ts)
198 y,m,d,H,M,S,x,x,x = time.gmtime(ts) 313 y,m,d,H,M,S,x,x,x = time.gmtime(ts)
199 # gmtime loses the fractional seconds 314 # gmtime loses the fractional seconds
200 S = S + frac 315 S = S + frac
316
317 # whether we need to convert to UTC
318 adjust = False
201 319
202 if info['y'] is not None or info['a'] is not None: 320 if info['y'] is not None or info['a'] is not None:
203 if info['y'] is not None: 321 if info['y'] is not None:
204 y = int(info['y']) 322 y = int(info['y'])
205 if y < 1970: raise ValueError, 'year must be > 1970' 323 if y < 1970: raise ValueError, 'year must be > 1970'
209 if info['d'] is not None: 327 if info['d'] is not None:
210 d = int(info['d']) 328 d = int(info['d'])
211 if info['a'] is not None: 329 if info['a'] is not None:
212 m = int(info['a']) 330 m = int(info['a'])
213 d = int(info['b']) 331 d = int(info['b'])
214 H = -offset 332 H = 0
215 M = S = 0 333 M = S = 0
334 adjust = True
216 335
217 # override hour, minute, second parts 336 # override hour, minute, second parts
218 if info['H'] is not None and info['M'] is not None: 337 if info['H'] is not None and info['M'] is not None:
219 H = int(info['H']) - offset 338 H = int(info['H'])
220 M = int(info['M']) 339 M = int(info['M'])
221 S = 0 340 S = 0
222 if info['S'] is not None: 341 if info['S'] is not None:
223 S = float(info['S']) 342 S = float(info['S'])
343 adjust = True
224 344
225 if add_granularity: 345 if add_granularity:
226 S = S - 1 346 S = S - 1
227 347
228 # now handle the adjustment of hour 348 # now handle the adjustment of hour
229 frac = S - int(S) 349 frac = S - int(S)
230 ts = calendar.timegm((y,m,d,H,M,S,0,0,0)) 350 ts = calendar.timegm((y,m,d,H,M,S,0,0,0))
351 y, m, d, H, M, S, x, x, x = time.gmtime(ts)
352 if adjust:
353 y, m, d, H, M, S = _local_to_utc(y, m, d, H, M, S, offset)
231 self.year, self.month, self.day, self.hour, self.minute, \ 354 self.year, self.month, self.day, self.hour, self.minute, \
232 self.second, x, x, x = time.gmtime(ts) 355 self.second = y, m, d, H, M, S
233 # we lost the fractional part along the way 356 # we lost the fractional part along the way
234 self.second = self.second + frac 357 self.second = self.second + frac
235 358
236 if info.get('o', None): 359 if info.get('o', None):
237 try: 360 try:
394 return '<Date %s>'%self.formal(sec='%06.3f') 517 return '<Date %s>'%self.formal(sec='%06.3f')
395 518
396 def local(self, offset): 519 def local(self, offset):
397 """ Return this date as yyyy-mm-dd.hh:mm:ss in a local time zone. 520 """ Return this date as yyyy-mm-dd.hh:mm:ss in a local time zone.
398 """ 521 """
399 return Date((self.year, self.month, self.day, self.hour + offset, 522 y, m, d, H, M, S = _utc_to_local(self.year, self.month, self.day,
400 self.minute, self.second, 0, 0, 0), translator=self.translator) 523 self.hour, self.minute, self.second, offset)
524 return Date((y, m, d, H, M, S, 0, 0, 0), translator=self.translator)
401 525
402 def __deepcopy__(self, memo): 526 def __deepcopy__(self, memo):
403 return Date((self.year, self.month, self.day, self.hour, 527 return Date((self.year, self.month, self.day, self.hour,
404 self.minute, self.second, 0, 0, 0), translator=self.translator) 528 self.minute, self.second, 0, 0, 0), translator=self.translator)
405 529

Roundup Issue Tracker: http://roundup-tracker.org/