parser.py 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205
  1. # -*- coding:iso-8859-1 -*-
  2. """
  3. This module offers a generic date/time string parser which is able to parse
  4. most known formats to represent a date and/or time.
  5. Additional resources about date/time string formats can be found below:
  6. - `A summary of the international standard date and time notation
  7. <http://www.cl.cam.ac.uk/~mgk25/iso-time.html>`_
  8. - `W3C Date and Time Formats <http://www.w3.org/TR/NOTE-datetime>`_
  9. - `Time Formats (Planetary Rings Node) <http://pds-rings.seti.org/tools/time_formats.html>`_
  10. - `CPAN ParseDate module
  11. <http://search.cpan.org/~muir/Time-modules-2013.0912/lib/Time/ParseDate.pm>`_
  12. - `Java SimpleDateFormat Class
  13. <https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>`_
  14. """
  15. from __future__ import unicode_literals
  16. import datetime
  17. import string
  18. import time
  19. import collections
  20. from io import StringIO
  21. from six import text_type, binary_type, integer_types
  22. from . import relativedelta
  23. from . import tz
  24. __all__ = ["parse", "parserinfo"]
  25. class _timelex(object):
  26. def __init__(self, instream):
  27. if isinstance(instream, text_type):
  28. instream = StringIO(instream)
  29. self.instream = instream
  30. self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
  31. 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'
  32. 'ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
  33. 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
  34. self.numchars = '0123456789'
  35. self.whitespace = ' \t\r\n'
  36. self.charstack = []
  37. self.tokenstack = []
  38. self.eof = False
  39. def get_token(self):
  40. """
  41. This function breaks the time string into lexical units (tokens), which
  42. can be parsed by the parser. Lexical units are demarcated by changes in
  43. the character set, so any continuous string of letters is considered one
  44. unit, any continuous string of numbers is considered one unit.
  45. The main complication arises from the fact that dots ('.') can be used
  46. both as separators (e.g. "Sep.20.2009") or decimal points (e.g.
  47. "4:30:21.447"). As such, it is necessary to read the full context of
  48. any dot-separated strings before breaking it into tokens; as such, this
  49. function maintains a "token stack", for when the ambiguous context
  50. demands that multiple tokens be parsed at once.
  51. """
  52. if self.tokenstack:
  53. return self.tokenstack.pop(0)
  54. seenletters = False
  55. token = None
  56. state = None
  57. wordchars = self.wordchars
  58. numchars = self.numchars
  59. whitespace = self.whitespace
  60. while not self.eof:
  61. # We only realize that we've reached the end of a token when we find
  62. # a character that's not part of the current token - since that
  63. # character may be part of the next token, it's stored in the
  64. # charstack.
  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. # First character of the token - determines if we're starting
  76. # to parse a word, a number or something else.
  77. token = nextchar
  78. if nextchar in wordchars:
  79. state = 'a'
  80. elif nextchar in numchars:
  81. state = '0'
  82. elif nextchar in whitespace:
  83. token = ' '
  84. break # emit token
  85. else:
  86. break # emit token
  87. elif state == 'a':
  88. # If we've already started reading a word, we keep reading
  89. # letters until we find something that's not part of a word.
  90. seenletters = True
  91. if nextchar in wordchars:
  92. token += nextchar
  93. elif nextchar == '.':
  94. token += nextchar
  95. state = 'a.'
  96. else:
  97. self.charstack.append(nextchar)
  98. break # emit token
  99. elif state == '0':
  100. # If we've already started reading a number, we keep reading
  101. # numbers until we find something that doesn't fit.
  102. if nextchar in numchars:
  103. token += nextchar
  104. elif nextchar == '.':
  105. token += nextchar
  106. state = '0.'
  107. else:
  108. self.charstack.append(nextchar)
  109. break # emit token
  110. elif state == 'a.':
  111. # If we've seen some letters and a dot separator, continue
  112. # parsing, and the tokens will be broken up later.
  113. seenletters = True
  114. if nextchar == '.' or nextchar in wordchars:
  115. token += nextchar
  116. elif nextchar in numchars and token[-1] == '.':
  117. token += nextchar
  118. state = '0.'
  119. else:
  120. self.charstack.append(nextchar)
  121. break # emit token
  122. elif state == '0.':
  123. # If we've seen at least one dot separator, keep going, we'll
  124. # break up the tokens later.
  125. if nextchar == '.' or nextchar in numchars:
  126. token += nextchar
  127. elif nextchar in wordchars and token[-1] == '.':
  128. token += nextchar
  129. state = 'a.'
  130. else:
  131. self.charstack.append(nextchar)
  132. break # emit token
  133. if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or
  134. token[-1] == '.')):
  135. l = token.split('.')
  136. token = l[0]
  137. for tok in l[1:]:
  138. self.tokenstack.append('.')
  139. if tok:
  140. self.tokenstack.append(tok)
  141. return token
  142. def __iter__(self):
  143. return self
  144. def __next__(self):
  145. token = self.get_token()
  146. if token is None:
  147. raise StopIteration
  148. return token
  149. def next(self):
  150. return self.__next__() # Python 2.x support
  151. def split(cls, s):
  152. return list(cls(s))
  153. split = classmethod(split)
  154. class _resultbase(object):
  155. def __init__(self):
  156. for attr in self.__slots__:
  157. setattr(self, attr, None)
  158. def _repr(self, classname):
  159. l = []
  160. for attr in self.__slots__:
  161. value = getattr(self, attr)
  162. if value is not None:
  163. l.append("%s=%s" % (attr, repr(value)))
  164. return "%s(%s)" % (classname, ", ".join(l))
  165. def __repr__(self):
  166. return self._repr(self.__class__.__name__)
  167. class parserinfo(object):
  168. """
  169. Class which handles what inputs are accepted. Subclass this to customize the
  170. language and acceptable values for each parameter.
  171. :param dayfirst:
  172. Whether to interpret the first value in an ambiguous 3-integer date
  173. (e.g. 01/05/09) as the day (`True`) or month (`False`). If
  174. `yearfirst` is set to `True`, this distinguishes between YDM and
  175. YMD. Default is `False`.
  176. :param yearfirst:
  177. Whether to interpret the first value in an ambiguous 3-integer date
  178. (e.g. 01/05/09) as the year. If `True`, the first number is taken to
  179. be the year, otherwise the last number is taken to be the year.
  180. Default is `False`.
  181. """
  182. # m from a.m/p.m, t from ISO T separator
  183. JUMP = [" ", ".", ",", ";", "-", "/", "'",
  184. "at", "on", "and", "ad", "m", "t", "of",
  185. "st", "nd", "rd", "th"]
  186. WEEKDAYS = [("Mon", "Monday"),
  187. ("Tue", "Tuesday"),
  188. ("Wed", "Wednesday"),
  189. ("Thu", "Thursday"),
  190. ("Fri", "Friday"),
  191. ("Sat", "Saturday"),
  192. ("Sun", "Sunday")]
  193. MONTHS = [("Jan", "January"),
  194. ("Feb", "February"),
  195. ("Mar", "March"),
  196. ("Apr", "April"),
  197. ("May", "May"),
  198. ("Jun", "June"),
  199. ("Jul", "July"),
  200. ("Aug", "August"),
  201. ("Sep", "Sept", "September"),
  202. ("Oct", "October"),
  203. ("Nov", "November"),
  204. ("Dec", "December")]
  205. HMS = [("h", "hour", "hours"),
  206. ("m", "minute", "minutes"),
  207. ("s", "second", "seconds")]
  208. AMPM = [("am", "a"),
  209. ("pm", "p")]
  210. UTCZONE = ["UTC", "GMT", "Z"]
  211. PERTAIN = ["of"]
  212. TZOFFSET = {}
  213. def __init__(self, dayfirst=False, yearfirst=False):
  214. self._jump = self._convert(self.JUMP)
  215. self._weekdays = self._convert(self.WEEKDAYS)
  216. self._months = self._convert(self.MONTHS)
  217. self._hms = self._convert(self.HMS)
  218. self._ampm = self._convert(self.AMPM)
  219. self._utczone = self._convert(self.UTCZONE)
  220. self._pertain = self._convert(self.PERTAIN)
  221. self.dayfirst = dayfirst
  222. self.yearfirst = yearfirst
  223. self._year = time.localtime().tm_year
  224. self._century = self._year // 100*100
  225. def _convert(self, lst):
  226. dct = {}
  227. for i, v in enumerate(lst):
  228. if isinstance(v, tuple):
  229. for v in v:
  230. dct[v.lower()] = i
  231. else:
  232. dct[v.lower()] = i
  233. return dct
  234. def jump(self, name):
  235. return name.lower() in self._jump
  236. def weekday(self, name):
  237. if len(name) >= 3:
  238. try:
  239. return self._weekdays[name.lower()]
  240. except KeyError:
  241. pass
  242. return None
  243. def month(self, name):
  244. if len(name) >= 3:
  245. try:
  246. return self._months[name.lower()]+1
  247. except KeyError:
  248. pass
  249. return None
  250. def hms(self, name):
  251. try:
  252. return self._hms[name.lower()]
  253. except KeyError:
  254. return None
  255. def ampm(self, name):
  256. try:
  257. return self._ampm[name.lower()]
  258. except KeyError:
  259. return None
  260. def pertain(self, name):
  261. return name.lower() in self._pertain
  262. def utczone(self, name):
  263. return name.lower() in self._utczone
  264. def tzoffset(self, name):
  265. if name in self._utczone:
  266. return 0
  267. return self.TZOFFSET.get(name)
  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. def validate(self, res):
  278. # move to info
  279. if res.year is not None:
  280. res.year = self.convertyear(res.year)
  281. if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z':
  282. res.tzname = "UTC"
  283. res.tzoffset = 0
  284. elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
  285. res.tzoffset = 0
  286. return True
  287. class parser(object):
  288. def __init__(self, info=None):
  289. self.info = info or parserinfo()
  290. def parse(self, timestr, default=None, ignoretz=False, tzinfos=None,
  291. **kwargs):
  292. """
  293. Parse the date/time string into a datetime object.
  294. :param timestr:
  295. Any date/time string using the supported formats.
  296. :param default:
  297. The default datetime object, if this is a datetime object and not
  298. `None`, elements specified in `timestr` replace elements in the
  299. default object.
  300. :param ignoretz:
  301. Whether or not to ignore the time zone.
  302. :param tzinfos:
  303. A time zone, to be applied to the date, if `ignoretz` is `True`.
  304. This can be either a subclass of `tzinfo`, a time zone string or an
  305. integer offset.
  306. :param **kwargs:
  307. Keyword arguments as passed to `_parse()`.
  308. :return:
  309. Returns a `datetime.datetime` object or, if the `fuzzy_with_tokens`
  310. option is `True`, returns a tuple, the first element being a
  311. `datetime.datetime` object, the second a tuple containing the
  312. fuzzy tokens.
  313. :raises ValueError:
  314. Raised for invalid or unknown string format, if the provided
  315. `tzinfo` is not in a valid format, or if an invalid date would
  316. be created.
  317. :raises OverFlowError:
  318. Raised if the parsed date exceeds the largest valid C integer on
  319. your system.
  320. """
  321. default_specified = default is not None
  322. if not default_specified:
  323. default = datetime.datetime.now().replace(hour=0, minute=0,
  324. second=0, microsecond=0)
  325. if kwargs.get('fuzzy_with_tokens', False):
  326. res, skipped_tokens = self._parse(timestr, **kwargs)
  327. else:
  328. res = self._parse(timestr, **kwargs)
  329. if res is None:
  330. raise ValueError("Unknown string format")
  331. repl = {}
  332. for attr in ["year", "month", "day", "hour",
  333. "minute", "second", "microsecond"]:
  334. value = getattr(res, attr)
  335. if value is not None:
  336. repl[attr] = value
  337. ret = default.replace(**repl)
  338. if res.weekday is not None and not res.day:
  339. ret = ret+relativedelta.relativedelta(weekday=res.weekday)
  340. if not ignoretz:
  341. if (isinstance(tzinfos, collections.Callable) or
  342. tzinfos and res.tzname in tzinfos):
  343. if isinstance(tzinfos, collections.Callable):
  344. tzdata = tzinfos(res.tzname, res.tzoffset)
  345. else:
  346. tzdata = tzinfos.get(res.tzname)
  347. if isinstance(tzdata, datetime.tzinfo):
  348. tzinfo = tzdata
  349. elif isinstance(tzdata, text_type):
  350. tzinfo = tz.tzstr(tzdata)
  351. elif isinstance(tzdata, integer_types):
  352. tzinfo = tz.tzoffset(res.tzname, tzdata)
  353. else:
  354. raise ValueError("Offset must be tzinfo subclass, "
  355. "tz string, or int offset.")
  356. ret = ret.replace(tzinfo=tzinfo)
  357. elif res.tzname and res.tzname in time.tzname:
  358. ret = ret.replace(tzinfo=tz.tzlocal())
  359. elif res.tzoffset == 0:
  360. ret = ret.replace(tzinfo=tz.tzutc())
  361. elif res.tzoffset:
  362. ret = ret.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
  363. if kwargs.get('fuzzy_with_tokens', False):
  364. return ret, skipped_tokens
  365. else:
  366. return ret
  367. class _result(_resultbase):
  368. __slots__ = ["year", "month", "day", "weekday",
  369. "hour", "minute", "second", "microsecond",
  370. "tzname", "tzoffset", "ampm"]
  371. def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False,
  372. fuzzy_with_tokens=False):
  373. """
  374. Private method which performs the heavy lifting of parsing, called from
  375. `parse()`, which passes on its `kwargs` to this function.
  376. :param timestr:
  377. The string to parse.
  378. :param dayfirst:
  379. Whether to interpret the first value in an ambiguous 3-integer date
  380. (e.g. 01/05/09) as the day (`True`) or month (`False`). If
  381. `yearfirst` is set to `True`, this distinguishes between YDM and
  382. YMD. If set to `None`, this value is retrieved from the current
  383. `parserinfo` object (which itself defaults to `False`).
  384. :param yearfirst:
  385. Whether to interpret the first value in an ambiguous 3-integer date
  386. (e.g. 01/05/09) as the year. If `True`, the first number is taken to
  387. be the year, otherwise the last number is taken to be the year. If
  388. this is set to `None`, the value is retrieved from the current
  389. `parserinfo` object (which itself defaults to `False`).
  390. :param fuzzy:
  391. Whether to allow fuzzy parsing, allowing for string like "Today is
  392. January 1, 2047 at 8:21:00AM".
  393. :param fuzzy_with_tokens:
  394. If `True`, `fuzzy` is automatically set to True, and the parser will
  395. return a tuple where the first element is the parsed
  396. `datetime.datetime` datetimestamp and the second element is a tuple
  397. containing the portions of the string which were ignored, e.g.
  398. "Today is January 1, 2047 at 8:21:00AM" should return
  399. `(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))`
  400. """
  401. if fuzzy_with_tokens:
  402. fuzzy = True
  403. info = self.info
  404. if dayfirst is None:
  405. dayfirst = info.dayfirst
  406. if yearfirst is None:
  407. yearfirst = info.yearfirst
  408. res = self._result()
  409. l = _timelex.split(timestr) # Splits the timestr into tokens
  410. # keep up with the last token skipped so we can recombine
  411. # consecutively skipped tokens (-2 for when i begins at 0).
  412. last_skipped_token_i = -2
  413. skipped_tokens = list()
  414. try:
  415. # year/month/day list
  416. ymd = []
  417. # Index of the month string in ymd
  418. mstridx = -1
  419. len_l = len(l)
  420. i = 0
  421. while i < len_l:
  422. # Check if it's a number
  423. try:
  424. value_repr = l[i]
  425. value = float(value_repr)
  426. except ValueError:
  427. value = None
  428. if value is not None:
  429. # Token is a number
  430. len_li = len(l[i])
  431. i += 1
  432. if (len(ymd) == 3 and len_li in (2, 4)
  433. and res.hour is None and (i >= len_l or (l[i] != ':' and
  434. info.hms(l[i]) is None))):
  435. # 19990101T23[59]
  436. s = l[i-1]
  437. res.hour = int(s[:2])
  438. if len_li == 4:
  439. res.minute = int(s[2:])
  440. elif len_li == 6 or (len_li > 6 and l[i-1].find('.') == 6):
  441. # YYMMDD or HHMMSS[.ss]
  442. s = l[i-1]
  443. if not ymd and l[i-1].find('.') == -1:
  444. ymd.append(info.convertyear(int(s[:2])))
  445. ymd.append(int(s[2:4]))
  446. ymd.append(int(s[4:]))
  447. else:
  448. # 19990101T235959[.59]
  449. res.hour = int(s[:2])
  450. res.minute = int(s[2:4])
  451. res.second, res.microsecond = _parsems(s[4:])
  452. elif len_li == 8:
  453. # YYYYMMDD
  454. s = l[i-1]
  455. ymd.append(int(s[:4]))
  456. ymd.append(int(s[4:6]))
  457. ymd.append(int(s[6:]))
  458. elif len_li in (12, 14):
  459. # YYYYMMDDhhmm[ss]
  460. s = l[i-1]
  461. ymd.append(int(s[:4]))
  462. ymd.append(int(s[4:6]))
  463. ymd.append(int(s[6:8]))
  464. res.hour = int(s[8:10])
  465. res.minute = int(s[10:12])
  466. if len_li == 14:
  467. res.second = int(s[12:])
  468. elif ((i < len_l and info.hms(l[i]) is not None) or
  469. (i+1 < len_l and l[i] == ' ' and
  470. info.hms(l[i+1]) is not None)):
  471. # HH[ ]h or MM[ ]m or SS[.ss][ ]s
  472. if l[i] == ' ':
  473. i += 1
  474. idx = info.hms(l[i])
  475. while True:
  476. if idx == 0:
  477. res.hour = int(value)
  478. if value % 1:
  479. res.minute = int(60*(value % 1))
  480. elif idx == 1:
  481. res.minute = int(value)
  482. if value % 1:
  483. res.second = int(60*(value % 1))
  484. elif idx == 2:
  485. res.second, res.microsecond = \
  486. _parsems(value_repr)
  487. i += 1
  488. if i >= len_l or idx == 2:
  489. break
  490. # 12h00
  491. try:
  492. value_repr = l[i]
  493. value = float(value_repr)
  494. except ValueError:
  495. break
  496. else:
  497. i += 1
  498. idx += 1
  499. if i < len_l:
  500. newidx = info.hms(l[i])
  501. if newidx is not None:
  502. idx = newidx
  503. elif (i == len_l and l[i-2] == ' ' and
  504. info.hms(l[i-3]) is not None):
  505. # X h MM or X m SS
  506. idx = info.hms(l[i-3]) + 1
  507. if idx == 1:
  508. res.minute = int(value)
  509. if value % 1:
  510. res.second = int(60*(value % 1))
  511. elif idx == 2:
  512. res.second, res.microsecond = \
  513. _parsems(value_repr)
  514. i += 1
  515. elif i+1 < len_l and l[i] == ':':
  516. # HH:MM[:SS[.ss]]
  517. res.hour = int(value)
  518. i += 1
  519. value = float(l[i])
  520. res.minute = int(value)
  521. if value % 1:
  522. res.second = int(60*(value % 1))
  523. i += 1
  524. if i < len_l and l[i] == ':':
  525. res.second, res.microsecond = _parsems(l[i+1])
  526. i += 2
  527. elif i < len_l and l[i] in ('-', '/', '.'):
  528. sep = l[i]
  529. ymd.append(int(value))
  530. i += 1
  531. if i < len_l and not info.jump(l[i]):
  532. try:
  533. # 01-01[-01]
  534. ymd.append(int(l[i]))
  535. except ValueError:
  536. # 01-Jan[-01]
  537. value = info.month(l[i])
  538. if value is not None:
  539. ymd.append(value)
  540. assert mstridx == -1
  541. mstridx = len(ymd)-1
  542. else:
  543. return None
  544. i += 1
  545. if i < len_l and l[i] == sep:
  546. # We have three members
  547. i += 1
  548. value = info.month(l[i])
  549. if value is not None:
  550. ymd.append(value)
  551. mstridx = len(ymd)-1
  552. assert mstridx == -1
  553. else:
  554. ymd.append(int(l[i]))
  555. i += 1
  556. elif i >= len_l or info.jump(l[i]):
  557. if i+1 < len_l and info.ampm(l[i+1]) is not None:
  558. # 12 am
  559. res.hour = int(value)
  560. if res.hour < 12 and info.ampm(l[i+1]) == 1:
  561. res.hour += 12
  562. elif res.hour == 12 and info.ampm(l[i+1]) == 0:
  563. res.hour = 0
  564. i += 1
  565. else:
  566. # Year, month or day
  567. ymd.append(int(value))
  568. i += 1
  569. elif info.ampm(l[i]) is not None:
  570. # 12am
  571. res.hour = int(value)
  572. if res.hour < 12 and info.ampm(l[i]) == 1:
  573. res.hour += 12
  574. elif res.hour == 12 and info.ampm(l[i]) == 0:
  575. res.hour = 0
  576. i += 1
  577. elif not fuzzy:
  578. return None
  579. else:
  580. i += 1
  581. continue
  582. # Check weekday
  583. value = info.weekday(l[i])
  584. if value is not None:
  585. res.weekday = value
  586. i += 1
  587. continue
  588. # Check month name
  589. value = info.month(l[i])
  590. if value is not None:
  591. ymd.append(value)
  592. assert mstridx == -1
  593. mstridx = len(ymd)-1
  594. i += 1
  595. if i < len_l:
  596. if l[i] in ('-', '/'):
  597. # Jan-01[-99]
  598. sep = l[i]
  599. i += 1
  600. ymd.append(int(l[i]))
  601. i += 1
  602. if i < len_l and l[i] == sep:
  603. # Jan-01-99
  604. i += 1
  605. ymd.append(int(l[i]))
  606. i += 1
  607. elif (i+3 < len_l and l[i] == l[i+2] == ' '
  608. and info.pertain(l[i+1])):
  609. # Jan of 01
  610. # In this case, 01 is clearly year
  611. try:
  612. value = int(l[i+3])
  613. except ValueError:
  614. # Wrong guess
  615. pass
  616. else:
  617. # Convert it here to become unambiguous
  618. ymd.append(info.convertyear(value))
  619. i += 4
  620. continue
  621. # Check am/pm
  622. value = info.ampm(l[i])
  623. if value is not None:
  624. # For fuzzy parsing, 'a' or 'am' (both valid English words)
  625. # may erroneously trigger the AM/PM flag. Deal with that
  626. # here.
  627. val_is_ampm = True
  628. # If there's already an AM/PM flag, this one isn't one.
  629. if fuzzy and res.ampm is not None:
  630. val_is_ampm = False
  631. # If AM/PM is found and hour is not, raise a ValueError
  632. if res.hour is None:
  633. if fuzzy:
  634. val_is_ampm = False
  635. else:
  636. raise ValueError('No hour specified with ' +
  637. 'AM or PM flag.')
  638. elif not 0 <= res.hour <= 12:
  639. # If AM/PM is found, it's a 12 hour clock, so raise
  640. # an error for invalid range
  641. if fuzzy:
  642. val_is_ampm = False
  643. else:
  644. raise ValueError('Invalid hour specified for ' +
  645. '12-hour clock.')
  646. if val_is_ampm:
  647. if value == 1 and res.hour < 12:
  648. res.hour += 12
  649. elif value == 0 and res.hour == 12:
  650. res.hour = 0
  651. res.ampm = value
  652. i += 1
  653. continue
  654. # Check for a timezone name
  655. if (res.hour is not None and len(l[i]) <= 5 and
  656. res.tzname is None and res.tzoffset is None and
  657. not [x for x in l[i] if x not in
  658. string.ascii_uppercase]):
  659. res.tzname = l[i]
  660. res.tzoffset = info.tzoffset(res.tzname)
  661. i += 1
  662. # Check for something like GMT+3, or BRST+3. Notice
  663. # that it doesn't mean "I am 3 hours after GMT", but
  664. # "my time +3 is GMT". If found, we reverse the
  665. # logic so that timezone parsing code will get it
  666. # right.
  667. if i < len_l and l[i] in ('+', '-'):
  668. l[i] = ('+', '-')[l[i] == '+']
  669. res.tzoffset = None
  670. if info.utczone(res.tzname):
  671. # With something like GMT+3, the timezone
  672. # is *not* GMT.
  673. res.tzname = None
  674. continue
  675. # Check for a numbered timezone
  676. if res.hour is not None and l[i] in ('+', '-'):
  677. signal = (-1, 1)[l[i] == '+']
  678. i += 1
  679. len_li = len(l[i])
  680. if len_li == 4:
  681. # -0300
  682. res.tzoffset = int(l[i][:2])*3600+int(l[i][2:])*60
  683. elif i+1 < len_l and l[i+1] == ':':
  684. # -03:00
  685. res.tzoffset = int(l[i])*3600+int(l[i+2])*60
  686. i += 2
  687. elif len_li <= 2:
  688. # -[0]3
  689. res.tzoffset = int(l[i][:2])*3600
  690. else:
  691. return None
  692. i += 1
  693. res.tzoffset *= signal
  694. # Look for a timezone name between parenthesis
  695. if (i+3 < len_l and
  696. info.jump(l[i]) and l[i+1] == '(' and l[i+3] == ')' and
  697. 3 <= len(l[i+2]) <= 5 and
  698. not [x for x in l[i+2]
  699. if x not in string.ascii_uppercase]):
  700. # -0300 (BRST)
  701. res.tzname = l[i+2]
  702. i += 4
  703. continue
  704. # Check jumps
  705. if not (info.jump(l[i]) or fuzzy):
  706. return None
  707. if last_skipped_token_i == i - 1:
  708. # recombine the tokens
  709. skipped_tokens[-1] += l[i]
  710. else:
  711. # just append
  712. skipped_tokens.append(l[i])
  713. last_skipped_token_i = i
  714. i += 1
  715. # Process year/month/day
  716. len_ymd = len(ymd)
  717. if len_ymd > 3:
  718. # More than three members!?
  719. return None
  720. elif len_ymd == 1 or (mstridx != -1 and len_ymd == 2):
  721. # One member, or two members with a month string
  722. if mstridx != -1:
  723. res.month = ymd[mstridx]
  724. del ymd[mstridx]
  725. if len_ymd > 1 or mstridx == -1:
  726. if ymd[0] > 31:
  727. res.year = ymd[0]
  728. else:
  729. res.day = ymd[0]
  730. elif len_ymd == 2:
  731. # Two members with numbers
  732. if ymd[0] > 31:
  733. # 99-01
  734. res.year, res.month = ymd
  735. elif ymd[1] > 31:
  736. # 01-99
  737. res.month, res.year = ymd
  738. elif dayfirst and ymd[1] <= 12:
  739. # 13-01
  740. res.day, res.month = ymd
  741. else:
  742. # 01-13
  743. res.month, res.day = ymd
  744. elif len_ymd == 3:
  745. # Three members
  746. if mstridx == 0:
  747. res.month, res.day, res.year = ymd
  748. elif mstridx == 1:
  749. if ymd[0] > 31 or (yearfirst and ymd[2] <= 31):
  750. # 99-Jan-01
  751. res.year, res.month, res.day = ymd
  752. else:
  753. # 01-Jan-01
  754. # Give precendence to day-first, since
  755. # two-digit years is usually hand-written.
  756. res.day, res.month, res.year = ymd
  757. elif mstridx == 2:
  758. # WTF!?
  759. if ymd[1] > 31:
  760. # 01-99-Jan
  761. res.day, res.year, res.month = ymd
  762. else:
  763. # 99-01-Jan
  764. res.year, res.day, res.month = ymd
  765. else:
  766. if ymd[0] > 31 or \
  767. (yearfirst and ymd[1] <= 12 and ymd[2] <= 31):
  768. # 99-01-01
  769. res.year, res.month, res.day = ymd
  770. elif ymd[0] > 12 or (dayfirst and ymd[1] <= 12):
  771. # 13-01-01
  772. res.day, res.month, res.year = ymd
  773. else:
  774. # 01-13-01
  775. res.month, res.day, res.year = ymd
  776. except (IndexError, ValueError, AssertionError):
  777. return None
  778. if not info.validate(res):
  779. return None
  780. if fuzzy_with_tokens:
  781. return res, tuple(skipped_tokens)
  782. else:
  783. return res
  784. DEFAULTPARSER = parser()
  785. def parse(timestr, parserinfo=None, **kwargs):
  786. """
  787. Parse a string in one of the supported formats, using the `parserinfo`
  788. parameters.
  789. :param timestr:
  790. A string containing a date/time stamp.
  791. :param parserinfo:
  792. A :class:`parserinfo` object containing parameters for the parser.
  793. If `None`, the default arguments to the `parserinfo` constructor are
  794. used.
  795. The `**kwargs` parameter takes the following keyword arguments:
  796. :param default:
  797. The default datetime object, if this is a datetime object and not
  798. `None`, elements specified in `timestr` replace elements in the
  799. default object.
  800. :param ignoretz:
  801. Whether or not to ignore the time zone (boolean).
  802. :param tzinfos:
  803. A time zone, to be applied to the date, if `ignoretz` is `True`.
  804. This can be either a subclass of `tzinfo`, a time zone string or an
  805. integer offset.
  806. :param dayfirst:
  807. Whether to interpret the first value in an ambiguous 3-integer date
  808. (e.g. 01/05/09) as the day (`True`) or month (`False`). If
  809. `yearfirst` is set to `True`, this distinguishes between YDM and
  810. YMD. If set to `None`, this value is retrieved from the current
  811. :class:`parserinfo` object (which itself defaults to `False`).
  812. :param yearfirst:
  813. Whether to interpret the first value in an ambiguous 3-integer date
  814. (e.g. 01/05/09) as the year. If `True`, the first number is taken to
  815. be the year, otherwise the last number is taken to be the year. If
  816. this is set to `None`, the value is retrieved from the current
  817. :class:`parserinfo` object (which itself defaults to `False`).
  818. :param fuzzy:
  819. Whether to allow fuzzy parsing, allowing for string like "Today is
  820. January 1, 2047 at 8:21:00AM".
  821. :param fuzzy_with_tokens:
  822. If `True`, `fuzzy` is automatically set to True, and the parser will
  823. return a tuple where the first element is the parsed
  824. `datetime.datetime` datetimestamp and the second element is a tuple
  825. containing the portions of the string which were ignored, e.g.
  826. "Today is January 1, 2047 at 8:21:00AM" should return
  827. `(datetime.datetime(2011, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))`
  828. """
  829. # Python 2.x support: datetimes return their string presentation as
  830. # bytes in 2.x and unicode in 3.x, so it's reasonable to expect that
  831. # the parser will get both kinds. Internally we use unicode only.
  832. if isinstance(timestr, binary_type):
  833. timestr = timestr.decode()
  834. if parserinfo:
  835. return parser(parserinfo).parse(timestr, **kwargs)
  836. else:
  837. return DEFAULTPARSER.parse(timestr, **kwargs)
  838. class _tzparser(object):
  839. class _result(_resultbase):
  840. __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset",
  841. "start", "end"]
  842. class _attr(_resultbase):
  843. __slots__ = ["month", "week", "weekday",
  844. "yday", "jyday", "day", "time"]
  845. def __repr__(self):
  846. return self._repr("")
  847. def __init__(self):
  848. _resultbase.__init__(self)
  849. self.start = self._attr()
  850. self.end = self._attr()
  851. def parse(self, tzstr):
  852. # Python 2.x compatibility: tzstr should be converted to unicode before
  853. # being passed to _timelex.
  854. if isinstance(tzstr, binary_type):
  855. tzstr = tzstr.decode()
  856. res = self._result()
  857. l = _timelex.split(tzstr)
  858. try:
  859. len_l = len(l)
  860. i = 0
  861. while i < len_l:
  862. # BRST+3[BRDT[+2]]
  863. j = i
  864. while j < len_l and not [x for x in l[j]
  865. if x in "0123456789:,-+"]:
  866. j += 1
  867. if j != i:
  868. if not res.stdabbr:
  869. offattr = "stdoffset"
  870. res.stdabbr = "".join(l[i:j])
  871. else:
  872. offattr = "dstoffset"
  873. res.dstabbr = "".join(l[i:j])
  874. i = j
  875. if (i < len_l and (l[i] in ('+', '-') or l[i][0] in
  876. "0123456789")):
  877. if l[i] in ('+', '-'):
  878. # Yes, that's right. See the TZ variable
  879. # documentation.
  880. signal = (1, -1)[l[i] == '+']
  881. i += 1
  882. else:
  883. signal = -1
  884. len_li = len(l[i])
  885. if len_li == 4:
  886. # -0300
  887. setattr(res, offattr, (int(l[i][:2])*3600 +
  888. int(l[i][2:])*60)*signal)
  889. elif i+1 < len_l and l[i+1] == ':':
  890. # -03:00
  891. setattr(res, offattr,
  892. (int(l[i])*3600+int(l[i+2])*60)*signal)
  893. i += 2
  894. elif len_li <= 2:
  895. # -[0]3
  896. setattr(res, offattr,
  897. int(l[i][:2])*3600*signal)
  898. else:
  899. return None
  900. i += 1
  901. if res.dstabbr:
  902. break
  903. else:
  904. break
  905. if i < len_l:
  906. for j in range(i, len_l):
  907. if l[j] == ';':
  908. l[j] = ','
  909. assert l[i] == ','
  910. i += 1
  911. if i >= len_l:
  912. pass
  913. elif (8 <= l.count(',') <= 9 and
  914. not [y for x in l[i:] if x != ','
  915. for y in x if y not in "0123456789"]):
  916. # GMT0BST,3,0,30,3600,10,0,26,7200[,3600]
  917. for x in (res.start, res.end):
  918. x.month = int(l[i])
  919. i += 2
  920. if l[i] == '-':
  921. value = int(l[i+1])*-1
  922. i += 1
  923. else:
  924. value = int(l[i])
  925. i += 2
  926. if value:
  927. x.week = value
  928. x.weekday = (int(l[i])-1) % 7
  929. else:
  930. x.day = int(l[i])
  931. i += 2
  932. x.time = int(l[i])
  933. i += 2
  934. if i < len_l:
  935. if l[i] in ('-', '+'):
  936. signal = (-1, 1)[l[i] == "+"]
  937. i += 1
  938. else:
  939. signal = 1
  940. res.dstoffset = (res.stdoffset+int(l[i]))*signal
  941. elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
  942. not [y for x in l[i:] if x not in (',', '/', 'J', 'M',
  943. '.', '-', ':')
  944. for y in x if y not in "0123456789"]):
  945. for x in (res.start, res.end):
  946. if l[i] == 'J':
  947. # non-leap year day (1 based)
  948. i += 1
  949. x.jyday = int(l[i])
  950. elif l[i] == 'M':
  951. # month[-.]week[-.]weekday
  952. i += 1
  953. x.month = int(l[i])
  954. i += 1
  955. assert l[i] in ('-', '.')
  956. i += 1
  957. x.week = int(l[i])
  958. if x.week == 5:
  959. x.week = -1
  960. i += 1
  961. assert l[i] in ('-', '.')
  962. i += 1
  963. x.weekday = (int(l[i])-1) % 7
  964. else:
  965. # year day (zero based)
  966. x.yday = int(l[i])+1
  967. i += 1
  968. if i < len_l and l[i] == '/':
  969. i += 1
  970. # start time
  971. len_li = len(l[i])
  972. if len_li == 4:
  973. # -0300
  974. x.time = (int(l[i][:2])*3600+int(l[i][2:])*60)
  975. elif i+1 < len_l and l[i+1] == ':':
  976. # -03:00
  977. x.time = int(l[i])*3600+int(l[i+2])*60
  978. i += 2
  979. if i+1 < len_l and l[i+1] == ':':
  980. i += 2
  981. x.time += int(l[i])
  982. elif len_li <= 2:
  983. # -[0]3
  984. x.time = (int(l[i][:2])*3600)
  985. else:
  986. return None
  987. i += 1
  988. assert i == len_l or l[i] == ','
  989. i += 1
  990. assert i >= len_l
  991. except (IndexError, ValueError, AssertionError):
  992. return None
  993. return res
  994. DEFAULTTZPARSER = _tzparser()
  995. def _parsetz(tzstr):
  996. return DEFAULTTZPARSER.parse(tzstr)
  997. def _parsems(value):
  998. """Parse a I[.F] seconds value into (seconds, microseconds)."""
  999. if "." not in value:
  1000. return int(value), 0
  1001. else:
  1002. i, f = value.split(".")
  1003. return int(i), int(f.ljust(6, "0")[:6])
  1004. # vim:ts=4:sw=4:et