OLD | NEW |
(Empty) | |
| 1 # -*- coding:iso-8859-1 -*- |
| 2 """ |
| 3 Copyright (c) 2003-2007 Gustavo Niemeyer <gustavo@niemeyer.net> |
| 4 |
| 5 This module offers extensions to the standard python 2.3+ |
| 6 datetime module. |
| 7 """ |
| 8 __author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>" |
| 9 __license__ = "PSF License" |
| 10 |
| 11 import datetime |
| 12 import string |
| 13 import time |
| 14 import sys |
| 15 import os |
| 16 |
| 17 try: |
| 18 from cStringIO import StringIO |
| 19 except ImportError: |
| 20 from StringIO import StringIO |
| 21 |
| 22 import relativedelta |
| 23 import tz |
| 24 |
| 25 |
| 26 __all__ = ["parse", "parserinfo"] |
| 27 |
| 28 |
| 29 # Some pointers: |
| 30 # |
| 31 # http://www.cl.cam.ac.uk/~mgk25/iso-time.html |
| 32 # http://www.iso.ch/iso/en/prods-services/popstds/datesandtime.html |
| 33 # http://www.w3.org/TR/NOTE-datetime |
| 34 # http://ringmaster.arc.nasa.gov/tools/time_formats.html |
| 35 # http://search.cpan.org/author/MUIR/Time-modules-2003.0211/lib/Time/ParseDate.p
m |
| 36 # http://stein.cshl.org/jade/distrib/docs/java.text.SimpleDateFormat.html |
| 37 |
| 38 |
| 39 class _timelex(object): |
| 40 |
| 41 def __init__(self, instream): |
| 42 if isinstance(instream, basestring): |
| 43 instream = StringIO(instream) |
| 44 self.instream = instream |
| 45 self.wordchars = ('abcdfeghijklmnopqrstuvwxyz' |
| 46 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_' |
| 47 'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ' |
| 48 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ') |
| 49 self.numchars = '0123456789' |
| 50 self.whitespace = ' \t\r\n' |
| 51 self.charstack = [] |
| 52 self.tokenstack = [] |
| 53 self.eof = False |
| 54 |
| 55 def get_token(self): |
| 56 if self.tokenstack: |
| 57 return self.tokenstack.pop(0) |
| 58 seenletters = False |
| 59 token = None |
| 60 state = None |
| 61 wordchars = self.wordchars |
| 62 numchars = self.numchars |
| 63 whitespace = self.whitespace |
| 64 while not self.eof: |
| 65 if self.charstack: |
| 66 nextchar = self.charstack.pop(0) |
| 67 else: |
| 68 nextchar = self.instream.read(1) |
| 69 while nextchar == '\x00': |
| 70 nextchar = self.instream.read(1) |
| 71 if not nextchar: |
| 72 self.eof = True |
| 73 break |
| 74 elif not state: |
| 75 token = nextchar |
| 76 if nextchar in wordchars: |
| 77 state = 'a' |
| 78 elif nextchar in numchars: |
| 79 state = '0' |
| 80 elif nextchar in whitespace: |
| 81 token = ' ' |
| 82 break # emit token |
| 83 else: |
| 84 break # emit token |
| 85 elif state == 'a': |
| 86 seenletters = True |
| 87 if nextchar in wordchars: |
| 88 token += nextchar |
| 89 elif nextchar == '.': |
| 90 token += nextchar |
| 91 state = 'a.' |
| 92 else: |
| 93 self.charstack.append(nextchar) |
| 94 break # emit token |
| 95 elif state == '0': |
| 96 if nextchar in numchars: |
| 97 token += nextchar |
| 98 elif nextchar == '.': |
| 99 token += nextchar |
| 100 state = '0.' |
| 101 else: |
| 102 self.charstack.append(nextchar) |
| 103 break # emit token |
| 104 elif state == 'a.': |
| 105 seenletters = True |
| 106 if nextchar == '.' or nextchar in wordchars: |
| 107 token += nextchar |
| 108 elif nextchar in numchars and token[-1] == '.': |
| 109 token += nextchar |
| 110 state = '0.' |
| 111 else: |
| 112 self.charstack.append(nextchar) |
| 113 break # emit token |
| 114 elif state == '0.': |
| 115 if nextchar == '.' or nextchar in numchars: |
| 116 token += nextchar |
| 117 elif nextchar in wordchars and token[-1] == '.': |
| 118 token += nextchar |
| 119 state = 'a.' |
| 120 else: |
| 121 self.charstack.append(nextchar) |
| 122 break # emit token |
| 123 if (state in ('a.', '0.') and |
| 124 (seenletters or token.count('.') > 1 or token[-1] == '.')): |
| 125 l = token.split('.') |
| 126 token = l[0] |
| 127 for tok in l[1:]: |
| 128 self.tokenstack.append('.') |
| 129 if tok: |
| 130 self.tokenstack.append(tok) |
| 131 return token |
| 132 |
| 133 def __iter__(self): |
| 134 return self |
| 135 |
| 136 def next(self): |
| 137 token = self.get_token() |
| 138 if token is None: |
| 139 raise StopIteration |
| 140 return token |
| 141 |
| 142 def split(cls, s): |
| 143 return list(cls(s)) |
| 144 split = classmethod(split) |
| 145 |
| 146 |
| 147 class _resultbase(object): |
| 148 |
| 149 def __init__(self): |
| 150 for attr in self.__slots__: |
| 151 setattr(self, attr, None) |
| 152 |
| 153 def _repr(self, classname): |
| 154 l = [] |
| 155 for attr in self.__slots__: |
| 156 value = getattr(self, attr) |
| 157 if value is not None: |
| 158 l.append("%s=%s" % (attr, `value`)) |
| 159 return "%s(%s)" % (classname, ", ".join(l)) |
| 160 |
| 161 def __repr__(self): |
| 162 return self._repr(self.__class__.__name__) |
| 163 |
| 164 |
| 165 class parserinfo(object): |
| 166 |
| 167 # m from a.m/p.m, t from ISO T separator |
| 168 JUMP = [" ", ".", ",", ";", "-", "/", "'", |
| 169 "at", "on", "and", "ad", "m", "t", "of", |
| 170 "st", "nd", "rd", "th"] |
| 171 |
| 172 WEEKDAYS = [("Mon", "Monday"), |
| 173 ("Tue", "Tuesday"), |
| 174 ("Wed", "Wednesday"), |
| 175 ("Thu", "Thursday"), |
| 176 ("Fri", "Friday"), |
| 177 ("Sat", "Saturday"), |
| 178 ("Sun", "Sunday")] |
| 179 MONTHS = [("Jan", "January"), |
| 180 ("Feb", "February"), |
| 181 ("Mar", "March"), |
| 182 ("Apr", "April"), |
| 183 ("May", "May"), |
| 184 ("Jun", "June"), |
| 185 ("Jul", "July"), |
| 186 ("Aug", "August"), |
| 187 ("Sep", "September"), |
| 188 ("Oct", "October"), |
| 189 ("Nov", "November"), |
| 190 ("Dec", "December")] |
| 191 HMS = [("h", "hour", "hours"), |
| 192 ("m", "minute", "minutes"), |
| 193 ("s", "second", "seconds")] |
| 194 AMPM = [("am", "a"), |
| 195 ("pm", "p")] |
| 196 UTCZONE = ["UTC", "GMT", "Z"] |
| 197 PERTAIN = ["of"] |
| 198 TZOFFSET = {} |
| 199 |
| 200 def __init__(self, dayfirst=False, yearfirst=False): |
| 201 self._jump = self._convert(self.JUMP) |
| 202 self._weekdays = self._convert(self.WEEKDAYS) |
| 203 self._months = self._convert(self.MONTHS) |
| 204 self._hms = self._convert(self.HMS) |
| 205 self._ampm = self._convert(self.AMPM) |
| 206 self._utczone = self._convert(self.UTCZONE) |
| 207 self._pertain = self._convert(self.PERTAIN) |
| 208 |
| 209 self.dayfirst = dayfirst |
| 210 self.yearfirst = yearfirst |
| 211 |
| 212 self._year = time.localtime().tm_year |
| 213 self._century = self._year//100*100 |
| 214 |
| 215 def _convert(self, lst): |
| 216 dct = {} |
| 217 for i in range(len(lst)): |
| 218 v = lst[i] |
| 219 if isinstance(v, tuple): |
| 220 for v in v: |
| 221 dct[v.lower()] = i |
| 222 else: |
| 223 dct[v.lower()] = i |
| 224 return dct |
| 225 |
| 226 def jump(self, name): |
| 227 return name.lower() in self._jump |
| 228 |
| 229 def weekday(self, name): |
| 230 if len(name) >= 3: |
| 231 try: |
| 232 return self._weekdays[name.lower()] |
| 233 except KeyError: |
| 234 pass |
| 235 return None |
| 236 |
| 237 def month(self, name): |
| 238 if len(name) >= 3: |
| 239 try: |
| 240 return self._months[name.lower()]+1 |
| 241 except KeyError: |
| 242 pass |
| 243 return None |
| 244 |
| 245 def hms(self, name): |
| 246 try: |
| 247 return self._hms[name.lower()] |
| 248 except KeyError: |
| 249 return None |
| 250 |
| 251 def ampm(self, name): |
| 252 try: |
| 253 return self._ampm[name.lower()] |
| 254 except KeyError: |
| 255 return None |
| 256 |
| 257 def pertain(self, name): |
| 258 return name.lower() in self._pertain |
| 259 |
| 260 def utczone(self, name): |
| 261 return name.lower() in self._utczone |
| 262 |
| 263 def tzoffset(self, name): |
| 264 if name in self._utczone: |
| 265 return 0 |
| 266 return self.TZOFFSET.get(name) |
| 267 |
| 268 def convertyear(self, year): |
| 269 if year < 100: |
| 270 year += self._century |
| 271 if abs(year-self._year) >= 50: |
| 272 if year < self._year: |
| 273 year += 100 |
| 274 else: |
| 275 year -= 100 |
| 276 return year |
| 277 |
| 278 def validate(self, res): |
| 279 # move to info |
| 280 if res.year is not None: |
| 281 res.year = self.convertyear(res.year) |
| 282 if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z': |
| 283 res.tzname = "UTC" |
| 284 res.tzoffset = 0 |
| 285 elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname): |
| 286 res.tzoffset = 0 |
| 287 return True |
| 288 |
| 289 |
| 290 class parser(object): |
| 291 |
| 292 def __init__(self, info=None): |
| 293 self.info = info or parserinfo() |
| 294 |
| 295 def parse(self, timestr, default=None, |
| 296 ignoretz=False, tzinfos=None, |
| 297 **kwargs): |
| 298 if not default: |
| 299 default = datetime.datetime.now().replace(hour=0, minute=0, |
| 300 second=0, microsecond=0) |
| 301 res = self._parse(timestr, **kwargs) |
| 302 if res is None: |
| 303 raise ValueError, "unknown string format" |
| 304 repl = {} |
| 305 for attr in ["year", "month", "day", "hour", |
| 306 "minute", "second", "microsecond"]: |
| 307 value = getattr(res, attr) |
| 308 if value is not None: |
| 309 repl[attr] = value |
| 310 ret = default.replace(**repl) |
| 311 if res.weekday is not None and not res.day: |
| 312 ret = ret+relativedelta.relativedelta(weekday=res.weekday) |
| 313 if not ignoretz: |
| 314 if callable(tzinfos) or tzinfos and res.tzname in tzinfos: |
| 315 if callable(tzinfos): |
| 316 tzdata = tzinfos(res.tzname, res.tzoffset) |
| 317 else: |
| 318 tzdata = tzinfos.get(res.tzname) |
| 319 if isinstance(tzdata, datetime.tzinfo): |
| 320 tzinfo = tzdata |
| 321 elif isinstance(tzdata, basestring): |
| 322 tzinfo = tz.tzstr(tzdata) |
| 323 elif isinstance(tzdata, int): |
| 324 tzinfo = tz.tzoffset(res.tzname, tzdata) |
| 325 else: |
| 326 raise ValueError, "offset must be tzinfo subclass, " \ |
| 327 "tz string, or int offset" |
| 328 ret = ret.replace(tzinfo=tzinfo) |
| 329 elif res.tzname and res.tzname in time.tzname: |
| 330 ret = ret.replace(tzinfo=tz.tzlocal()) |
| 331 elif res.tzoffset == 0: |
| 332 ret = ret.replace(tzinfo=tz.tzutc()) |
| 333 elif res.tzoffset: |
| 334 ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset)) |
| 335 return ret |
| 336 |
| 337 class _result(_resultbase): |
| 338 __slots__ = ["year", "month", "day", "weekday", |
| 339 "hour", "minute", "second", "microsecond", |
| 340 "tzname", "tzoffset"] |
| 341 |
| 342 def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False): |
| 343 info = self.info |
| 344 if dayfirst is None: |
| 345 dayfirst = info.dayfirst |
| 346 if yearfirst is None: |
| 347 yearfirst = info.yearfirst |
| 348 res = self._result() |
| 349 l = _timelex.split(timestr) |
| 350 try: |
| 351 |
| 352 # year/month/day list |
| 353 ymd = [] |
| 354 |
| 355 # Index of the month string in ymd |
| 356 mstridx = -1 |
| 357 |
| 358 len_l = len(l) |
| 359 i = 0 |
| 360 while i < len_l: |
| 361 |
| 362 # Check if it's a number |
| 363 try: |
| 364 value_repr = l[i] |
| 365 value = float(value_repr) |
| 366 except ValueError: |
| 367 value = None |
| 368 |
| 369 if value is not None: |
| 370 # Token is a number |
| 371 len_li = len(l[i]) |
| 372 i += 1 |
| 373 if (len(ymd) == 3 and len_li in (2, 4) |
| 374 and (i >= len_l or (l[i] != ':' and |
| 375 info.hms(l[i]) is None))): |
| 376 # 19990101T23[59] |
| 377 s = l[i-1] |
| 378 res.hour = int(s[:2]) |
| 379 if len_li == 4: |
| 380 res.minute = int(s[2:]) |
| 381 elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6): |
| 382 # YYMMDD or HHMMSS[.ss] |
| 383 s = l[i-1] |
| 384 if not ymd and l[i-1].find('.') == -1: |
| 385 ymd.append(info.convertyear(int(s[:2]))) |
| 386 ymd.append(int(s[2:4])) |
| 387 ymd.append(int(s[4:])) |
| 388 else: |
| 389 # 19990101T235959[.59] |
| 390 res.hour = int(s[:2]) |
| 391 res.minute = int(s[2:4]) |
| 392 res.second, res.microsecond = _parsems(s[4:]) |
| 393 elif len_li == 8: |
| 394 # YYYYMMDD |
| 395 s = l[i-1] |
| 396 ymd.append(int(s[:4])) |
| 397 ymd.append(int(s[4:6])) |
| 398 ymd.append(int(s[6:])) |
| 399 elif len_li in (12, 14): |
| 400 # YYYYMMDDhhmm[ss] |
| 401 s = l[i-1] |
| 402 ymd.append(int(s[:4])) |
| 403 ymd.append(int(s[4:6])) |
| 404 ymd.append(int(s[6:8])) |
| 405 res.hour = int(s[8:10]) |
| 406 res.minute = int(s[10:12]) |
| 407 if len_li == 14: |
| 408 res.second = int(s[12:]) |
| 409 elif ((i < len_l and info.hms(l[i]) is not None) or |
| 410 (i+1 < len_l and l[i] == ' ' and |
| 411 info.hms(l[i+1]) is not None)): |
| 412 # HH[ ]h or MM[ ]m or SS[.ss][ ]s |
| 413 if l[i] == ' ': |
| 414 i += 1 |
| 415 idx = info.hms(l[i]) |
| 416 while True: |
| 417 if idx == 0: |
| 418 res.hour = int(value) |
| 419 if value%1: |
| 420 res.minute = int(60*(value%1)) |
| 421 elif idx == 1: |
| 422 res.minute = int(value) |
| 423 if value%1: |
| 424 res.second = int(60*(value%1)) |
| 425 elif idx == 2: |
| 426 res.second, res.microsecond = \ |
| 427 _parsems(value_repr) |
| 428 i += 1 |
| 429 if i >= len_l or idx == 2: |
| 430 break |
| 431 # 12h00 |
| 432 try: |
| 433 value_repr = l[i] |
| 434 value = float(value_repr) |
| 435 except ValueError: |
| 436 break |
| 437 else: |
| 438 i += 1 |
| 439 idx += 1 |
| 440 if i < len_l: |
| 441 newidx = info.hms(l[i]) |
| 442 if newidx is not None: |
| 443 idx = newidx |
| 444 elif i+1 < len_l and l[i] == ':': |
| 445 # HH:MM[:SS[.ss]] |
| 446 res.hour = int(value) |
| 447 i += 1 |
| 448 value = float(l[i]) |
| 449 res.minute = int(value) |
| 450 if value%1: |
| 451 res.second = int(60*(value%1)) |
| 452 i += 1 |
| 453 if i < len_l and l[i] == ':': |
| 454 res.second, res.microsecond = _parsems(l[i+1]) |
| 455 i += 2 |
| 456 elif i < len_l and l[i] in ('-', '/', '.'): |
| 457 sep = l[i] |
| 458 ymd.append(int(value)) |
| 459 i += 1 |
| 460 if i < len_l and not info.jump(l[i]): |
| 461 try: |
| 462 # 01-01[-01] |
| 463 ymd.append(int(l[i])) |
| 464 except ValueError: |
| 465 # 01-Jan[-01] |
| 466 value = info.month(l[i]) |
| 467 if value is not None: |
| 468 ymd.append(value) |
| 469 assert mstridx == -1 |
| 470 mstridx = len(ymd)-1 |
| 471 else: |
| 472 return None |
| 473 i += 1 |
| 474 if i < len_l and l[i] == sep: |
| 475 # We have three members |
| 476 i += 1 |
| 477 value = info.month(l[i]) |
| 478 if value is not None: |
| 479 ymd.append(value) |
| 480 mstridx = len(ymd)-1 |
| 481 assert mstridx == -1 |
| 482 else: |
| 483 ymd.append(int(l[i])) |
| 484 i += 1 |
| 485 elif i >= len_l or info.jump(l[i]): |
| 486 if i+1 < len_l and info.ampm(l[i+1]) is not None: |
| 487 # 12 am |
| 488 res.hour = int(value) |
| 489 if res.hour < 12 and info.ampm(l[i+1]) == 1: |
| 490 res.hour += 12 |
| 491 elif res.hour == 12 and info.ampm(l[i+1]) == 0: |
| 492 res.hour = 0 |
| 493 i += 1 |
| 494 else: |
| 495 # Year, month or day |
| 496 ymd.append(int(value)) |
| 497 i += 1 |
| 498 elif info.ampm(l[i]) is not None: |
| 499 # 12am |
| 500 res.hour = int(value) |
| 501 if res.hour < 12 and info.ampm(l[i]) == 1: |
| 502 res.hour += 12 |
| 503 elif res.hour == 12 and info.ampm(l[i]) == 0: |
| 504 res.hour = 0 |
| 505 i += 1 |
| 506 elif not fuzzy: |
| 507 return None |
| 508 else: |
| 509 i += 1 |
| 510 continue |
| 511 |
| 512 # Check weekday |
| 513 value = info.weekday(l[i]) |
| 514 if value is not None: |
| 515 res.weekday = value |
| 516 i += 1 |
| 517 continue |
| 518 |
| 519 # Check month name |
| 520 value = info.month(l[i]) |
| 521 if value is not None: |
| 522 ymd.append(value) |
| 523 assert mstridx == -1 |
| 524 mstridx = len(ymd)-1 |
| 525 i += 1 |
| 526 if i < len_l: |
| 527 if l[i] in ('-', '/'): |
| 528 # Jan-01[-99] |
| 529 sep = l[i] |
| 530 i += 1 |
| 531 ymd.append(int(l[i])) |
| 532 i += 1 |
| 533 if i < len_l and l[i] == sep: |
| 534 # Jan-01-99 |
| 535 i += 1 |
| 536 ymd.append(int(l[i])) |
| 537 i += 1 |
| 538 elif (i+3 < len_l and l[i] == l[i+2] == ' ' |
| 539 and info.pertain(l[i+1])): |
| 540 # Jan of 01 |
| 541 # In this case, 01 is clearly year |
| 542 try: |
| 543 value = int(l[i+3]) |
| 544 except ValueError: |
| 545 # Wrong guess |
| 546 pass |
| 547 else: |
| 548 # Convert it here to become unambiguous |
| 549 ymd.append(info.convertyear(value)) |
| 550 i += 4 |
| 551 continue |
| 552 |
| 553 # Check am/pm |
| 554 value = info.ampm(l[i]) |
| 555 if value is not None: |
| 556 if value == 1 and res.hour < 12: |
| 557 res.hour += 12 |
| 558 elif value == 0 and res.hour == 12: |
| 559 res.hour = 0 |
| 560 i += 1 |
| 561 continue |
| 562 |
| 563 # Check for a timezone name |
| 564 if (res.hour is not None and len(l[i]) <= 5 and |
| 565 res.tzname is None and res.tzoffset is None and |
| 566 not [x for x in l[i] if x not in string.ascii_uppercase]): |
| 567 res.tzname = l[i] |
| 568 res.tzoffset = info.tzoffset(res.tzname) |
| 569 i += 1 |
| 570 |
| 571 # Check for something like GMT+3, or BRST+3. Notice |
| 572 # that it doesn't mean "I am 3 hours after GMT", but |
| 573 # "my time +3 is GMT". If found, we reverse the |
| 574 # logic so that timezone parsing code will get it |
| 575 # right. |
| 576 if i < len_l and l[i] in ('+', '-'): |
| 577 l[i] = ('+', '-')[l[i] == '+'] |
| 578 res.tzoffset = None |
| 579 if info.utczone(res.tzname): |
| 580 # With something like GMT+3, the timezone |
| 581 # is *not* GMT. |
| 582 res.tzname = None |
| 583 |
| 584 continue |
| 585 |
| 586 # Check for a numbered timezone |
| 587 if res.hour is not None and l[i] in ('+', '-'): |
| 588 signal = (-1,1)[l[i] == '+'] |
| 589 i += 1 |
| 590 len_li = len(l[i]) |
| 591 if len_li == 4: |
| 592 # -0300 |
| 593 res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60 |
| 594 elif i+1 < len_l and l[i+1] == ':': |
| 595 # -03:00 |
| 596 res.tzoffset = int(l[i])*3600+int(l[i+2])*60 |
| 597 i += 2 |
| 598 elif len_li <= 2: |
| 599 # -[0]3 |
| 600 res.tzoffset = int(l[i][:2])*3600 |
| 601 else: |
| 602 return None |
| 603 i += 1 |
| 604 res.tzoffset *= signal |
| 605 |
| 606 # Look for a timezone name between parenthesis |
| 607 if (i+3 < len_l and |
| 608 info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and |
| 609 3 <= len(l[i+2]) <= 5 and |
| 610 not [x for x in l[i+2] |
| 611 if x not in string.ascii_uppercase]): |
| 612 # -0300 (BRST) |
| 613 res.tzname = l[i+2] |
| 614 i += 4 |
| 615 continue |
| 616 |
| 617 # Check jumps |
| 618 if not (info.jump(l[i]) or fuzzy): |
| 619 return None |
| 620 |
| 621 i += 1 |
| 622 |
| 623 # Process year/month/day |
| 624 len_ymd = len(ymd) |
| 625 if len_ymd > 3: |
| 626 # More than three members!? |
| 627 return None |
| 628 elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2): |
| 629 # One member, or two members with a month string |
| 630 if mstridx != -1: |
| 631 res.month = ymd[mstridx] |
| 632 del ymd[mstridx] |
| 633 if len_ymd > 1 or mstridx == -1: |
| 634 if ymd[0] > 31: |
| 635 res.year = ymd[0] |
| 636 else: |
| 637 res.day = ymd[0] |
| 638 elif len_ymd == 2: |
| 639 # Two members with numbers |
| 640 if ymd[0] > 31: |
| 641 # 99-01 |
| 642 res.year, res.month = ymd |
| 643 elif ymd[1] > 31: |
| 644 # 01-99 |
| 645 res.month, res.year = ymd |
| 646 elif dayfirst and ymd[1] <= 12: |
| 647 # 13-01 |
| 648 res.day, res.month = ymd |
| 649 else: |
| 650 # 01-13 |
| 651 res.month, res.day = ymd |
| 652 if len_ymd == 3: |
| 653 # Three members |
| 654 if mstridx == 0: |
| 655 res.month, res.day, res.year = ymd |
| 656 elif mstridx == 1: |
| 657 if ymd[0] > 31 or (yearfirst and ymd[2] <= 31): |
| 658 # 99-Jan-01 |
| 659 res.year, res.month, res.day = ymd |
| 660 else: |
| 661 # 01-Jan-01 |
| 662 # Give precendence to day-first, since |
| 663 # two-digit years is usually hand-written. |
| 664 res.day, res.month, res.year = ymd |
| 665 elif mstridx == 2: |
| 666 # WTF!? |
| 667 if ymd[1] > 31: |
| 668 # 01-99-Jan |
| 669 res.day, res.year, res.month = ymd |
| 670 else: |
| 671 # 99-01-Jan |
| 672 res.year, res.day, res.month = ymd |
| 673 else: |
| 674 if ymd[0] > 31 or \ |
| 675 (yearfirst and ymd[1] <= 12 and ymd[2] <= 31): |
| 676 # 99-01-01 |
| 677 res.year, res.month, res.day = ymd |
| 678 elif ymd[0] > 12 or (dayfirst and ymd[1] <= 12): |
| 679 # 13-01-01 |
| 680 res.day, res.month, res.year = ymd |
| 681 else: |
| 682 # 01-13-01 |
| 683 res.month, res.day, res.year = ymd |
| 684 |
| 685 except (IndexError, ValueError, AssertionError): |
| 686 return None |
| 687 |
| 688 if not info.validate(res): |
| 689 return None |
| 690 return res |
| 691 |
| 692 DEFAULTPARSER = parser() |
| 693 def parse(timestr, parserinfo=None, **kwargs): |
| 694 if parserinfo: |
| 695 return parser(parserinfo).parse(timestr, **kwargs) |
| 696 else: |
| 697 return DEFAULTPARSER.parse(timestr, **kwargs) |
| 698 |
| 699 |
| 700 class _tzparser(object): |
| 701 |
| 702 class _result(_resultbase): |
| 703 |
| 704 __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset", |
| 705 "start", "end"] |
| 706 |
| 707 class _attr(_resultbase): |
| 708 __slots__ = ["month", "week", "weekday", |
| 709 "yday", "jyday", "day", "time"] |
| 710 |
| 711 def __repr__(self): |
| 712 return self._repr("") |
| 713 |
| 714 def __init__(self): |
| 715 _resultbase.__init__(self) |
| 716 self.start = self._attr() |
| 717 self.end = self._attr() |
| 718 |
| 719 def parse(self, tzstr): |
| 720 res = self._result() |
| 721 l = _timelex.split(tzstr) |
| 722 try: |
| 723 |
| 724 len_l = len(l) |
| 725 |
| 726 i = 0 |
| 727 while i < len_l: |
| 728 # BRST+3[BRDT[+2]] |
| 729 j = i |
| 730 while j < len_l and not [x for x in l[j] |
| 731 if x in "0123456789:,-+"]: |
| 732 j += 1 |
| 733 if j != i: |
| 734 if not res.stdabbr: |
| 735 offattr = "stdoffset" |
| 736 res.stdabbr = "".join(l[i:j]) |
| 737 else: |
| 738 offattr = "dstoffset" |
| 739 res.dstabbr = "".join(l[i:j]) |
| 740 i = j |
| 741 if (i < len_l and |
| 742 (l[i] in ('+', '-') or l[i][0] in "0123456789")): |
| 743 if l[i] in ('+', '-'): |
| 744 # Yes, that's right. See the TZ variable |
| 745 # documentation. |
| 746 signal = (1,-1)[l[i] == '+'] |
| 747 i += 1 |
| 748 else: |
| 749 signal = -1 |
| 750 len_li = len(l[i]) |
| 751 if len_li == 4: |
| 752 # -0300 |
| 753 setattr(res, offattr, |
| 754 (int(l[i][:2])*3600+int(l[i][2:])*60)*signal
) |
| 755 elif i+1 < len_l and l[i+1] == ':': |
| 756 # -03:00 |
| 757 setattr(res, offattr, |
| 758 (int(l[i])*3600+int(l[i+2])*60)*signal) |
| 759 i += 2 |
| 760 elif len_li <= 2: |
| 761 # -[0]3 |
| 762 setattr(res, offattr, |
| 763 int(l[i][:2])*3600*signal) |
| 764 else: |
| 765 return None |
| 766 i += 1 |
| 767 if res.dstabbr: |
| 768 break |
| 769 else: |
| 770 break |
| 771 |
| 772 if i < len_l: |
| 773 for j in range(i, len_l): |
| 774 if l[j] == ';': l[j] = ',' |
| 775 |
| 776 assert l[i] == ',' |
| 777 |
| 778 i += 1 |
| 779 |
| 780 if i >= len_l: |
| 781 pass |
| 782 elif (8 <= l.count(',') <= 9 and |
| 783 not [y for x in l[i:] if x != ',' |
| 784 for y in x if y not in "0123456789"]): |
| 785 # GMT0BST,3,0,30,3600,10,0,26,7200[,3600] |
| 786 for x in (res.start, res.end): |
| 787 x.month = int(l[i]) |
| 788 i += 2 |
| 789 if l[i] == '-': |
| 790 value = int(l[i+1])*-1 |
| 791 i += 1 |
| 792 else: |
| 793 value = int(l[i]) |
| 794 i += 2 |
| 795 if value: |
| 796 x.week = value |
| 797 x.weekday = (int(l[i])-1)%7 |
| 798 else: |
| 799 x.day = int(l[i]) |
| 800 i += 2 |
| 801 x.time = int(l[i]) |
| 802 i += 2 |
| 803 if i < len_l: |
| 804 if l[i] in ('-','+'): |
| 805 signal = (-1,1)[l[i] == "+"] |
| 806 i += 1 |
| 807 else: |
| 808 signal = 1 |
| 809 res.dstoffset = (res.stdoffset+int(l[i]))*signal |
| 810 elif (l.count(',') == 2 and l[i:].count('/') <= 2 and |
| 811 not [y for x in l[i:] if x not in (',','/','J','M', |
| 812 '.','-',':') |
| 813 for y in x if y not in "0123456789"]): |
| 814 for x in (res.start, res.end): |
| 815 if l[i] == 'J': |
| 816 # non-leap year day (1 based) |
| 817 i += 1 |
| 818 x.jyday = int(l[i]) |
| 819 elif l[i] == 'M': |
| 820 # month[-.]week[-.]weekday |
| 821 i += 1 |
| 822 x.month = int(l[i]) |
| 823 i += 1 |
| 824 assert l[i] in ('-', '.') |
| 825 i += 1 |
| 826 x.week = int(l[i]) |
| 827 if x.week == 5: |
| 828 x.week = -1 |
| 829 i += 1 |
| 830 assert l[i] in ('-', '.') |
| 831 i += 1 |
| 832 x.weekday = (int(l[i])-1)%7 |
| 833 else: |
| 834 # year day (zero based) |
| 835 x.yday = int(l[i])+1 |
| 836 |
| 837 i += 1 |
| 838 |
| 839 if i < len_l and l[i] == '/': |
| 840 i += 1 |
| 841 # start time |
| 842 len_li = len(l[i]) |
| 843 if len_li == 4: |
| 844 # -0300 |
| 845 x.time = (int(l[i][:2])*3600+int(l[i][2:])*60) |
| 846 elif i+1 < len_l and l[i+1] == ':': |
| 847 # -03:00 |
| 848 x.time = int(l[i])*3600+int(l[i+2])*60 |
| 849 i += 2 |
| 850 if i+1 < len_l and l[i+1] == ':': |
| 851 i += 2 |
| 852 x.time += int(l[i]) |
| 853 elif len_li <= 2: |
| 854 # -[0]3 |
| 855 x.time = (int(l[i][:2])*3600) |
| 856 else: |
| 857 return None |
| 858 i += 1 |
| 859 |
| 860 assert i == len_l or l[i] == ',' |
| 861 |
| 862 i += 1 |
| 863 |
| 864 assert i >= len_l |
| 865 |
| 866 except (IndexError, ValueError, AssertionError): |
| 867 return None |
| 868 |
| 869 return res |
| 870 |
| 871 |
| 872 DEFAULTTZPARSER = _tzparser() |
| 873 def _parsetz(tzstr): |
| 874 return DEFAULTTZPARSER.parse(tzstr) |
| 875 |
| 876 |
| 877 def _parsems(value): |
| 878 """Parse a I[.F] seconds value into (seconds, microseconds).""" |
| 879 if "." not in value: |
| 880 return int(value), 0 |
| 881 else: |
| 882 i, f = value.split(".") |
| 883 return int(i), int(f.ljust(6, "0")[:6]) |
| 884 |
| 885 |
| 886 # vim:ts=4:sw=4:et |
OLD | NEW |