parser.py 32 KB

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