Mercurial > p > roundup > code
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 |
