Mercurial > moin > 1.9
changeset 6098:83b1bc99457c
upgrade parsedatetime from 0.8.7 to 2.1
author | Thomas Waldmann <tw AT waldmann-edv DOT de> |
---|---|
date | Tue, 06 Sep 2016 00:09:31 +0200 |
parents | 815981fad7fd |
children | 1736bc375fc4 |
files | MoinMoin/support/parsedatetime/__init__.py MoinMoin/support/parsedatetime/context.py MoinMoin/support/parsedatetime/parsedatetime.py MoinMoin/support/parsedatetime/parsedatetime_consts.py MoinMoin/support/parsedatetime/pdt_locales/__init__.py MoinMoin/support/parsedatetime/pdt_locales/base.py MoinMoin/support/parsedatetime/pdt_locales/de_DE.py MoinMoin/support/parsedatetime/pdt_locales/en_AU.py MoinMoin/support/parsedatetime/pdt_locales/en_US.py MoinMoin/support/parsedatetime/pdt_locales/es.py MoinMoin/support/parsedatetime/pdt_locales/icu.py MoinMoin/support/parsedatetime/pdt_locales/nl_NL.py MoinMoin/support/parsedatetime/pdt_locales/pt_BR.py MoinMoin/support/parsedatetime/pdt_locales/ru_RU.py MoinMoin/support/parsedatetime/warns.py |
diffstat | 15 files changed, 3891 insertions(+), 2686 deletions(-) [+] |
line wrap: on
line diff
--- a/MoinMoin/support/parsedatetime/__init__.py Mon Sep 05 23:55:33 2016 +0200 +++ b/MoinMoin/support/parsedatetime/__init__.py Tue Sep 06 00:09:31 2016 +0200 @@ -1,28 +1,2782 @@ -""" -parsedatetime.py contains the C{Calendar} class where the C{parse()} -method can be found. - -parsedatetime_consts.py contains the C{Constants} class that builds the -various regex values using locale information if available. +# -*- coding: utf-8 -*- +# +# vim: sw=2 ts=2 sts=2 +# +# Copyright 2004-2016 Mike Taylor +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""parsedatetime + +Parse human-readable date/time text. + +Requires Python 2.6 or later """ -version = '0.8.7' -author = 'Mike Taylor and Darshana Chhajed' -license = """ -Copyright (c) 2004-2008 Mike Taylor -Copyright (c) 2006-2008 Darshana Chhajed -All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - +from __future__ import with_statement, absolute_import, unicode_literals + +import re +import time +import logging +import warnings +import datetime +import calendar +import contextlib +import email.utils + +from .pdt_locales import (locales as _locales, + get_icu, load_locale) +from .context import pdtContext, pdtContextStack +from .warns import pdt20DeprecationWarning + + +__author__ = 'Mike Taylor' +__email__ = 'bear@bear.im' +__copyright__ = 'Copyright (c) 2016 Mike Taylor' +__license__ = 'Apache License 2.0' +__version__ = '2.1' +__url__ = 'https://github.com/bear/parsedatetime' +__download_url__ = 'https://pypi.python.org/pypi/parsedatetime' +__description__ = 'Parse human-readable date/time text.' + +# as a library, do *not* setup logging +# see docs.python.org/2/howto/logging.html#configuring-logging-for-a-library +# Set default logging handler to avoid "No handler found" warnings. + +try: # Python 2.7+ + from logging import NullHandler +except ImportError: + class NullHandler(logging.Handler): + + def emit(self, record): + pass + +log = logging.getLogger(__name__) +log.addHandler(NullHandler()) + +debug = False + +pdtLocales = dict([(x, load_locale(x)) for x in _locales]) + + +# Copied from feedparser.py +# Universal Feedparser +# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved. +# Originally a def inside of _parse_date_w3dtf() +def _extract_date(m): + year = int(m.group('year')) + if year < 100: + year = 100 * int(time.gmtime()[0] / 100) + int(year) + if year < 1000: + return 0, 0, 0 + julian = m.group('julian') + if julian: + julian = int(julian) + month = julian / 30 + 1 + day = julian % 30 + 1 + jday = None + while jday != julian: + t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0)) + jday = time.gmtime(t)[-2] + diff = abs(jday - julian) + if jday > julian: + if diff < day: + day = day - diff + else: + month = month - 1 + day = 31 + elif jday < julian: + if day + diff < 28: + day = day + diff + else: + month = month + 1 + return year, month, day + month = m.group('month') + day = 1 + if month is None: + month = 1 + else: + month = int(month) + day = m.group('day') + if day: + day = int(day) + else: + day = 1 + return year, month, day + + +# Copied from feedparser.py +# Universal Feedparser +# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved. +# Originally a def inside of _parse_date_w3dtf() +def _extract_time(m): + if not m: + return 0, 0, 0 + hours = m.group('hours') + if not hours: + return 0, 0, 0 + hours = int(hours) + minutes = int(m.group('minutes')) + seconds = m.group('seconds') + if seconds: + seconds = seconds.replace(',', '.').split('.', 1)[0] + seconds = int(seconds) + else: + seconds = 0 + return hours, minutes, seconds + + +def _pop_time_accuracy(m, ctx): + if not m: + return + if m.group('hours'): + ctx.updateAccuracy(ctx.ACU_HOUR) + if m.group('minutes'): + ctx.updateAccuracy(ctx.ACU_MIN) + if m.group('seconds'): + ctx.updateAccuracy(ctx.ACU_SEC) + + +# Copied from feedparser.py +# Universal Feedparser +# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved. +# Modified to return a tuple instead of mktime +# +# Original comment: +# W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by +# Drake and licensed under the Python license. Removed all range checking +# for month, day, hour, minute, and second, since mktime will normalize +# these later +def __closure_parse_date_w3dtf(): + # the __extract_date and __extract_time methods were + # copied-out so they could be used by my code --bear + def __extract_tzd(m): + '''Return the Time Zone Designator as an offset in seconds from UTC.''' + if not m: + return 0 + tzd = m.group('tzd') + if not tzd: + return 0 + if tzd == 'Z': + return 0 + hours = int(m.group('tzdhours')) + minutes = m.group('tzdminutes') + if minutes: + minutes = int(minutes) + else: + minutes = 0 + offset = (hours * 60 + minutes) * 60 + if tzd[0] == '+': + return -offset + return offset + + def _parse_date_w3dtf(dateString): + m = __datetime_rx.match(dateString) + if m is None or m.group() != dateString: + return + return _extract_date(m) + _extract_time(m) + (0, 0, 0) + + __date_re = (r'(?P<year>\d\d\d\d)' + r'(?:(?P<dsep>-|)' + r'(?:(?P<julian>\d\d\d)' + r'|(?P<month>\d\d)(?:(?P=dsep)(?P<day>\d\d))?))?') + __tzd_re = r'(?P<tzd>[-+](?P<tzdhours>\d\d)(?::?(?P<tzdminutes>\d\d))|Z)' + # __tzd_rx = re.compile(__tzd_re) + __time_re = (r'(?P<hours>\d\d)(?P<tsep>:|)(?P<minutes>\d\d)' + r'(?:(?P=tsep)(?P<seconds>\d\d(?:[.,]\d+)?))?' + + __tzd_re) + __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re) + __datetime_rx = re.compile(__datetime_re) + + return _parse_date_w3dtf + + +_parse_date_w3dtf = __closure_parse_date_w3dtf() +del __closure_parse_date_w3dtf + +_monthnames = set([ + 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', + 'aug', 'sep', 'oct', 'nov', 'dec', + 'january', 'february', 'march', 'april', 'may', 'june', 'july', + 'august', 'september', 'october', 'november', 'december']) +_daynames = set(['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']) + + +# Copied from feedparser.py +# Universal Feedparser +# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved. +# Modified to return a tuple instead of mktime +def _parse_date_rfc822(dateString): + '''Parse an RFC822, RFC1123, RFC2822, or asctime-style date''' + data = dateString.split() + if data[0][-1] in (',', '.') or data[0].lower() in _daynames: + del data[0] + if len(data) == 4: + s = data[3] + s = s.split('+', 1) + if len(s) == 2: + data[3:] = s + else: + data.append('') + dateString = " ".join(data) + if len(data) < 5: + dateString += ' 00:00:00 GMT' + return email.utils.parsedate_tz(dateString) + + +# rfc822.py defines several time zones, but we define some extra ones. +# 'ET' is equivalent to 'EST', etc. +# _additional_timezones = {'AT': -400, 'ET': -500, +# 'CT': -600, 'MT': -700, +# 'PT': -800} +# email.utils._timezones.update(_additional_timezones) + +VERSION_FLAG_STYLE = 1 +VERSION_CONTEXT_STYLE = 2 + + +class Calendar(object): + + """ + A collection of routines to input, parse and manipulate date and times. + The text can either be 'normal' date values or it can be human readable. + """ + + def __init__(self, constants=None, version=VERSION_FLAG_STYLE): + """ + Default constructor for the L{Calendar} class. + + @type constants: object + @param constants: Instance of the class L{Constants} + @type version: integer + @param version: Default style version of current Calendar instance. + Valid value can be 1 (L{VERSION_FLAG_STYLE}) or + 2 (L{VERSION_CONTEXT_STYLE}). See L{parse()}. + + @rtype: object + @return: L{Calendar} instance + """ + # if a constants reference is not included, use default + if constants is None: + self.ptc = Constants() + else: + self.ptc = constants + + self.version = version + if version == VERSION_FLAG_STYLE: + warnings.warn( + 'Flag style will be deprecated in parsedatetime 2.0. ' + 'Instead use the context style by instantiating `Calendar()` ' + 'with argument `version=parsedatetime.VERSION_CONTEXT_STYLE`.', + pdt20DeprecationWarning) + self._ctxStack = pdtContextStack() + + @contextlib.contextmanager + def context(self): + ctx = pdtContext() + self._ctxStack.push(ctx) + yield ctx + ctx = self._ctxStack.pop() + if not self._ctxStack.isEmpty(): + self.currentContext.update(ctx) + + @property + def currentContext(self): + return self._ctxStack.last() + + def _convertUnitAsWords(self, unitText): + """ + Converts text units into their number value. + + @type unitText: string + @param unitText: number text to convert + + @rtype: integer + @return: numerical value of unitText + """ + word_list, a, b = re.split(r"[,\s-]+", unitText), 0, 0 + for word in word_list: + x = self.ptc.small.get(word) + if x is not None: + a += x + elif word == "hundred": + a *= 100 + else: + x = self.ptc.magnitude.get(word) + if x is not None: + b += a * x + a = 0 + elif word in self.ptc.ignore: + pass + else: + raise Exception("Unknown number: " + word) + return a + b + + def _buildTime(self, source, quantity, modifier, units): + """ + Take C{quantity}, C{modifier} and C{unit} strings and convert them + into values. After converting, calcuate the time and return the + adjusted sourceTime. + + @type source: time + @param source: time to use as the base (or source) + @type quantity: string + @param quantity: quantity string + @type modifier: string + @param modifier: how quantity and units modify the source time + @type units: string + @param units: unit of the quantity (i.e. hours, days, months, etc) + + @rtype: struct_time + @return: C{struct_time} of the calculated time + """ + ctx = self.currentContext + debug and log.debug('_buildTime: [%s][%s][%s]', + quantity, modifier, units) + + if source is None: + source = time.localtime() + + if quantity is None: + quantity = '' + else: + quantity = quantity.strip() + + qty = self._quantityToReal(quantity) + + if modifier in self.ptc.Modifiers: + qty = qty * self.ptc.Modifiers[modifier] + + if units is None or units == '': + units = 'dy' + + # plurals are handled by regex's (could be a bug tho) + + (yr, mth, dy, hr, mn, sec, _, _, _) = source + + start = datetime.datetime(yr, mth, dy, hr, mn, sec) + target = start + # realunit = next((key for key, values in self.ptc.units.items() + # if any(imap(units.__contains__, values))), None) + realunit = units + for key, values in self.ptc.units.items(): + if units in values: + realunit = key + break + + debug and log.debug('units %s --> realunit %s (qty=%s)', + units, realunit, qty) + + try: + if realunit in ('years', 'months'): + target = self.inc(start, **{realunit[:-1]: qty}) + elif realunit in ('days', 'hours', 'minutes', 'seconds', 'weeks'): + delta = datetime.timedelta(**{realunit: qty}) + target = start + delta + except OverflowError: + # OverflowError is raise when target.year larger than 9999 + pass + else: + ctx.updateAccuracy(realunit) + + return target.timetuple() + + def parseDate(self, dateString, sourceTime=None): + """ + Parse short-form date strings:: + + '05/28/2006' or '04.21' + + @type dateString: string + @param dateString: text to convert to a C{datetime} + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: struct_time + @return: calculated C{struct_time} value of dateString + """ + if sourceTime is None: + yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime() + else: + yr, mth, dy, hr, mn, sec, wd, yd, isdst = sourceTime + + # values pulled from regex's will be stored here and later + # assigned to mth, dy, yr based on information from the locale + # -1 is used as the marker value because we want zero values + # to be passed thru so they can be flagged as errors later + v1 = -1 + v2 = -1 + v3 = -1 + accuracy = [] + + s = dateString + m = self.ptc.CRE_DATE2.search(s) + if m is not None: + index = m.start() + v1 = int(s[:index]) + s = s[index + 1:] + + m = self.ptc.CRE_DATE2.search(s) + if m is not None: + index = m.start() + v2 = int(s[:index]) + v3 = int(s[index + 1:]) + else: + v2 = int(s.strip()) + + v = [v1, v2, v3] + d = {'m': mth, 'd': dy, 'y': yr} + + # yyyy/mm/dd format + dp_order = self.ptc.dp_order if v1 <= 31 else ['y', 'm', 'd'] + + for i in range(0, 3): + n = v[i] + c = dp_order[i] + if n >= 0: + d[c] = n + accuracy.append({'m': pdtContext.ACU_MONTH, + 'd': pdtContext.ACU_DAY, + 'y': pdtContext.ACU_YEAR}[c]) + + # if the year is not specified and the date has already + # passed, increment the year + if v3 == -1 and ((mth > d['m']) or (mth == d['m'] and dy > d['d'])): + yr = d['y'] + self.ptc.YearParseStyle + else: + yr = d['y'] + + mth = d['m'] + dy = d['d'] + + # birthday epoch constraint + if yr < self.ptc.BirthdayEpoch: + yr += 2000 + elif yr < 100: + yr += 1900 + + daysInCurrentMonth = self.ptc.daysInMonth(mth, yr) + debug and log.debug('parseDate: %s %s %s %s', + yr, mth, dy, daysInCurrentMonth) + + with self.context() as ctx: + if mth > 0 and mth <= 12 and dy > 0 and \ + dy <= daysInCurrentMonth: + sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) + ctx.updateAccuracy(*accuracy) + else: + # return current time if date string is invalid + sourceTime = time.localtime() + + return sourceTime + + def parseDateText(self, dateString, sourceTime=None): + """ + Parse long-form date strings:: + + 'May 31st, 2006' + 'Jan 1st' + 'July 2006' + + @type dateString: string + @param dateString: text to convert to a datetime + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: struct_time + @return: calculated C{struct_time} value of dateString + """ + if sourceTime is None: + yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime() + else: + yr, mth, dy, hr, mn, sec, wd, yd, isdst = sourceTime + + currentMth = mth + currentDy = dy + accuracy = [] + + debug and log.debug('parseDateText currentMth %s currentDy %s', + mth, dy) + + s = dateString.lower() + m = self.ptc.CRE_DATE3.search(s) + mth = m.group('mthname') + mth = self.ptc.MonthOffsets[mth] + accuracy.append('month') + + if m.group('day') is not None: + dy = int(m.group('day')) + accuracy.append('day') + else: + dy = 1 + + if m.group('year') is not None: + yr = int(m.group('year')) + accuracy.append('year') + + # birthday epoch constraint + if yr < self.ptc.BirthdayEpoch: + yr += 2000 + elif yr < 100: + yr += 1900 + + elif (mth < currentMth) or (mth == currentMth and dy < currentDy): + # if that day and month have already passed in this year, + # then increment the year by 1 + yr += self.ptc.YearParseStyle + + with self.context() as ctx: + if dy > 0 and dy <= self.ptc.daysInMonth(mth, yr): + sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) + ctx.updateAccuracy(*accuracy) + else: + # Return current time if date string is invalid + sourceTime = time.localtime() + + debug and log.debug('parseDateText returned ' + 'mth %d dy %d yr %d sourceTime %s', + mth, dy, yr, sourceTime) + + return sourceTime + + def evalRanges(self, datetimeString, sourceTime=None): + """ + Evaluate the C{datetimeString} text and determine if + it represents a date or time range. + + @type datetimeString: string + @param datetimeString: datetime text to evaluate + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: tuple + @return: tuple of: start datetime, end datetime and the invalid flag + """ + rangeFlag = retFlag = 0 + startStr = endStr = '' + + s = datetimeString.strip().lower() + + if self.ptc.rangeSep in s: + s = s.replace(self.ptc.rangeSep, ' %s ' % self.ptc.rangeSep) + s = s.replace(' ', ' ') + + for cre, rflag in [(self.ptc.CRE_TIMERNG1, 1), + (self.ptc.CRE_TIMERNG2, 2), + (self.ptc.CRE_TIMERNG4, 7), + (self.ptc.CRE_TIMERNG3, 3), + (self.ptc.CRE_DATERNG1, 4), + (self.ptc.CRE_DATERNG2, 5), + (self.ptc.CRE_DATERNG3, 6)]: + m = cre.search(s) + if m is not None: + rangeFlag = rflag + break + + debug and log.debug('evalRanges: rangeFlag = %s [%s]', rangeFlag, s) + + if m is not None: + if (m.group() != s): + # capture remaining string + parseStr = m.group() + chunk1 = s[:m.start()] + chunk2 = s[m.end():] + s = '%s %s' % (chunk1, chunk2) + + sourceTime, ctx = self.parse(s, sourceTime, + VERSION_CONTEXT_STYLE) + + if not ctx.hasDateOrTime: + sourceTime = None + else: + parseStr = s + + if rangeFlag in (1, 2): + m = re.search(self.ptc.rangeSep, parseStr) + startStr = parseStr[:m.start()] + endStr = parseStr[m.start() + 1:] + retFlag = 2 + + elif rangeFlag in (3, 7): + m = re.search(self.ptc.rangeSep, parseStr) + # capturing the meridian from the end time + if self.ptc.usesMeridian: + ampm = re.search(self.ptc.am[0], parseStr) + + # appending the meridian to the start time + if ampm is not None: + startStr = parseStr[:m.start()] + self.ptc.meridian[0] + else: + startStr = parseStr[:m.start()] + self.ptc.meridian[1] + else: + startStr = parseStr[:m.start()] + + endStr = parseStr[m.start() + 1:] + retFlag = 2 + + elif rangeFlag == 4: + m = re.search(self.ptc.rangeSep, parseStr) + startStr = parseStr[:m.start()] + endStr = parseStr[m.start() + 1:] + retFlag = 1 + + elif rangeFlag == 5: + m = re.search(self.ptc.rangeSep, parseStr) + endStr = parseStr[m.start() + 1:] + + # capturing the year from the end date + date = self.ptc.CRE_DATE3.search(endStr) + endYear = date.group('year') + + # appending the year to the start date if the start date + # does not have year information and the end date does. + # eg : "Aug 21 - Sep 4, 2007" + if endYear is not None: + startStr = (parseStr[:m.start()]).strip() + date = self.ptc.CRE_DATE3.search(startStr) + startYear = date.group('year') + + if startYear is None: + startStr = startStr + ', ' + endYear + else: + startStr = parseStr[:m.start()] + + retFlag = 1 + + elif rangeFlag == 6: + m = re.search(self.ptc.rangeSep, parseStr) + + startStr = parseStr[:m.start()] + + # capturing the month from the start date + mth = self.ptc.CRE_DATE3.search(startStr) + mth = mth.group('mthname') + + # appending the month name to the end date + endStr = mth + parseStr[(m.start() + 1):] + + retFlag = 1 + + else: + # if range is not found + startDT = endDT = time.localtime() + + if retFlag: + startDT, sctx = self.parse(startStr, sourceTime, + VERSION_CONTEXT_STYLE) + endDT, ectx = self.parse(endStr, sourceTime, + VERSION_CONTEXT_STYLE) + + if not sctx.hasDateOrTime or not ectx.hasDateOrTime: + retFlag = 0 + + return startDT, endDT, retFlag + + def _CalculateDOWDelta(self, wd, wkdy, offset, style, currentDayStyle): + """ + Based on the C{style} and C{currentDayStyle} determine what + day-of-week value is to be returned. + + @type wd: integer + @param wd: day-of-week value for the current day + @type wkdy: integer + @param wkdy: day-of-week value for the parsed day + @type offset: integer + @param offset: offset direction for any modifiers (-1, 0, 1) + @type style: integer + @param style: normally the value + set in C{Constants.DOWParseStyle} + @type currentDayStyle: integer + @param currentDayStyle: normally the value + set in C{Constants.CurrentDOWParseStyle} + + @rtype: integer + @return: calculated day-of-week + """ + diffBase = wkdy - wd + origOffset = offset + + if offset == 2: + # no modifier is present. + # i.e. string to be parsed is just DOW + if wkdy * style > wd * style or \ + currentDayStyle and wkdy == wd: + # wkdy located in current week + offset = 0 + elif style in (-1, 1): + # wkdy located in last (-1) or next (1) week + offset = style + else: + # invalid style, or should raise error? + offset = 0 + + # offset = -1 means last week + # offset = 0 means current week + # offset = 1 means next week + diff = diffBase + 7 * offset + if style == 1 and diff < -7: + diff += 7 + elif style == -1 and diff > 7: + diff -= 7 + + debug and log.debug("wd %s, wkdy %s, offset %d, " + "style %d, currentDayStyle %d", + wd, wkdy, origOffset, style, currentDayStyle) + + return diff + + def _quantityToReal(self, quantity): + """ + Convert a quantity, either spelled-out or numeric, to a float + + @type quantity: string + @param quantity: quantity to parse to float + @rtype: int + @return: the quantity as an float, defaulting to 0.0 + """ + if not quantity: + return 1.0 + + try: + return float(quantity.replace(',', '.')) + except ValueError: + pass + + try: + return float(self.ptc.numbers[quantity]) + except KeyError: + pass + + return 0.0 + + def _evalModifier(self, modifier, chunk1, chunk2, sourceTime): + """ + Evaluate the C{modifier} string and following text (passed in + as C{chunk1} and C{chunk2}) and if they match any known modifiers + calculate the delta and apply it to C{sourceTime}. + + @type modifier: string + @param modifier: modifier text to apply to sourceTime + @type chunk1: string + @param chunk1: text chunk that preceded modifier (if any) + @type chunk2: string + @param chunk2: text chunk that followed modifier (if any) + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: tuple + @return: tuple of: remaining text and the modified sourceTime + """ + ctx = self.currentContext + offset = self.ptc.Modifiers[modifier] + + if sourceTime is not None: + (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime + else: + (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime() + + if self.ptc.StartTimeFromSourceTime: + startHour = hr + startMinute = mn + startSecond = sec + else: + startHour = 9 + startMinute = 0 + startSecond = 0 + + # capture the units after the modifier and the remaining + # string after the unit + m = self.ptc.CRE_REMAINING.search(chunk2) + if m is not None: + index = m.start() + 1 + unit = chunk2[:m.start()] + chunk2 = chunk2[index:] + else: + unit = chunk2 + chunk2 = '' + + debug and log.debug("modifier [%s] chunk1 [%s] " + "chunk2 [%s] unit [%s]", + modifier, chunk1, chunk2, unit) + + if unit in self.ptc.units['months']: + currentDaysInMonth = self.ptc.daysInMonth(mth, yr) + if offset == 0: + dy = currentDaysInMonth + sourceTime = (yr, mth, dy, startHour, startMinute, + startSecond, wd, yd, isdst) + elif offset == 2: + # if day is the last day of the month, calculate the last day + # of the next month + if dy == currentDaysInMonth: + dy = self.ptc.daysInMonth(mth + 1, yr) + + start = datetime.datetime(yr, mth, dy, startHour, + startMinute, startSecond) + target = self.inc(start, month=1) + sourceTime = target.timetuple() + else: + start = datetime.datetime(yr, mth, 1, startHour, + startMinute, startSecond) + target = self.inc(start, month=offset) + sourceTime = target.timetuple() + ctx.updateAccuracy(ctx.ACU_MONTH) + + elif unit in self.ptc.units['weeks']: + if offset == 0: + start = datetime.datetime(yr, mth, dy, 17, 0, 0) + target = start + datetime.timedelta(days=(4 - wd)) + sourceTime = target.timetuple() + elif offset == 2: + start = datetime.datetime(yr, mth, dy, startHour, + startMinute, startSecond) + target = start + datetime.timedelta(days=7) + sourceTime = target.timetuple() + else: + start = datetime.datetime(yr, mth, dy, startHour, + startMinute, startSecond) + target = start + offset * datetime.timedelta(weeks=1) + sourceTime = target.timetuple() + ctx.updateAccuracy(ctx.ACU_WEEK) + + elif unit in self.ptc.units['days']: + if offset == 0: + sourceTime = (yr, mth, dy, 17, 0, 0, wd, yd, isdst) + ctx.updateAccuracy(ctx.ACU_HALFDAY) + elif offset == 2: + start = datetime.datetime(yr, mth, dy, hr, mn, sec) + target = start + datetime.timedelta(days=1) + sourceTime = target.timetuple() + else: + start = datetime.datetime(yr, mth, dy, startHour, + startMinute, startSecond) + target = start + datetime.timedelta(days=offset) + sourceTime = target.timetuple() + ctx.updateAccuracy(ctx.ACU_DAY) + + elif unit in self.ptc.units['hours']: + if offset == 0: + sourceTime = (yr, mth, dy, hr, 0, 0, wd, yd, isdst) + else: + start = datetime.datetime(yr, mth, dy, hr, 0, 0) + target = start + datetime.timedelta(hours=offset) + sourceTime = target.timetuple() + ctx.updateAccuracy(ctx.ACU_HOUR) + + elif unit in self.ptc.units['years']: + if offset == 0: + sourceTime = (yr, 12, 31, hr, mn, sec, wd, yd, isdst) + elif offset == 2: + sourceTime = (yr + 1, mth, dy, hr, mn, sec, wd, yd, isdst) + else: + sourceTime = (yr + offset, 1, 1, startHour, startMinute, + startSecond, wd, yd, isdst) + ctx.updateAccuracy(ctx.ACU_YEAR) + + elif modifier == 'eom': + dy = self.ptc.daysInMonth(mth, yr) + sourceTime = (yr, mth, dy, startHour, startMinute, + startSecond, wd, yd, isdst) + ctx.updateAccuracy(ctx.ACU_DAY) + + elif modifier == 'eoy': + mth = 12 + dy = self.ptc.daysInMonth(mth, yr) + sourceTime = (yr, mth, dy, startHour, startMinute, + startSecond, wd, yd, isdst) + ctx.updateAccuracy(ctx.ACU_MONTH) + + elif self.ptc.CRE_WEEKDAY.match(unit): + m = self.ptc.CRE_WEEKDAY.match(unit) + debug and log.debug('CRE_WEEKDAY matched') + wkdy = m.group() + + if modifier == 'eod': + ctx.updateAccuracy(ctx.ACU_HOUR) + # Calculate the upcoming weekday + sourceTime, subctx = self.parse(wkdy, sourceTime, + VERSION_CONTEXT_STYLE) + sTime = self.ptc.getSource(modifier, sourceTime) + if sTime is not None: + sourceTime = sTime + ctx.updateAccuracy(ctx.ACU_HALFDAY) + else: + # unless one of these modifiers is being applied to the + # day-of-week, we want to start with target as the day + # in the current week. + dowOffset = offset + if modifier not in ['next', 'last', 'prior', 'previous']: + dowOffset = 0 + + wkdy = self.ptc.WeekdayOffsets[wkdy] + diff = self._CalculateDOWDelta( + wd, wkdy, dowOffset, self.ptc.DOWParseStyle, + self.ptc.CurrentDOWParseStyle) + start = datetime.datetime(yr, mth, dy, startHour, + startMinute, startSecond) + target = start + datetime.timedelta(days=diff) + + if chunk1 != '': + # consider "one day before thursday": we need to parse chunk1 ("one day") + # and apply according to the offset ("before"), rather than allowing the + # remaining parse step to apply "one day" without the offset direction. + t, subctx = self.parse(chunk1, sourceTime, VERSION_CONTEXT_STYLE) + if subctx.hasDateOrTime: + delta = time.mktime(t) - time.mktime(sourceTime) + target = start + datetime.timedelta(days=diff) + datetime.timedelta(seconds=delta * offset) + chunk1 = '' + + sourceTime = target.timetuple() + ctx.updateAccuracy(ctx.ACU_DAY) + + elif self.ptc.CRE_TIME.match(unit): + m = self.ptc.CRE_TIME.match(unit) + debug and log.debug('CRE_TIME matched') + (yr, mth, dy, hr, mn, sec, wd, yd, isdst), subctx = \ + self.parse(unit, None, VERSION_CONTEXT_STYLE) + + start = datetime.datetime(yr, mth, dy, hr, mn, sec) + target = start + datetime.timedelta(days=offset) + sourceTime = target.timetuple() + + else: + # check if the remaining text is parsable and if so, + # use it as the base time for the modifier source time + + debug and log.debug('check for modifications ' + 'to source time [%s] [%s]', + chunk1, unit) + + unit = unit.strip() + if unit: + s = '%s %s' % (unit, chunk2) + t, subctx = self.parse(s, sourceTime, VERSION_CONTEXT_STYLE) + + if subctx.hasDate: # working with dates + u = unit.lower() + if u in self.ptc.Months or \ + u in self.ptc.shortMonths: + yr, mth, dy, hr, mn, sec, wd, yd, isdst = t + start = datetime.datetime( + yr, mth, dy, hr, mn, sec) + t = self.inc(start, year=offset).timetuple() + elif u in self.ptc.Weekdays: + t = t + datetime.timedelta(weeks=offset) + + if subctx.hasDateOrTime: + sourceTime = t + chunk2 = '' + + chunk1 = chunk1.strip() + + # if the word after next is a number, the string is more than + # likely to be "next 4 hrs" which we will have to combine the + # units with the rest of the string + if chunk1: + try: + m = list(self.ptc.CRE_NUMBER.finditer(chunk1))[-1] + except IndexError: + pass + else: + qty = None + debug and log.debug('CRE_NUMBER matched') + qty = self._quantityToReal(m.group()) * offset + chunk1 = '%s%s%s' % (chunk1[:m.start()], + qty, chunk1[m.end():]) + t, subctx = self.parse(chunk1, sourceTime, + VERSION_CONTEXT_STYLE) + + chunk1 = '' + + if subctx.hasDateOrTime: + sourceTime = t + + debug and log.debug('looking for modifier %s', modifier) + sTime = self.ptc.getSource(modifier, sourceTime) + if sTime is not None: + debug and log.debug('modifier found in sources') + sourceTime = sTime + ctx.updateAccuracy(ctx.ACU_HALFDAY) + + debug and log.debug('returning chunk = "%s %s" and sourceTime = %s', + chunk1, chunk2, sourceTime) + + return '%s %s' % (chunk1, chunk2), sourceTime + + def _evalDT(self, datetimeString, sourceTime): + """ + Calculate the datetime from known format like RFC822 or W3CDTF + + Examples handled:: + RFC822, W3CDTF formatted dates + HH:MM[:SS][ am/pm] + MM/DD/YYYY + DD MMMM YYYY + + @type datetimeString: string + @param datetimeString: text to try and parse as more "traditional" + date/time text + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: datetime + @return: calculated C{struct_time} value or current C{struct_time} + if not parsed + """ + ctx = self.currentContext + s = datetimeString.strip() + + # Given string date is a RFC822 date + if sourceTime is None: + sourceTime = _parse_date_rfc822(s) + debug and log.debug( + 'attempt to parse as rfc822 - %s', str(sourceTime)) + + if sourceTime is not None: + (yr, mth, dy, hr, mn, sec, wd, yd, isdst, _) = sourceTime + ctx.updateAccuracy(ctx.ACU_YEAR, ctx.ACU_MONTH, ctx.ACU_DAY) + + if hr != 0 and mn != 0 and sec != 0: + ctx.updateAccuracy(ctx.ACU_HOUR, ctx.ACU_MIN, ctx.ACU_SEC) + + sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) + + # Given string date is a W3CDTF date + if sourceTime is None: + sourceTime = _parse_date_w3dtf(s) + + if sourceTime is not None: + ctx.updateAccuracy(ctx.ACU_YEAR, ctx.ACU_MONTH, ctx.ACU_DAY, + ctx.ACU_HOUR, ctx.ACU_MIN, ctx.ACU_SEC) + + if sourceTime is None: + sourceTime = time.localtime() + + return sourceTime + + def _evalUnits(self, datetimeString, sourceTime): + """ + Evaluate text passed by L{_partialParseUnits()} + """ + s = datetimeString.strip() + sourceTime = self._evalDT(datetimeString, sourceTime) + + # Given string is a time string with units like "5 hrs 30 min" + modifier = '' # TODO + + m = self.ptc.CRE_UNITS.search(s) + if m is not None: + units = m.group('units') + quantity = s[:m.start('units')] + + sourceTime = self._buildTime(sourceTime, quantity, modifier, units) + return sourceTime + + def _evalQUnits(self, datetimeString, sourceTime): + """ + Evaluate text passed by L{_partialParseQUnits()} + """ + s = datetimeString.strip() + sourceTime = self._evalDT(datetimeString, sourceTime) + + # Given string is a time string with single char units like "5 h 30 m" + modifier = '' # TODO + + m = self.ptc.CRE_QUNITS.search(s) + if m is not None: + units = m.group('qunits') + quantity = s[:m.start('qunits')] + + sourceTime = self._buildTime(sourceTime, quantity, modifier, units) + return sourceTime + + def _evalDateStr(self, datetimeString, sourceTime): + """ + Evaluate text passed by L{_partialParseDateStr()} + """ + s = datetimeString.strip() + sourceTime = self._evalDT(datetimeString, sourceTime) + + # Given string is in the format "May 23rd, 2005" + debug and log.debug('checking for MMM DD YYYY') + return self.parseDateText(s, sourceTime) + + def _evalDateStd(self, datetimeString, sourceTime): + """ + Evaluate text passed by L{_partialParseDateStd()} + """ + s = datetimeString.strip() + sourceTime = self._evalDT(datetimeString, sourceTime) + + # Given string is in the format 07/21/2006 + return self.parseDate(s, sourceTime) + + def _evalDayStr(self, datetimeString, sourceTime): + """ + Evaluate text passed by L{_partialParseDaystr()} + """ + s = datetimeString.strip() + sourceTime = self._evalDT(datetimeString, sourceTime) + + # Given string is a natural language date string like today, tomorrow.. + (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime + + try: + offset = self.ptc.dayOffsets[s] + except KeyError: + offset = 0 + + if self.ptc.StartTimeFromSourceTime: + startHour = hr + startMinute = mn + startSecond = sec + else: + startHour = 9 + startMinute = 0 + startSecond = 0 + + self.currentContext.updateAccuracy(pdtContext.ACU_DAY) + start = datetime.datetime(yr, mth, dy, startHour, + startMinute, startSecond) + target = start + datetime.timedelta(days=offset) + return target.timetuple() + + def _evalWeekday(self, datetimeString, sourceTime): + """ + Evaluate text passed by L{_partialParseWeekday()} + """ + s = datetimeString.strip() + sourceTime = self._evalDT(datetimeString, sourceTime) + + # Given string is a weekday + yr, mth, dy, hr, mn, sec, wd, yd, isdst = sourceTime + + start = datetime.datetime(yr, mth, dy, hr, mn, sec) + wkdy = self.ptc.WeekdayOffsets[s] + + if wkdy > wd: + qty = self._CalculateDOWDelta(wd, wkdy, 2, + self.ptc.DOWParseStyle, + self.ptc.CurrentDOWParseStyle) + else: + qty = self._CalculateDOWDelta(wd, wkdy, 2, + self.ptc.DOWParseStyle, + self.ptc.CurrentDOWParseStyle) + + self.currentContext.updateAccuracy(pdtContext.ACU_DAY) + target = start + datetime.timedelta(days=qty) + return target.timetuple() + + def _evalTimeStr(self, datetimeString, sourceTime): + """ + Evaluate text passed by L{_partialParseTimeStr()} + """ + s = datetimeString.strip() + sourceTime = self._evalDT(datetimeString, sourceTime) + + if s in self.ptc.re_values['now']: + self.currentContext.updateAccuracy(pdtContext.ACU_NOW) + else: + # Given string is a natural language time string like + # lunch, midnight, etc + sTime = self.ptc.getSource(s, sourceTime) + if sTime: + sourceTime = sTime + self.currentContext.updateAccuracy(pdtContext.ACU_HALFDAY) + + return sourceTime + + def _evalMeridian(self, datetimeString, sourceTime): + """ + Evaluate text passed by L{_partialParseMeridian()} + """ + s = datetimeString.strip() + sourceTime = self._evalDT(datetimeString, sourceTime) + + # Given string is in the format HH:MM(:SS)(am/pm) + yr, mth, dy, hr, mn, sec, wd, yd, isdst = sourceTime + + m = self.ptc.CRE_TIMEHMS2.search(s) + if m is not None: + dt = s[:m.start('meridian')].strip() + if len(dt) <= 2: + hr = int(dt) + mn = 0 + sec = 0 + else: + hr, mn, sec = _extract_time(m) + + if hr == 24: + hr = 0 + + meridian = m.group('meridian').lower() + + # if 'am' found and hour is 12 - force hour to 0 (midnight) + if (meridian in self.ptc.am) and hr == 12: + hr = 0 + + # if 'pm' found and hour < 12, add 12 to shift to evening + if (meridian in self.ptc.pm) and hr < 12: + hr += 12 + + # time validation + if hr < 24 and mn < 60 and sec < 60: + sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) + _pop_time_accuracy(m, self.currentContext) + + return sourceTime + + def _evalTimeStd(self, datetimeString, sourceTime): + """ + Evaluate text passed by L{_partialParseTimeStd()} + """ + s = datetimeString.strip() + sourceTime = self._evalDT(datetimeString, sourceTime) + + # Given string is in the format HH:MM(:SS) + yr, mth, dy, hr, mn, sec, wd, yd, isdst = sourceTime + + m = self.ptc.CRE_TIMEHMS.search(s) + if m is not None: + hr, mn, sec = _extract_time(m) + if hr == 24: + hr = 0 + + # time validation + if hr < 24 and mn < 60 and sec < 60: + sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) + _pop_time_accuracy(m, self.currentContext) + + return sourceTime + + def _UnitsTrapped(self, s, m, key): + # check if a day suffix got trapped by a unit match + # for example Dec 31st would match for 31s (aka 31 seconds) + # Dec 31st + # ^ ^ + # | +-- m.start('units') + # | and also m2.start('suffix') + # +---- m.start('qty') + # and also m2.start('day') + m2 = self.ptc.CRE_DAY2.search(s) + if m2 is not None: + t = '%s%s' % (m2.group('day'), m.group(key)) + if m.start(key) == m2.start('suffix') and \ + m.start('qty') == m2.start('day') and \ + m.group('qty') == t: + return True + else: + return False + else: + return False + + def _partialParseModifier(self, s, sourceTime): + """ + test if giving C{s} matched CRE_MODIFIER, used by L{parse()} + + @type s: string + @param s: date/time text to evaluate + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: tuple + @return: tuple of remained date/time text, datetime object and + an boolean value to describ if matched or not + + """ + parseStr = None + chunk1 = chunk2 = '' + + # Modifier like next/prev/from/after/prior.. + m = self.ptc.CRE_MODIFIER.search(s) + if m is not None: + if m.group() != s: + # capture remaining string + parseStr = m.group() + chunk1 = s[:m.start()].strip() + chunk2 = s[m.end():].strip() + else: + parseStr = s + + if parseStr: + debug and log.debug('found (modifier) [%s][%s][%s]', + parseStr, chunk1, chunk2) + s, sourceTime = self._evalModifier(parseStr, chunk1, + chunk2, sourceTime) + + return s, sourceTime, bool(parseStr) + + def _partialParseUnits(self, s, sourceTime): + """ + test if giving C{s} matched CRE_UNITS, used by L{parse()} + + @type s: string + @param s: date/time text to evaluate + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: tuple + @return: tuple of remained date/time text, datetime object and + an boolean value to describ if matched or not + + """ + parseStr = None + chunk1 = chunk2 = '' + + # Quantity + Units + m = self.ptc.CRE_UNITS.search(s) + if m is not None: + debug and log.debug('CRE_UNITS matched') + if self._UnitsTrapped(s, m, 'units'): + debug and log.debug('day suffix trapped by unit match') + else: + if (m.group('qty') != s): + # capture remaining string + parseStr = m.group('qty') + chunk1 = s[:m.start('qty')].strip() + chunk2 = s[m.end('qty'):].strip() + + if chunk1[-1:] == '-': + parseStr = '-%s' % parseStr + chunk1 = chunk1[:-1] + + s = '%s %s' % (chunk1, chunk2) + else: + parseStr = s + s = '' + + if parseStr: + debug and log.debug('found (units) [%s][%s][%s]', + parseStr, chunk1, chunk2) + sourceTime = self._evalUnits(parseStr, sourceTime) + + return s, sourceTime, bool(parseStr) + + def _partialParseQUnits(self, s, sourceTime): + """ + test if giving C{s} matched CRE_QUNITS, used by L{parse()} + + @type s: string + @param s: date/time text to evaluate + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: tuple + @return: tuple of remained date/time text, datetime object and + an boolean value to describ if matched or not + + """ + parseStr = None + chunk1 = chunk2 = '' + + # Quantity + Units + m = self.ptc.CRE_QUNITS.search(s) + if m is not None: + debug and log.debug('CRE_QUNITS matched') + if self._UnitsTrapped(s, m, 'qunits'): + debug and log.debug( + 'day suffix trapped by qunit match') + else: + if (m.group('qty') != s): + # capture remaining string + parseStr = m.group('qty') + chunk1 = s[:m.start('qty')].strip() + chunk2 = s[m.end('qty'):].strip() + + if chunk1[-1:] == '-': + parseStr = '-%s' % parseStr + chunk1 = chunk1[:-1] + + s = '%s %s' % (chunk1, chunk2) + else: + parseStr = s + s = '' + + if parseStr: + debug and log.debug('found (qunits) [%s][%s][%s]', + parseStr, chunk1, chunk2) + sourceTime = self._evalQUnits(parseStr, sourceTime) + + return s, sourceTime, bool(parseStr) + + def _partialParseDateStr(self, s, sourceTime): + """ + test if giving C{s} matched CRE_DATE3, used by L{parse()} + + @type s: string + @param s: date/time text to evaluate + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: tuple + @return: tuple of remained date/time text, datetime object and + an boolean value to describ if matched or not + + """ + parseStr = None + chunk1 = chunk2 = '' + + m = self.ptc.CRE_DATE3.search(s) + # NO LONGER NEEDED, THE REGEXP HANDLED MTHNAME NOW + # for match in self.ptc.CRE_DATE3.finditer(s): + # to prevent "HH:MM(:SS) time strings" expressions from + # triggering this regex, we checks if the month field + # exists in the searched expression, if it doesn't exist, + # the date field is not valid + # if match.group('mthname'): + # m = self.ptc.CRE_DATE3.search(s, match.start()) + # valid_date = True + # break + + # String date format + if m is not None: + + if (m.group('date') != s): + # capture remaining string + mStart = m.start('date') + mEnd = m.end('date') + + # we need to check that anything following the parsed + # date is a time expression because it is often picked + # up as a valid year if the hour is 2 digits + fTime = False + mm = self.ptc.CRE_TIMEHMS2.search(s) + # "February 24th 1PM" doesn't get caught + # "February 24th 12PM" does + mYear = m.group('year') + if mm is not None and mYear is not None: + fTime = True + else: + # "February 24th 12:00" + mm = self.ptc.CRE_TIMEHMS.search(s) + if mm is not None and mYear is None: + fTime = True + if fTime: + hoursStart = mm.start('hours') + + if hoursStart < m.end('year'): + mEnd = hoursStart + + parseStr = s[mStart:mEnd] + chunk1 = s[:mStart] + chunk2 = s[mEnd:] + + s = '%s %s' % (chunk1, chunk2) + else: + parseStr = s + s = '' + + if parseStr: + debug and log.debug( + 'found (date3) [%s][%s][%s]', parseStr, chunk1, chunk2) + sourceTime = self._evalDateStr(parseStr, sourceTime) + + return s, sourceTime, bool(parseStr) + + def _partialParseDateStd(self, s, sourceTime): + """ + test if giving C{s} matched CRE_DATE, used by L{parse()} + + @type s: string + @param s: date/time text to evaluate + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: tuple + @return: tuple of remained date/time text, datetime object and + an boolean value to describ if matched or not + + """ + parseStr = None + chunk1 = chunk2 = '' + + # Standard date format + m = self.ptc.CRE_DATE.search(s) + if m is not None: + + if (m.group('date') != s): + # capture remaining string + parseStr = m.group('date') + chunk1 = s[:m.start('date')] + chunk2 = s[m.end('date'):] + s = '%s %s' % (chunk1, chunk2) + else: + parseStr = s + s = '' + + if parseStr: + debug and log.debug( + 'found (date) [%s][%s][%s]', parseStr, chunk1, chunk2) + sourceTime = self._evalDateStd(parseStr, sourceTime) + + return s, sourceTime, bool(parseStr) + + def _partialParseDayStr(self, s, sourceTime): + """ + test if giving C{s} matched CRE_DAY, used by L{parse()} + + @type s: string + @param s: date/time text to evaluate + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: tuple + @return: tuple of remained date/time text, datetime object and + an boolean value to describ if matched or not + + """ + parseStr = None + chunk1 = chunk2 = '' + + # Natural language day strings + m = self.ptc.CRE_DAY.search(s) + if m is not None: + + if (m.group() != s): + # capture remaining string + parseStr = m.group() + chunk1 = s[:m.start()] + chunk2 = s[m.end():] + s = '%s %s' % (chunk1, chunk2) + else: + parseStr = s + s = '' + + if parseStr: + debug and log.debug( + 'found (day) [%s][%s][%s]', parseStr, chunk1, chunk2) + sourceTime = self._evalDayStr(parseStr, sourceTime) + + return s, sourceTime, bool(parseStr) + + def _partialParseWeekday(self, s, sourceTime): + """ + test if giving C{s} matched CRE_WEEKDAY, used by L{parse()} + + @type s: string + @param s: date/time text to evaluate + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: tuple + @return: tuple of remained date/time text, datetime object and + an boolean value to describ if matched or not + + """ + parseStr = None + chunk1 = chunk2 = '' + + # Weekday + m = self.ptc.CRE_WEEKDAY.search(s) + if m is not None: + gv = m.group() + if s not in self.ptc.dayOffsets: + + if (gv != s): + # capture remaining string + parseStr = gv + chunk1 = s[:m.start()] + chunk2 = s[m.end():] + s = '%s %s' % (chunk1, chunk2) + else: + parseStr = s + s = '' + + if parseStr: + debug and log.debug( + 'found (weekday) [%s][%s][%s]', parseStr, chunk1, chunk2) + sourceTime = self._evalWeekday(parseStr, sourceTime) + + return s, sourceTime, bool(parseStr) + + def _partialParseTimeStr(self, s, sourceTime): + """ + test if giving C{s} matched CRE_TIME, used by L{parse()} + + @type s: string + @param s: date/time text to evaluate + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: tuple + @return: tuple of remained date/time text, datetime object and + an boolean value to describ if matched or not + + """ + parseStr = None + chunk1 = chunk2 = '' + + # Natural language time strings + m = self.ptc.CRE_TIME.search(s) + if m is not None or s in self.ptc.re_values['now']: + + if (m and m.group() != s): + # capture remaining string + parseStr = m.group() + chunk1 = s[:m.start()] + chunk2 = s[m.end():] + s = '%s %s' % (chunk1, chunk2) + else: + parseStr = s + s = '' + + if parseStr: + debug and log.debug( + 'found (time) [%s][%s][%s]', parseStr, chunk1, chunk2) + sourceTime = self._evalTimeStr(parseStr, sourceTime) + + return s, sourceTime, bool(parseStr) + + def _partialParseMeridian(self, s, sourceTime): + """ + test if giving C{s} matched CRE_TIMEHMS2, used by L{parse()} + + @type s: string + @param s: date/time text to evaluate + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: tuple + @return: tuple of remained date/time text, datetime object and + an boolean value to describ if matched or not + + """ + parseStr = None + chunk1 = chunk2 = '' + + # HH:MM(:SS) am/pm time strings + m = self.ptc.CRE_TIMEHMS2.search(s) + if m is not None: + + if m.group('minutes') is not None: + if m.group('seconds') is not None: + parseStr = '%s:%s:%s' % (m.group('hours'), + m.group('minutes'), + m.group('seconds')) + else: + parseStr = '%s:%s' % (m.group('hours'), + m.group('minutes')) + else: + parseStr = m.group('hours') + parseStr += ' ' + m.group('meridian') + + chunk1 = s[:m.start()] + chunk2 = s[m.end():] + + s = '%s %s' % (chunk1, chunk2) + + if parseStr: + debug and log.debug('found (meridian) [%s][%s][%s]', + parseStr, chunk1, chunk2) + sourceTime = self._evalMeridian(parseStr, sourceTime) + + return s, sourceTime, bool(parseStr) + + def _partialParseTimeStd(self, s, sourceTime): + """ + test if giving C{s} matched CRE_TIMEHMS, used by L{parse()} + + @type s: string + @param s: date/time text to evaluate + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + + @rtype: tuple + @return: tuple of remained date/time text, datetime object and + an boolean value to describ if matched or not + + """ + parseStr = None + chunk1 = chunk2 = '' + + # HH:MM(:SS) time strings + m = self.ptc.CRE_TIMEHMS.search(s) + if m is not None: + + if m.group('seconds') is not None: + parseStr = '%s:%s:%s' % (m.group('hours'), + m.group('minutes'), + m.group('seconds')) + chunk1 = s[:m.start('hours')] + chunk2 = s[m.end('seconds'):] + else: + parseStr = '%s:%s' % (m.group('hours'), + m.group('minutes')) + chunk1 = s[:m.start('hours')] + chunk2 = s[m.end('minutes'):] + + s = '%s %s' % (chunk1, chunk2) + + if parseStr: + debug and log.debug( + 'found (hms) [%s][%s][%s]', parseStr, chunk1, chunk2) + sourceTime = self._evalTimeStd(parseStr, sourceTime) + + return s, sourceTime, bool(parseStr) + + def parseDT(self, datetimeString, sourceTime=None, + tzinfo=None, version=None): + """ + C{datetimeString} is as C{.parse}, C{sourceTime} has the same semantic + meaning as C{.parse}, but now also accepts datetime objects. C{tzinfo} + accepts a tzinfo object. It is advisable to use pytz. + + + @type datetimeString: string + @param datetimeString: date/time text to evaluate + @type sourceTime: struct_time, datetime, date, time + @param sourceTime: time value to use as the base + @type tzinfo: tzinfo + @param tzinfo: Timezone to apply to generated datetime objs. + @type version: integer + @param version: style version, default will use L{Calendar} + parameter version value + + @rtype: tuple + @return: tuple of: modified C{sourceTime} and the result flag/context + + see .parse for return code details. + """ + # if sourceTime has a timetuple method, use thet, else, just pass the + # entire thing to parse and prey the user knows what the hell they are + # doing. + sourceTime = getattr(sourceTime, 'timetuple', (lambda: sourceTime))() + # You REALLY SHOULD be using pytz. Using localize if available, + # hacking if not. Note, None is a valid tzinfo object in the case of + # the ugly hack. + localize = getattr( + tzinfo, + 'localize', + (lambda dt: dt.replace(tzinfo=tzinfo)), # ugly hack is ugly :( + ) + + # Punt + time_struct, ret_code = self.parse( + datetimeString, + sourceTime=sourceTime, + version=version) + + # Comments from GHI indicate that it is desired to have the same return + # signature on this method as that one it punts to, with the exception + # of using datetime objects instead of time_structs. + dt = localize(datetime.datetime(*time_struct[:6])) + return dt, ret_code + + def parse(self, datetimeString, sourceTime=None, version=None): + """ + Splits the given C{datetimeString} into tokens, finds the regex + patterns that match and then calculates a C{struct_time} value from + the chunks. + + If C{sourceTime} is given then the C{struct_time} value will be + calculated from that value, otherwise from the current date/time. + + If the C{datetimeString} is parsed and date/time value found, then:: + + If C{version} equals to L{VERSION_FLAG_STYLE}, the second item of + the returned tuple will be a flag to let you know what kind of + C{struct_time} value is being returned:: + + 0 = not parsed at all + 1 = parsed as a C{date} + 2 = parsed as a C{time} + 3 = parsed as a C{datetime} + + If C{version} equals to L{VERSION_CONTEXT_STYLE}, the second value + will be an instance of L{pdtContext} + + @type datetimeString: string + @param datetimeString: date/time text to evaluate + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + @type version: integer + @param version: style version, default will use L{Calendar} + parameter version value + + @rtype: tuple + @return: tuple of: modified C{sourceTime} and the result flag/context + """ + debug and log.debug('parse()') + + datetimeString = re.sub(r'(\w)\.(\s)', r'\1\2', datetimeString) + datetimeString = re.sub(r'(\w)[\'"](\s|$)', r'\1 \2', datetimeString) + datetimeString = re.sub(r'(\s|^)[\'"](\w)', r'\1 \2', datetimeString) + + if sourceTime: + if isinstance(sourceTime, datetime.datetime): + debug and log.debug('coercing datetime to timetuple') + sourceTime = sourceTime.timetuple() + else: + if not isinstance(sourceTime, time.struct_time) and \ + not isinstance(sourceTime, tuple): + raise ValueError('sourceTime is not a struct_time') + else: + sourceTime = time.localtime() + + with self.context() as ctx: + s = datetimeString.lower().strip() + debug and log.debug('remainedString (before parsing): [%s]', s) + + while s: + for parseMeth in (self._partialParseModifier, + self._partialParseUnits, + self._partialParseQUnits, + self._partialParseDateStr, + self._partialParseDateStd, + self._partialParseDayStr, + self._partialParseWeekday, + self._partialParseTimeStr, + self._partialParseMeridian, + self._partialParseTimeStd): + retS, retTime, matched = parseMeth(s, sourceTime) + if matched: + s, sourceTime = retS.strip(), retTime + break + else: + # nothing matched + s = '' + + debug and log.debug('hasDate: [%s], hasTime: [%s]', + ctx.hasDate, ctx.hasTime) + debug and log.debug('remainedString: [%s]', s) + + # String is not parsed at all + if sourceTime is None: + debug and log.debug('not parsed [%s]', str(sourceTime)) + sourceTime = time.localtime() + + if not isinstance(sourceTime, time.struct_time): + sourceTime = time.struct_time(sourceTime) + + version = self.version if version is None else version + if version == VERSION_CONTEXT_STYLE: + return sourceTime, ctx + else: + return sourceTime, ctx.dateTimeFlag + + def inc(self, source, month=None, year=None): + """ + Takes the given C{source} date, or current date if none is + passed, and increments it according to the values passed in + by month and/or year. + + This routine is needed because Python's C{timedelta()} function + does not allow for month or year increments. + + @type source: struct_time + @param source: C{struct_time} value to increment + @type month: float or integer + @param month: optional number of months to increment + @type year: float or integer + @param year: optional number of years to increment + + @rtype: datetime + @return: C{source} incremented by the number of months and/or years + """ + yr = source.year + mth = source.month + dy = source.day + + try: + month = float(month) + except (TypeError, ValueError): + month = 0 + + try: + year = float(year) + except (TypeError, ValueError): + year = 0 + finally: + month += year * 12 + year = 0 + + subMi = 0.0 + maxDay = 0 + if month: + mi = int(month) + subMi = month - mi + + y = int(mi / 12.0) + m = mi - y * 12 + + mth = mth + m + if mth < 1: # cross start-of-year? + y -= 1 # yes - decrement year + mth += 12 # and fix month + elif mth > 12: # cross end-of-year? + y += 1 # yes - increment year + mth -= 12 # and fix month + + yr += y + + # if the day ends up past the last day of + # the new month, set it to the last day + maxDay = self.ptc.daysInMonth(mth, yr) + if dy > maxDay: + dy = maxDay + + if yr > datetime.MAXYEAR or yr < datetime.MINYEAR: + raise OverflowError('year is out of range') + + d = source.replace(year=yr, month=mth, day=dy) + if subMi: + d += datetime.timedelta(days=subMi * maxDay) + return source + (d - source) + + def nlp(self, inputString, sourceTime=None, version=None): + """Utilizes parse() after making judgements about what datetime + information belongs together. + + It makes logical groupings based on proximity and returns a parsed + datetime for each matched grouping of datetime text, along with + location info within the given inputString. + + @type inputString: string + @param inputString: natural language text to evaluate + @type sourceTime: struct_time + @param sourceTime: C{struct_time} value to use as the base + @type version: integer + @param version: style version, default will use L{Calendar} + parameter version value + + @rtype: tuple or None + @return: tuple of tuples in the format (parsed_datetime as + datetime.datetime, flags as int, start_pos as int, + end_pos as int, matched_text as string) or None if there + were no matches + """ + + orig_inputstring = inputString + + # replace periods at the end of sentences w/ spaces + # opposed to removing them altogether in order to + # retain relative positions (identified by alpha, period, space). + # this is required for some of the regex patterns to match + inputString = re.sub(r'(\w)(\.)(\s)', r'\1 \3', inputString).lower() + inputString = re.sub(r'(\w)(\'|")(\s|$)', r'\1 \3', inputString) + inputString = re.sub(r'(\s|^)(\'|")(\w)', r'\1 \3', inputString) + + startpos = 0 # the start position in the inputString during the loop + + # list of lists in format: + # [startpos, endpos, matchedstring, flags, type] + matches = [] + + while startpos < len(inputString): + + # empty match + leftmost_match = [0, 0, None, 0, None] + + # Modifier like next\prev.. + m = self.ptc.CRE_MODIFIER.search(inputString[startpos:]) + if m is not None: + if leftmost_match[1] == 0 or \ + leftmost_match[0] > m.start() + startpos: + leftmost_match[0] = m.start() + startpos + leftmost_match[1] = m.end() + startpos + leftmost_match[2] = m.group() + leftmost_match[3] = 0 + leftmost_match[4] = 'modifier' + + # Quantity + Units + m = self.ptc.CRE_UNITS.search(inputString[startpos:]) + if m is not None: + debug and log.debug('CRE_UNITS matched') + if self._UnitsTrapped(inputString[startpos:], m, 'units'): + debug and log.debug('day suffix trapped by unit match') + else: + + if leftmost_match[1] == 0 or \ + leftmost_match[0] > m.start('qty') + startpos: + leftmost_match[0] = m.start('qty') + startpos + leftmost_match[1] = m.end('qty') + startpos + leftmost_match[2] = m.group('qty') + leftmost_match[3] = 3 + leftmost_match[4] = 'units' + + if m.start('qty') > 0 and \ + inputString[m.start('qty') - 1] == '-': + leftmost_match[0] = leftmost_match[0] - 1 + leftmost_match[2] = '-' + leftmost_match[2] + + # Quantity + Units + m = self.ptc.CRE_QUNITS.search(inputString[startpos:]) + if m is not None: + debug and log.debug('CRE_QUNITS matched') + if self._UnitsTrapped(inputString[startpos:], m, 'qunits'): + debug and log.debug('day suffix trapped by qunit match') + else: + if leftmost_match[1] == 0 or \ + leftmost_match[0] > m.start('qty') + startpos: + leftmost_match[0] = m.start('qty') + startpos + leftmost_match[1] = m.end('qty') + startpos + leftmost_match[2] = m.group('qty') + leftmost_match[3] = 3 + leftmost_match[4] = 'qunits' + + if m.start('qty') > 0 and \ + inputString[m.start('qty') - 1] == '-': + leftmost_match[0] = leftmost_match[0] - 1 + leftmost_match[2] = '-' + leftmost_match[2] + + m = self.ptc.CRE_DATE3.search(inputString[startpos:]) + # NO LONGER NEEDED, THE REGEXP HANDLED MTHNAME NOW + # for match in self.ptc.CRE_DATE3.finditer(inputString[startpos:]): + # to prevent "HH:MM(:SS) time strings" expressions from + # triggering this regex, we checks if the month field exists + # in the searched expression, if it doesn't exist, the date + # field is not valid + # if match.group('mthname'): + # m = self.ptc.CRE_DATE3.search(inputString[startpos:], + # match.start()) + # break + + # String date format + if m is not None: + if leftmost_match[1] == 0 or \ + leftmost_match[0] > m.start('date') + startpos: + leftmost_match[0] = m.start('date') + startpos + leftmost_match[1] = m.end('date') + startpos + leftmost_match[2] = m.group('date') + leftmost_match[3] = 1 + leftmost_match[4] = 'dateStr' + + # Standard date format + m = self.ptc.CRE_DATE.search(inputString[startpos:]) + if m is not None: + if leftmost_match[1] == 0 or \ + leftmost_match[0] > m.start('date') + startpos: + leftmost_match[0] = m.start('date') + startpos + leftmost_match[1] = m.end('date') + startpos + leftmost_match[2] = m.group('date') + leftmost_match[3] = 1 + leftmost_match[4] = 'dateStd' + + # Natural language day strings + m = self.ptc.CRE_DAY.search(inputString[startpos:]) + if m is not None: + if leftmost_match[1] == 0 or \ + leftmost_match[0] > m.start() + startpos: + leftmost_match[0] = m.start() + startpos + leftmost_match[1] = m.end() + startpos + leftmost_match[2] = m.group() + leftmost_match[3] = 1 + leftmost_match[4] = 'dayStr' + + # Weekday + m = self.ptc.CRE_WEEKDAY.search(inputString[startpos:]) + if m is not None: + if inputString[startpos:] not in self.ptc.dayOffsets: + if leftmost_match[1] == 0 or \ + leftmost_match[0] > m.start() + startpos: + leftmost_match[0] = m.start() + startpos + leftmost_match[1] = m.end() + startpos + leftmost_match[2] = m.group() + leftmost_match[3] = 1 + leftmost_match[4] = 'weekdy' + + # Natural language time strings + m = self.ptc.CRE_TIME.search(inputString[startpos:]) + if m is not None: + if leftmost_match[1] == 0 or \ + leftmost_match[0] > m.start() + startpos: + leftmost_match[0] = m.start() + startpos + leftmost_match[1] = m.end() + startpos + leftmost_match[2] = m.group() + leftmost_match[3] = 2 + leftmost_match[4] = 'timeStr' + + # HH:MM(:SS) am/pm time strings + m = self.ptc.CRE_TIMEHMS2.search(inputString[startpos:]) + if m is not None: + if leftmost_match[1] == 0 or \ + leftmost_match[0] > m.start('hours') + startpos: + leftmost_match[0] = m.start('hours') + startpos + leftmost_match[1] = m.end('meridian') + startpos + leftmost_match[2] = inputString[leftmost_match[0]: + leftmost_match[1]] + leftmost_match[3] = 2 + leftmost_match[4] = 'meridian' + + # HH:MM(:SS) time strings + m = self.ptc.CRE_TIMEHMS.search(inputString[startpos:]) + if m is not None: + if leftmost_match[1] == 0 or \ + leftmost_match[0] > m.start('hours') + startpos: + leftmost_match[0] = m.start('hours') + startpos + if m.group('seconds') is not None: + leftmost_match[1] = m.end('seconds') + startpos + else: + leftmost_match[1] = m.end('minutes') + startpos + leftmost_match[2] = inputString[leftmost_match[0]: + leftmost_match[1]] + leftmost_match[3] = 2 + leftmost_match[4] = 'timeStd' + + # Units only; must be preceded by a modifier + if len(matches) > 0 and matches[-1][3] == 0: + m = self.ptc.CRE_UNITS_ONLY.search(inputString[startpos:]) + # Ensure that any match is immediately proceded by the + # modifier. "Next is the word 'month'" should not parse as a + # date while "next month" should + if m is not None and \ + inputString[startpos:startpos + + m.start()].strip() == '': + debug and log.debug('CRE_UNITS_ONLY matched [%s]', + m.group()) + if leftmost_match[1] == 0 or \ + leftmost_match[0] > m.start() + startpos: + leftmost_match[0] = m.start() + startpos + leftmost_match[1] = m.end() + startpos + leftmost_match[2] = m.group() + leftmost_match[3] = 3 + leftmost_match[4] = 'unitsOnly' + + # set the start position to the end pos of the leftmost match + startpos = leftmost_match[1] + + # nothing was detected + # so break out of the loop + if startpos == 0: + startpos = len(inputString) + else: + if leftmost_match[3] > 0: + m = self.ptc.CRE_NLP_PREFIX.search( + inputString[:leftmost_match[0]] + + ' ' + str(leftmost_match[3])) + if m is not None: + leftmost_match[0] = m.start('nlp_prefix') + leftmost_match[2] = inputString[leftmost_match[0]: + leftmost_match[1]] + matches.append(leftmost_match) + + # find matches in proximity with one another and + # return all the parsed values + proximity_matches = [] + if len(matches) > 1: + combined = '' + from_match_index = 0 + date = matches[0][3] == 1 + time = matches[0][3] == 2 + units = matches[0][3] == 3 + for i in range(1, len(matches)): + + # test proximity (are there characters between matches?) + endofprevious = matches[i - 1][1] + begofcurrent = matches[i][0] + if orig_inputstring[endofprevious: + begofcurrent].lower().strip() != '': + # this one isn't in proximity, but maybe + # we have enough to make a datetime + # TODO: make sure the combination of + # formats (modifier, dateStd, etc) makes logical sense + # before parsing together + if date or time or units: + combined = orig_inputstring[matches[from_match_index] + [0]:matches[i - 1][1]] + parsed_datetime, flags = self.parse(combined, + sourceTime, + version) + proximity_matches.append(( + datetime.datetime(*parsed_datetime[:6]), + flags, + matches[from_match_index][0], + matches[i - 1][1], + combined)) + # not in proximity, reset starting from current + from_match_index = i + date = matches[i][3] == 1 + time = matches[i][3] == 2 + units = matches[i][3] == 3 + continue + else: + if matches[i][3] == 1: + date = True + if matches[i][3] == 2: + time = True + if matches[i][3] == 3: + units = True + + # check last + # we have enough to make a datetime + if date or time or units: + combined = orig_inputstring[matches[from_match_index][0]: + matches[len(matches) - 1][1]] + parsed_datetime, flags = self.parse(combined, sourceTime, + version) + proximity_matches.append(( + datetime.datetime(*parsed_datetime[:6]), + flags, + matches[from_match_index][0], + matches[len(matches) - 1][1], + combined)) + + elif len(matches) == 0: + return None + else: + if matches[0][3] == 0: # not enough info to parse + return None + else: + combined = orig_inputstring[matches[0][0]:matches[0][1]] + parsed_datetime, flags = self.parse(matches[0][2], sourceTime, + version) + proximity_matches.append(( + datetime.datetime(*parsed_datetime[:6]), + flags, + matches[0][0], + matches[0][1], + combined)) + + return tuple(proximity_matches) + + +def _initSymbols(ptc): + """ + Initialize symbols and single character constants. + """ + # build am and pm lists to contain + # original case, lowercase, first-char and dotted + # versions of the meridian text + ptc.am = ['', ''] + ptc.pm = ['', ''] + for idx, xm in enumerate(ptc.locale.meridian[:2]): + # 0: am + # 1: pm + target = ['am', 'pm'][idx] + setattr(ptc, target, [xm]) + target = getattr(ptc, target) + if xm: + lxm = xm.lower() + target.extend((xm[0], '{0}.{1}.'.format(*xm), + lxm, lxm[0], '{0}.{1}.'.format(*lxm))) + + +class Constants(object): + + """ + Default set of constants for parsedatetime. + + If PyICU is present, then the class will first try to get PyICU + to return a locale specified by C{localeID}. If either C{localeID} is + None or if the locale does not exist within PyICU, then each of the + locales defined in C{fallbackLocales} is tried in order. + + If PyICU is not present or none of the specified locales can be used, + then the class will initialize itself to the en_US locale. + + if PyICU is not present or not requested, only the locales defined by + C{pdtLocales} will be searched. + """ + + def __init__(self, localeID=None, usePyICU=True, + fallbackLocales=['en_US']): + self.localeID = localeID + self.fallbackLocales = fallbackLocales[:] + + if 'en_US' not in self.fallbackLocales: + self.fallbackLocales.append('en_US') + + # define non-locale specific constants + self.locale = None + self.usePyICU = usePyICU + + # starting cache of leap years + # daysInMonth will add to this if during + # runtime it gets a request for a year not found + self._leapYears = list(range(1904, 2097, 4)) + + self.Second = 1 + self.Minute = 60 # 60 * self.Second + self.Hour = 3600 # 60 * self.Minute + self.Day = 86400 # 24 * self.Hour + self.Week = 604800 # 7 * self.Day + self.Month = 2592000 # 30 * self.Day + self.Year = 31536000 # 365 * self.Day + + self._DaysInMonthList = (31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31) + self.rangeSep = '-' + self.BirthdayEpoch = 50 + + # When True the starting time for all relative calculations will come + # from the given SourceTime, otherwise it will be 9am + + self.StartTimeFromSourceTime = False + + # YearParseStyle controls how we parse "Jun 12", i.e. dates that do + # not have a year present. The default is to compare the date given + # to the current date, and if prior, then assume the next year. + # Setting this to 0 will prevent that. + + self.YearParseStyle = 1 + + # DOWParseStyle controls how we parse "Tuesday" + # If the current day was Thursday and the text to parse is "Tuesday" + # then the following table shows how each style would be returned + # -1, 0, +1 + # + # Current day marked as *** + # + # Sun Mon Tue Wed Thu Fri Sat + # week -1 + # current -1,0 *** + # week +1 +1 + # + # If the current day was Monday and the text to parse is "Tuesday" + # then the following table shows how each style would be returned + # -1, 0, +1 + # + # Sun Mon Tue Wed Thu Fri Sat + # week -1 -1 + # current *** 0,+1 + # week +1 + + self.DOWParseStyle = 1 + + # CurrentDOWParseStyle controls how we parse "Friday" + # If the current day was Friday and the text to parse is "Friday" + # then the following table shows how each style would be returned + # True/False. This also depends on DOWParseStyle. + # + # Current day marked as *** + # + # DOWParseStyle = 0 + # Sun Mon Tue Wed Thu Fri Sat + # week -1 + # current T,F + # week +1 + # + # DOWParseStyle = -1 + # Sun Mon Tue Wed Thu Fri Sat + # week -1 F + # current T + # week +1 + # + # DOWParseStyle = +1 + # + # Sun Mon Tue Wed Thu Fri Sat + # week -1 + # current T + # week +1 F + + self.CurrentDOWParseStyle = False + + if self.usePyICU: + self.locale = get_icu(self.localeID) + + if self.locale.icu is None: + self.usePyICU = False + self.locale = None + + if self.locale is None: + if self.localeID not in pdtLocales: + for localeId in range(0, len(self.fallbackLocales)): + self.localeID = self.fallbackLocales[localeId] + if self.localeID in pdtLocales: + break + + self.locale = pdtLocales[self.localeID] + + if self.locale is not None: + + def _getLocaleDataAdjusted(localeData): + """ + If localeData is defined as ["mon|mnd", 'tu|tues'...] then this + function splits those definitions on | + """ + adjusted = [] + for d in localeData: + if '|' in d: + adjusted += d.split("|") + else: + adjusted.append(d) + return adjusted + + mths = _getLocaleDataAdjusted(self.locale.Months) + smths = _getLocaleDataAdjusted(self.locale.shortMonths) + swds = _getLocaleDataAdjusted(self.locale.shortWeekdays) + wds = _getLocaleDataAdjusted(self.locale.Weekdays) + + re_join = lambda g: '|'.join(re.escape(i) for i in g) + + # escape any regex special characters that may be found + self.locale.re_values['months'] = re_join(mths) + self.locale.re_values['shortmonths'] = re_join(smths) + self.locale.re_values['days'] = re_join(wds) + self.locale.re_values['shortdays'] = re_join(swds) + self.locale.re_values['dayoffsets'] = \ + re_join(self.locale.dayOffsets) + self.locale.re_values['numbers'] = \ + re_join(self.locale.numbers) + self.locale.re_values['decimal_mark'] = \ + re.escape(self.locale.decimal_mark) + + units = [unit for units in self.locale.units.values() + for unit in units] # flatten + units.sort(key=len, reverse=True) # longest first + self.locale.re_values['units'] = re_join(units) + self.locale.re_values['modifiers'] = re_join(self.locale.Modifiers) + self.locale.re_values['sources'] = re_join(self.locale.re_sources) + + # For distinguishing numeric dates from times, look for timeSep + # and meridian, if specified in the locale + self.locale.re_values['timecomponents'] = \ + re_join(self.locale.timeSep + self.locale.meridian) + + # build weekday offsets - yes, it assumes the Weekday and + # shortWeekday lists are in the same order and Mon..Sun + # (Python style) + def _buildOffsets(offsetDict, localeData, indexStart): + o = indexStart + for key in localeData: + if '|' in key: + for k in key.split('|'): + offsetDict[k] = o + else: + offsetDict[key] = o + o += 1 + + _buildOffsets(self.locale.WeekdayOffsets, + self.locale.Weekdays, 0) + _buildOffsets(self.locale.WeekdayOffsets, + self.locale.shortWeekdays, 0) + + # build month offsets - yes, it assumes the Months and shortMonths + # lists are in the same order and Jan..Dec + _buildOffsets(self.locale.MonthOffsets, + self.locale.Months, 1) + _buildOffsets(self.locale.MonthOffsets, + self.locale.shortMonths, 1) + + _initSymbols(self) + + # TODO: add code to parse the date formats and build the regexes up + # from sub-parts, find all hard-coded uses of date/time separators + + # not being used in code, but kept in case others are manually + # utilizing this regex for their own purposes + self.RE_DATE4 = r'''(?P<date> + ( + ( + (?P<day>\d\d?) + (?P<suffix>{daysuffix})? + (,)? + (\s)? + ) + (?P<mthname> + \b({months}|{shortmonths})\b + )\s? + (?P<year>\d\d + (\d\d)? + )? + ) + )'''.format(**self.locale.re_values) + + # still not completely sure of the behavior of the regex and + # whether it would be best to consume all possible irrelevant + # characters before the option groups (but within the {1,3} repetition + # group or inside of each option group, as it currently does + # however, right now, all tests are passing that were, + # including fixing the bug of matching a 4-digit year as ddyy + # when the day is absent from the string + self.RE_DATE3 = r'''(?P<date> + (?: + (?:^|\s) + (?P<mthname> + {months}|{shortmonths} + )\b + | + (?:^|\s) + (?P<day>[1-9]|[012]\d|3[01]) + (?P<suffix>{daysuffix}|)\b + (?!\s*(?:{timecomponents})) + | + ,?\s + (?P<year>\d\d(?:\d\d|))\b + (?!\s*(?:{timecomponents})) + ){{1,3}} + (?(mthname)|$-^) + )'''.format(**self.locale.re_values) + + # not being used in code, but kept in case others are manually + # utilizing this regex for their own purposes + self.RE_MONTH = r'''(\s|^) + (?P<month> + ( + (?P<mthname> + \b({months}|{shortmonths})\b + ) + (\s? + (?P<year>(\d{{4}})) + )? + ) + ) + (?=\s|$|[^\w])'''.format(**self.locale.re_values) + + self.RE_WEEKDAY = r'''\b + (?: + {days}|{shortdays} + ) + \b'''.format(**self.locale.re_values) + + self.RE_NUMBER = (r'(\b(?:{numbers})\b|\d+(?:{decimal_mark}\d+|))' + .format(**self.locale.re_values)) + + self.RE_SPECIAL = (r'(?P<special>^[{specials}]+)\s+' + .format(**self.locale.re_values)) + + self.RE_UNITS_ONLY = (r'''\b({units})\b''' + .format(**self.locale.re_values)) + + self.RE_UNITS = r'''\b(?P<qty> + -? + (?:\d+(?:{decimal_mark}\d+|)|(?:{numbers})\b)\s* + (?P<units>{units}) + )\b'''.format(**self.locale.re_values) + + self.RE_QUNITS = r'''\b(?P<qty> + -? + (?:\d+(?:{decimal_mark}\d+|)|(?:{numbers})s)\s? + (?P<qunits>{qunits}) + )\b'''.format(**self.locale.re_values) + + self.RE_MODIFIER = r'''\b(?: + {modifiers} + )\b'''.format(**self.locale.re_values) + + self.RE_TIMEHMS = r'''([\s(\["'-]|^) + (?P<hours>\d\d?) + (?P<tsep>{timeseparator}|) + (?P<minutes>\d\d) + (?:(?P=tsep) + (?P<seconds>\d\d + (?:[\.,]\d+)? + ) + )?\b'''.format(**self.locale.re_values) + + self.RE_TIMEHMS2 = r'''([\s(\["'-]|^) + (?P<hours>\d\d?) + (?: + (?P<tsep>{timeseparator}|) + (?P<minutes>\d\d?) + (?:(?P=tsep) + (?P<seconds>\d\d? + (?:[\.,]\d+)? + ) + )? + )?'''.format(**self.locale.re_values) + + # 1, 2, and 3 here refer to the type of match date, time, or units + self.RE_NLP_PREFIX = r'''\b(?P<nlp_prefix> + (on) + (\s)+1 + | + (at|in) + (\s)+2 + | + (in) + (\s)+3 + )''' + + if 'meridian' in self.locale.re_values: + self.RE_TIMEHMS2 += (r'\s?(?P<meridian>{meridian})\b' + .format(**self.locale.re_values)) + else: + self.RE_TIMEHMS2 += r'\b' + + # Always support common . and - separators + dateSeps = ''.join(re.escape(s) + for s in self.locale.dateSep + ['-', '.']) + + self.RE_DATE = r'''([\s(\["'-]|^) + (?P<date> + \d\d?[{0}]\d\d?(?:[{0}]\d\d(?:\d\d)?)? + | + \d{{4}}[{0}]\d\d?[{0}]\d\d? + ) + \b'''.format(dateSeps) + + self.RE_DATE2 = r'[{0}]'.format(dateSeps) + + assert 'dayoffsets' in self.locale.re_values + + self.RE_DAY = r'''\b + (?: + {dayoffsets} + ) + \b'''.format(**self.locale.re_values) + + self.RE_DAY2 = r'''(?P<day>\d\d?) + (?P<suffix>{daysuffix})? + '''.format(**self.locale.re_values) + + self.RE_TIME = r'''\b + (?: + {sources} + ) + \b'''.format(**self.locale.re_values) + + self.RE_REMAINING = r'\s+' + + # Regex for date/time ranges + self.RE_RTIMEHMS = r'''(\s?|^) + (\d\d?){timeseparator} + (\d\d) + ({timeseparator}(\d\d))? + (\s?|$)'''.format(**self.locale.re_values) + + self.RE_RTIMEHMS2 = (r'''(\s?|^) + (\d\d?) + ({timeseparator}(\d\d?))? + ({timeseparator}(\d\d?))?''' + .format(**self.locale.re_values)) + + if 'meridian' in self.locale.re_values: + self.RE_RTIMEHMS2 += (r'\s?({meridian})' + .format(**self.locale.re_values)) + + self.RE_RDATE = r'(\d+([%s]\d+)+)' % dateSeps + self.RE_RDATE3 = r'''( + ( + ( + \b({months})\b + )\s? + ( + (\d\d?) + (\s?|{daysuffix}|$)+ + )? + (,\s?\d{{4}})? + ) + )'''.format(**self.locale.re_values) + + # "06/07/06 - 08/09/06" + self.DATERNG1 = (r'{0}\s?{rangeseparator}\s?{0}' + .format(self.RE_RDATE, **self.locale.re_values)) + + # "march 31 - june 1st, 2006" + self.DATERNG2 = (r'{0}\s?{rangeseparator}\s?{0}' + .format(self.RE_RDATE3, **self.locale.re_values)) + + # "march 1rd -13th" + self.DATERNG3 = (r'{0}\s?{rangeseparator}\s?(\d\d?)\s?(rd|st|nd|th)?' + .format(self.RE_RDATE3, **self.locale.re_values)) + + # "4:00:55 pm - 5:90:44 am", '4p-5p' + self.TIMERNG1 = (r'{0}\s?{rangeseparator}\s?{0}' + .format(self.RE_RTIMEHMS2, **self.locale.re_values)) + + self.TIMERNG2 = (r'{0}\s?{rangeseparator}\s?{0}' + .format(self.RE_RTIMEHMS, **self.locale.re_values)) + + # "4-5pm " + self.TIMERNG3 = (r'\d\d?\s?{rangeseparator}\s?{0}' + .format(self.RE_RTIMEHMS2, **self.locale.re_values)) + + # "4:30-5pm " + self.TIMERNG4 = (r'{0}\s?{rangeseparator}\s?{1}' + .format(self.RE_RTIMEHMS, self.RE_RTIMEHMS2, + **self.locale.re_values)) + + self.re_option = re.IGNORECASE + re.VERBOSE + self.cre_source = {'CRE_SPECIAL': self.RE_SPECIAL, + 'CRE_NUMBER': self.RE_NUMBER, + 'CRE_UNITS': self.RE_UNITS, + 'CRE_UNITS_ONLY': self.RE_UNITS_ONLY, + 'CRE_QUNITS': self.RE_QUNITS, + 'CRE_MODIFIER': self.RE_MODIFIER, + 'CRE_TIMEHMS': self.RE_TIMEHMS, + 'CRE_TIMEHMS2': self.RE_TIMEHMS2, + 'CRE_DATE': self.RE_DATE, + 'CRE_DATE2': self.RE_DATE2, + 'CRE_DATE3': self.RE_DATE3, + 'CRE_DATE4': self.RE_DATE4, + 'CRE_MONTH': self.RE_MONTH, + 'CRE_WEEKDAY': self.RE_WEEKDAY, + 'CRE_DAY': self.RE_DAY, + 'CRE_DAY2': self.RE_DAY2, + 'CRE_TIME': self.RE_TIME, + 'CRE_REMAINING': self.RE_REMAINING, + 'CRE_RTIMEHMS': self.RE_RTIMEHMS, + 'CRE_RTIMEHMS2': self.RE_RTIMEHMS2, + 'CRE_RDATE': self.RE_RDATE, + 'CRE_RDATE3': self.RE_RDATE3, + 'CRE_TIMERNG1': self.TIMERNG1, + 'CRE_TIMERNG2': self.TIMERNG2, + 'CRE_TIMERNG3': self.TIMERNG3, + 'CRE_TIMERNG4': self.TIMERNG4, + 'CRE_DATERNG1': self.DATERNG1, + 'CRE_DATERNG2': self.DATERNG2, + 'CRE_DATERNG3': self.DATERNG3, + 'CRE_NLP_PREFIX': self.RE_NLP_PREFIX} + self.cre_keys = set(self.cre_source.keys()) + + def __getattr__(self, name): + if name in self.cre_keys: + value = re.compile(self.cre_source[name], self.re_option) + setattr(self, name, value) + return value + elif name in self.locale.locale_keys: + return getattr(self.locale, name) + else: + raise AttributeError(name) + + def daysInMonth(self, month, year): + """ + Take the given month (1-12) and a given year (4 digit) return + the number of days in the month adjusting for leap year as needed + """ + result = None + debug and log.debug('daysInMonth(%s, %s)', month, year) + if month > 0 and month <= 12: + result = self._DaysInMonthList[month - 1] + + if month == 2: + if year in self._leapYears: + result += 1 + else: + if calendar.isleap(year): + self._leapYears.append(year) + result += 1 + + return result + + def getSource(self, sourceKey, sourceTime=None): + """ + GetReturn a date/time tuple based on the giving source key + and the corresponding key found in self.re_sources. + + The current time is used as the default and any specified + item found in self.re_sources is inserted into the value + and the generated dictionary is returned. + """ + if sourceKey not in self.re_sources: + return None + + if sourceTime is None: + (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime() + else: + (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime + + defaults = {'yr': yr, 'mth': mth, 'dy': dy, + 'hr': hr, 'mn': mn, 'sec': sec} + + source = self.re_sources[sourceKey] + + values = {} + + for key, default in defaults.items(): + values[key] = source.get(key, default) + + return (values['yr'], values['mth'], values['dy'], + values['hr'], values['mn'], values['sec'], + wd, yd, isdst)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/support/parsedatetime/context.py Tue Sep 06 00:09:31 2016 +0200 @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +""" +parsedatetime/context.py + +Context related classes + +""" + +from threading import local + + +class pdtContextStack(object): + """ + A thread-safe stack to store context(s) + + Internally used by L{Calendar} object + """ + + def __init__(self): + self.__local = local() + + @property + def __stack(self): + if not hasattr(self.__local, 'stack'): + self.__local.stack = [] + return self.__local.stack + + def push(self, ctx): + self.__stack.append(ctx) + + def pop(self): + try: + return self.__stack.pop() + except IndexError: + return None + + def last(self): + try: + return self.__stack[-1] + except IndexError: + raise RuntimeError('context stack is empty') + + def isEmpty(self): + return not self.__stack + + +class pdtContext(object): + """ + Context contains accuracy flag detected by L{Calendar.parse()} + + Accuracy flag uses bitwise-OR operation and is combined by: + + ACU_YEAR - "next year", "2014" + ACU_MONTH - "March", "July 2014" + ACU_WEEK - "last week", "next 3 weeks" + ACU_DAY - "tomorrow", "July 4th 2014" + ACU_HALFDAY - "morning", "tonight" + ACU_HOUR - "18:00", "next hour" + ACU_MIN - "18:32", "next 10 minutes" + ACU_SEC - "18:32:55" + ACU_NOW - "now" + + """ + + __slots__ = ('accuracy',) + + ACU_YEAR = 2 ** 0 + ACU_MONTH = 2 ** 1 + ACU_WEEK = 2 ** 2 + ACU_DAY = 2 ** 3 + ACU_HALFDAY = 2 ** 4 + ACU_HOUR = 2 ** 5 + ACU_MIN = 2 ** 6 + ACU_SEC = 2 ** 7 + ACU_NOW = 2 ** 8 + + ACU_DATE = ACU_YEAR | ACU_MONTH | ACU_WEEK | ACU_DAY + ACU_TIME = ACU_HALFDAY | ACU_HOUR | ACU_MIN | ACU_SEC | ACU_NOW + + _ACCURACY_MAPPING = [ + (ACU_YEAR, 'year'), + (ACU_MONTH, 'month'), + (ACU_WEEK, 'week'), + (ACU_DAY, 'day'), + (ACU_HALFDAY, 'halfday'), + (ACU_HOUR, 'hour'), + (ACU_MIN, 'min'), + (ACU_SEC, 'sec'), + (ACU_NOW, 'now')] + + _ACCURACY_REVERSE_MAPPING = { + 'year': ACU_YEAR, + 'years': ACU_YEAR, + 'month': ACU_MONTH, + 'months': ACU_MONTH, + 'week': ACU_WEEK, + 'weeks': ACU_WEEK, + 'day': ACU_DAY, + 'days': ACU_DAY, + 'halfday': ACU_HALFDAY, + 'morning': ACU_HALFDAY, + 'afternoon': ACU_HALFDAY, + 'evening': ACU_HALFDAY, + 'night': ACU_HALFDAY, + 'tonight': ACU_HALFDAY, + 'midnight': ACU_HALFDAY, + 'hour': ACU_HOUR, + 'hours': ACU_HOUR, + 'min': ACU_MIN, + 'minute': ACU_MIN, + 'mins': ACU_MIN, + 'minutes': ACU_MIN, + 'sec': ACU_SEC, + 'second': ACU_SEC, + 'secs': ACU_SEC, + 'seconds': ACU_SEC, + 'now': ACU_NOW} + + def __init__(self, accuracy=0): + """ + Default constructor of L{pdtContext} class. + + @type accuracy: integer + @param accuracy: Accuracy flag + + @rtype: object + @return: L{pdtContext} instance + """ + self.accuracy = accuracy + + def updateAccuracy(self, *accuracy): + """ + Updates current accuracy flag + """ + for acc in accuracy: + if not isinstance(acc, int): + acc = self._ACCURACY_REVERSE_MAPPING[acc] + self.accuracy |= acc + + def update(self, context): + """ + Uses another L{pdtContext} instance to update current one + """ + self.updateAccuracy(context.accuracy) + + @property + def hasDate(self): + """ + Returns True if current context is accurate to date + """ + return bool(self.accuracy & self.ACU_DATE) + + @property + def hasTime(self): + """ + Returns True if current context is accurate to time + """ + return bool(self.accuracy & self.ACU_TIME) + + @property + def dateTimeFlag(self): + """ + Returns the old date/time flag code + """ + return int(self.hasDate and 1) | int(self.hasTime and 2) + + @property + def hasDateOrTime(self): + """ + Returns True if current context is accurate to date/time + """ + return bool(self.accuracy) + + def __repr__(self): + accuracy_repr = [] + for acc, name in self._ACCURACY_MAPPING: + if acc & self.accuracy: + accuracy_repr.append('pdtContext.ACU_%s' % name.upper()) + if accuracy_repr: + accuracy_repr = 'accuracy=' + ' | '.join(accuracy_repr) + else: + accuracy_repr = '' + + return 'pdtContext(%s)' % accuracy_repr + + def __eq__(self, ctx): + return self.accuracy == ctx.accuracy
--- a/MoinMoin/support/parsedatetime/parsedatetime.py Mon Sep 05 23:55:33 2016 +0200 +++ b/MoinMoin/support/parsedatetime/parsedatetime.py Tue Sep 06 00:09:31 2016 +0200 @@ -1,1541 +1,2 @@ -#!/usr/bin/env python - -""" -Parse human-readable date/time text. -""" - -__license__ = """ -Copyright (c) 2004-2008 Mike Taylor -Copyright (c) 2006-2008 Darshana Chhajed -All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -_debug = False - - -import re -import time -import datetime -import rfc822 -import parsedatetime_consts - - -# Copied from feedparser.py -# Universal Feedparser -# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved. -# Originally a def inside of _parse_date_w3dtf() -def _extract_date(m): - year = int(m.group('year')) - if year < 100: - year = 100 * int(time.gmtime()[0] / 100) + int(year) - if year < 1000: - return 0, 0, 0 - julian = m.group('julian') - if julian: - julian = int(julian) - month = julian / 30 + 1 - day = julian % 30 + 1 - jday = None - while jday != julian: - t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0)) - jday = time.gmtime(t)[-2] - diff = abs(jday - julian) - if jday > julian: - if diff < day: - day = day - diff - else: - month = month - 1 - day = 31 - elif jday < julian: - if day + diff < 28: - day = day + diff - else: - month = month + 1 - return year, month, day - month = m.group('month') - day = 1 - if month is None: - month = 1 - else: - month = int(month) - day = m.group('day') - if day: - day = int(day) - else: - day = 1 - return year, month, day - -# Copied from feedparser.py -# Universal Feedparser -# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved. -# Originally a def inside of _parse_date_w3dtf() -def _extract_time(m): - if not m: - return 0, 0, 0 - hours = m.group('hours') - if not hours: - return 0, 0, 0 - hours = int(hours) - minutes = int(m.group('minutes')) - seconds = m.group('seconds') - if seconds: - seconds = int(seconds) - else: - seconds = 0 - return hours, minutes, seconds - - -# Copied from feedparser.py -# Universal Feedparser -# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved. -# Modified to return a tuple instead of mktime -# -# Original comment: -# W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by -# Drake and licensed under the Python license. Removed all range checking -# for month, day, hour, minute, and second, since mktime will normalize -# these later -def _parse_date_w3dtf(dateString): - # the __extract_date and __extract_time methods were - # copied-out so they could be used by my code --bear - def __extract_tzd(m): - '''Return the Time Zone Designator as an offset in seconds from UTC.''' - if not m: - return 0 - tzd = m.group('tzd') - if not tzd: - return 0 - if tzd == 'Z': - return 0 - hours = int(m.group('tzdhours')) - minutes = m.group('tzdminutes') - if minutes: - minutes = int(minutes) - else: - minutes = 0 - offset = (hours*60 + minutes) * 60 - if tzd[0] == '+': - return -offset - return offset - - __date_re = ('(?P<year>\d\d\d\d)' - '(?:(?P<dsep>-|)' - '(?:(?P<julian>\d\d\d)' - '|(?P<month>\d\d)(?:(?P=dsep)(?P<day>\d\d))?))?') - __tzd_re = '(?P<tzd>[-+](?P<tzdhours>\d\d)(?::?(?P<tzdminutes>\d\d))|Z)' - __tzd_rx = re.compile(__tzd_re) - __time_re = ('(?P<hours>\d\d)(?P<tsep>:|)(?P<minutes>\d\d)' - '(?:(?P=tsep)(?P<seconds>\d\d(?:[.,]\d+)?))?' - + __tzd_re) - __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re) - __datetime_rx = re.compile(__datetime_re) - m = __datetime_rx.match(dateString) - if (m is None) or (m.group() != dateString): return - return _extract_date(m) + _extract_time(m) + (0, 0, 0) - - -# Copied from feedparser.py -# Universal Feedparser -# Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved. -# Modified to return a tuple instead of mktime -# -def _parse_date_rfc822(dateString): - '''Parse an RFC822, RFC1123, RFC2822, or asctime-style date''' - data = dateString.split() - if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames: - del data[0] - if len(data) == 4: - s = data[3] - i = s.find('+') - if i > 0: - data[3:] = [s[:i], s[i+1:]] - else: - data.append('') - dateString = " ".join(data) - if len(data) < 5: - dateString += ' 00:00:00 GMT' - return rfc822.parsedate_tz(dateString) - -# rfc822.py defines several time zones, but we define some extra ones. -# 'ET' is equivalent to 'EST', etc. -_additional_timezones = {'AT': -400, 'ET': -500, - 'CT': -600, 'MT': -700, - 'PT': -800} -rfc822._timezones.update(_additional_timezones) - - -class Calendar: - """ - A collection of routines to input, parse and manipulate date and times. - The text can either be 'normal' date values or it can be human readable. - """ - - def __init__(self, constants=None): - """ - Default constructor for the L{Calendar} class. - - @type constants: object - @param constants: Instance of the class L{parsedatetime_consts.Constants} - - @rtype: object - @return: L{Calendar} instance - """ - # if a constants reference is not included, use default - if constants is None: - self.ptc = parsedatetime_consts.Constants() - else: - self.ptc = constants - - self.weekdyFlag = False # monday/tuesday/... - self.dateStdFlag = False # 07/21/06 - self.dateStrFlag = False # July 21st, 2006 - self.timeStdFlag = False # 5:50 - self.meridianFlag = False # am/pm - self.dayStrFlag = False # tomorrow/yesterday/today/.. - self.timeStrFlag = False # lunch/noon/breakfast/... - self.modifierFlag = False # after/before/prev/next/.. - self.modifier2Flag = False # after/before/prev/next/.. - self.unitsFlag = False # hrs/weeks/yrs/min/.. - self.qunitsFlag = False # h/m/t/d.. - - self.timeFlag = 0 - self.dateFlag = 0 - - - def _convertUnitAsWords(self, unitText): - """ - Converts text units into their number value - - Five = 5 - Twenty Five = 25 - Two hundred twenty five = 225 - Two thousand and twenty five = 2025 - Two thousand twenty five = 2025 - - @type unitText: string - @param unitText: number text to convert - - @rtype: integer - @return: numerical value of unitText - """ - # TODO: implement this - pass - - - def _buildTime(self, source, quantity, modifier, units): - """ - Take C{quantity}, C{modifier} and C{unit} strings and convert them into values. - After converting, calcuate the time and return the adjusted sourceTime. - - @type source: time - @param source: time to use as the base (or source) - @type quantity: string - @param quantity: quantity string - @type modifier: string - @param modifier: how quantity and units modify the source time - @type units: string - @param units: unit of the quantity (i.e. hours, days, months, etc) - - @rtype: struct_time - @return: C{struct_time} of the calculated time - """ - if _debug: - print '_buildTime: [%s][%s][%s]' % (quantity, modifier, units) - - if source is None: - source = time.localtime() - - if quantity is None: - quantity = '' - else: - quantity = quantity.strip() - - if len(quantity) == 0: - qty = 1 - else: - try: - qty = int(quantity) - except ValueError: - qty = 0 - - if modifier in self.ptc.Modifiers: - qty = qty * self.ptc.Modifiers[modifier] - - if units is None or units == '': - units = 'dy' - - # plurals are handled by regex's (could be a bug tho) - - (yr, mth, dy, hr, mn, sec, _, _, _) = source - - start = datetime.datetime(yr, mth, dy, hr, mn, sec) - target = start - - if units.startswith('y'): - target = self.inc(start, year=qty) - self.dateFlag = 1 - elif units.endswith('th') or units.endswith('ths'): - target = self.inc(start, month=qty) - self.dateFlag = 1 - else: - if units.startswith('d'): - target = start + datetime.timedelta(days=qty) - self.dateFlag = 1 - elif units.startswith('h'): - target = start + datetime.timedelta(hours=qty) - self.timeFlag = 2 - elif units.startswith('m'): - target = start + datetime.timedelta(minutes=qty) - self.timeFlag = 2 - elif units.startswith('s'): - target = start + datetime.timedelta(seconds=qty) - self.timeFlag = 2 - elif units.startswith('w'): - target = start + datetime.timedelta(weeks=qty) - self.dateFlag = 1 - - return target.timetuple() - - - def parseDate(self, dateString): - """ - Parse short-form date strings:: - - '05/28/2006' or '04.21' - - @type dateString: string - @param dateString: text to convert to a C{datetime} - - @rtype: struct_time - @return: calculated C{struct_time} value of dateString - """ - yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime() - - # values pulled from regex's will be stored here and later - # assigned to mth, dy, yr based on information from the locale - # -1 is used as the marker value because we want zero values - # to be passed thru so they can be flagged as errors later - v1 = -1 - v2 = -1 - v3 = -1 - - s = dateString - m = self.ptc.CRE_DATE2.search(s) - if m is not None: - index = m.start() - v1 = int(s[:index]) - s = s[index + 1:] - - m = self.ptc.CRE_DATE2.search(s) - if m is not None: - index = m.start() - v2 = int(s[:index]) - v3 = int(s[index + 1:]) - else: - v2 = int(s.strip()) - - v = [ v1, v2, v3 ] - d = { 'm': mth, 'd': dy, 'y': yr } - - for i in range(0, 3): - n = v[i] - c = self.ptc.dp_order[i] - if n >= 0: - d[c] = n - - # if the year is not specified and the date has already - # passed, increment the year - if v3 == -1 and ((mth > d['m']) or (mth == d['m'] and dy > d['d'])): - yr = d['y'] + 1 - else: - yr = d['y'] - - mth = d['m'] - dy = d['d'] - - # birthday epoch constraint - if yr < self.ptc.BirthdayEpoch: - yr += 2000 - elif yr < 100: - yr += 1900 - - if _debug: - print 'parseDate: ', yr, mth, dy, self.ptc.daysInMonth(mth, yr) - - if (mth > 0 and mth <= 12) and \ - (dy > 0 and dy <= self.ptc.daysInMonth(mth, yr)): - sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) - else: - self.dateFlag = 0 - self.timeFlag = 0 - sourceTime = time.localtime() # return current time if date - # string is invalid - - return sourceTime - - - def parseDateText(self, dateString): - """ - Parse long-form date strings:: - - 'May 31st, 2006' - 'Jan 1st' - 'July 2006' - - @type dateString: string - @param dateString: text to convert to a datetime - - @rtype: struct_time - @return: calculated C{struct_time} value of dateString - """ - yr, mth, dy, hr, mn, sec, wd, yd, isdst = time.localtime() - - currentMth = mth - currentDy = dy - - s = dateString.lower() - m = self.ptc.CRE_DATE3.search(s) - mth = m.group('mthname') - mth = self.ptc.MonthOffsets[mth] - - if m.group('day') != None: - dy = int(m.group('day')) - else: - dy = 1 - - if m.group('year') != None: - yr = int(m.group('year')) - - # birthday epoch constraint - if yr < self.ptc.BirthdayEpoch: - yr += 2000 - elif yr < 100: - yr += 1900 - - elif (mth < currentMth) or (mth == currentMth and dy < currentDy): - # if that day and month have already passed in this year, - # then increment the year by 1 - yr += 1 - - if dy > 0 and dy <= self.ptc.daysInMonth(mth, yr): - sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) - else: - # Return current time if date string is invalid - self.dateFlag = 0 - self.timeFlag = 0 - sourceTime = time.localtime() - - return sourceTime - - - def evalRanges(self, datetimeString, sourceTime=None): - """ - Evaluate the C{datetimeString} text and determine if - it represents a date or time range. - - @type datetimeString: string - @param datetimeString: datetime text to evaluate - @type sourceTime: struct_time - @param sourceTime: C{struct_time} value to use as the base - - @rtype: tuple - @return: tuple of: start datetime, end datetime and the invalid flag - """ - startTime = '' - endTime = '' - startDate = '' - endDate = '' - rangeFlag = 0 - - s = datetimeString.strip().lower() - - if self.ptc.rangeSep in s: - s = s.replace(self.ptc.rangeSep, ' %s ' % self.ptc.rangeSep) - s = s.replace(' ', ' ') - - m = self.ptc.CRE_TIMERNG1.search(s) - if m is not None: - rangeFlag = 1 - else: - m = self.ptc.CRE_TIMERNG2.search(s) - if m is not None: - rangeFlag = 2 - else: - m = self.ptc.CRE_TIMERNG4.search(s) - if m is not None: - rangeFlag = 7 - else: - m = self.ptc.CRE_TIMERNG3.search(s) - if m is not None: - rangeFlag = 3 - else: - m = self.ptc.CRE_DATERNG1.search(s) - if m is not None: - rangeFlag = 4 - else: - m = self.ptc.CRE_DATERNG2.search(s) - if m is not None: - rangeFlag = 5 - else: - m = self.ptc.CRE_DATERNG3.search(s) - if m is not None: - rangeFlag = 6 - - if _debug: - print 'evalRanges: rangeFlag =', rangeFlag, '[%s]' % s - - if m is not None: - if (m.group() != s): - # capture remaining string - parseStr = m.group() - chunk1 = s[:m.start()] - chunk2 = s[m.end():] - s = '%s %s' % (chunk1, chunk2) - flag = 1 - - sourceTime, flag = self.parse(s, sourceTime) - - if flag == 0: - sourceTime = None - else: - parseStr = s - - if rangeFlag == 1: - m = re.search(self.ptc.rangeSep, parseStr) - startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime) - endTime, eflag = self.parse((parseStr[(m.start() + 1):]), sourceTime) - - if (eflag != 0) and (sflag != 0): - return (startTime, endTime, 2) - - elif rangeFlag == 2: - m = re.search(self.ptc.rangeSep, parseStr) - startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime) - endTime, eflag = self.parse((parseStr[(m.start() + 1):]), sourceTime) - - if (eflag != 0) and (sflag != 0): - return (startTime, endTime, 2) - - elif rangeFlag == 3 or rangeFlag == 7: - m = re.search(self.ptc.rangeSep, parseStr) - # capturing the meridian from the end time - if self.ptc.usesMeridian: - ampm = re.search(self.ptc.am[0], parseStr) - - # appending the meridian to the start time - if ampm is not None: - startTime, sflag = self.parse((parseStr[:m.start()] + self.ptc.meridian[0]), sourceTime) - else: - startTime, sflag = self.parse((parseStr[:m.start()] + self.ptc.meridian[1]), sourceTime) - else: - startTime, sflag = self.parse((parseStr[:m.start()]), sourceTime) - - endTime, eflag = self.parse(parseStr[(m.start() + 1):], sourceTime) - - if (eflag != 0) and (sflag != 0): - return (startTime, endTime, 2) - - elif rangeFlag == 4: - m = re.search(self.ptc.rangeSep, parseStr) - startDate, sflag = self.parse((parseStr[:m.start()]), sourceTime) - endDate, eflag = self.parse((parseStr[(m.start() + 1):]), sourceTime) - - if (eflag != 0) and (sflag != 0): - return (startDate, endDate, 1) - - elif rangeFlag == 5: - m = re.search(self.ptc.rangeSep, parseStr) - endDate = parseStr[(m.start() + 1):] - - # capturing the year from the end date - date = self.ptc.CRE_DATE3.search(endDate) - endYear = date.group('year') - - # appending the year to the start date if the start date - # does not have year information and the end date does. - # eg : "Aug 21 - Sep 4, 2007" - if endYear is not None: - startDate = (parseStr[:m.start()]).strip() - date = self.ptc.CRE_DATE3.search(startDate) - startYear = date.group('year') - - if startYear is None: - startDate = startDate + ', ' + endYear - else: - startDate = parseStr[:m.start()] - - startDate, sflag = self.parse(startDate, sourceTime) - endDate, eflag = self.parse(endDate, sourceTime) - - if (eflag != 0) and (sflag != 0): - return (startDate, endDate, 1) - - elif rangeFlag == 6: - m = re.search(self.ptc.rangeSep, parseStr) - - startDate = parseStr[:m.start()] - - # capturing the month from the start date - mth = self.ptc.CRE_DATE3.search(startDate) - mth = mth.group('mthname') - - # appending the month name to the end date - endDate = mth + parseStr[(m.start() + 1):] - - startDate, sflag = self.parse(startDate, sourceTime) - endDate, eflag = self.parse(endDate, sourceTime) - - if (eflag != 0) and (sflag != 0): - return (startDate, endDate, 1) - else: - # if range is not found - sourceTime = time.localtime() - - return (sourceTime, sourceTime, 0) - - - def _CalculateDOWDelta(self, wd, wkdy, offset, style, currentDayStyle): - """ - Based on the C{style} and C{currentDayStyle} determine what - day-of-week value is to be returned. - - @type wd: integer - @param wd: day-of-week value for the current day - @type wkdy: integer - @param wkdy: day-of-week value for the parsed day - @type offset: integer - @param offset: offset direction for any modifiers (-1, 0, 1) - @type style: integer - @param style: normally the value set in C{Constants.DOWParseStyle} - @type currentDayStyle: integer - @param currentDayStyle: normally the value set in C{Constants.CurrentDOWParseStyle} - - @rtype: integer - @return: calculated day-of-week - """ - if offset == 1: - # modifier is indicating future week eg: "next". - # DOW is calculated as DOW of next week - diff = 7 - wd + wkdy - - elif offset == -1: - # modifier is indicating past week eg: "last","previous" - # DOW is calculated as DOW of previous week - diff = wkdy - wd - 7 - - elif offset == 0: - # modifier is indiacting current week eg: "this" - # DOW is calculated as DOW of this week - diff = wkdy - wd - - elif offset == 2: - # no modifier is present. - # i.e. string to be parsed is just DOW - if style == 1: - # next occurance of the DOW is calculated - if currentDayStyle == True: - if wkdy >= wd: - diff = wkdy - wd - else: - diff = 7 - wd + wkdy - else: - if wkdy > wd: - diff = wkdy - wd - else: - diff = 7 - wd + wkdy - - elif style == -1: - # last occurance of the DOW is calculated - if currentDayStyle == True: - if wkdy <= wd: - diff = wkdy - wd - else: - diff = wkdy - wd - 7 - else: - if wkdy < wd: - diff = wkdy - wd - else: - diff = wkdy - wd - 7 - else: - # occurance of the DOW in the current week is calculated - diff = wkdy - wd - - if _debug: - print "wd %s, wkdy %s, offset %d, style %d\n" % (wd, wkdy, offset, style) - - return diff - - - def _evalModifier(self, modifier, chunk1, chunk2, sourceTime): - """ - Evaluate the C{modifier} string and following text (passed in - as C{chunk1} and C{chunk2}) and if they match any known modifiers - calculate the delta and apply it to C{sourceTime}. - - @type modifier: string - @param modifier: modifier text to apply to sourceTime - @type chunk1: string - @param chunk1: first text chunk that followed modifier (if any) - @type chunk2: string - @param chunk2: second text chunk that followed modifier (if any) - @type sourceTime: struct_time - @param sourceTime: C{struct_time} value to use as the base - - @rtype: tuple - @return: tuple of: remaining text and the modified sourceTime - """ - offset = self.ptc.Modifiers[modifier] - - if sourceTime is not None: - (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime - else: - (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime() - - # capture the units after the modifier and the remaining - # string after the unit - m = self.ptc.CRE_REMAINING.search(chunk2) - if m is not None: - index = m.start() + 1 - unit = chunk2[:m.start()] - chunk2 = chunk2[index:] - else: - unit = chunk2 - chunk2 = '' - - flag = False - - if unit == 'month' or \ - unit == 'mth' or \ - unit == 'm': - if offset == 0: - dy = self.ptc.daysInMonth(mth, yr) - sourceTime = (yr, mth, dy, 9, 0, 0, wd, yd, isdst) - elif offset == 2: - # if day is the last day of the month, calculate the last day - # of the next month - if dy == self.ptc.daysInMonth(mth, yr): - dy = self.ptc.daysInMonth(mth + 1, yr) - - start = datetime.datetime(yr, mth, dy, 9, 0, 0) - target = self.inc(start, month=1) - sourceTime = target.timetuple() - else: - start = datetime.datetime(yr, mth, 1, 9, 0, 0) - target = self.inc(start, month=offset) - sourceTime = target.timetuple() - - flag = True - self.dateFlag = 1 - - if unit == 'week' or \ - unit == 'wk' or \ - unit == 'w': - if offset == 0: - start = datetime.datetime(yr, mth, dy, 17, 0, 0) - target = start + datetime.timedelta(days=(4 - wd)) - sourceTime = target.timetuple() - elif offset == 2: - start = datetime.datetime(yr, mth, dy, 9, 0, 0) - target = start + datetime.timedelta(days=7) - sourceTime = target.timetuple() - else: - return self._evalModifier(modifier, chunk1, "monday " + chunk2, sourceTime) - - flag = True - self.dateFlag = 1 - - if unit == 'day' or \ - unit == 'dy' or \ - unit == 'd': - if offset == 0: - sourceTime = (yr, mth, dy, 17, 0, 0, wd, yd, isdst) - self.timeFlag = 2 - elif offset == 2: - start = datetime.datetime(yr, mth, dy, hr, mn, sec) - target = start + datetime.timedelta(days=1) - sourceTime = target.timetuple() - else: - start = datetime.datetime(yr, mth, dy, 9, 0, 0) - target = start + datetime.timedelta(days=offset) - sourceTime = target.timetuple() - - flag = True - self.dateFlag = 1 - - if unit == 'hour' or \ - unit == 'hr': - if offset == 0: - sourceTime = (yr, mth, dy, hr, 0, 0, wd, yd, isdst) - else: - start = datetime.datetime(yr, mth, dy, hr, 0, 0) - target = start + datetime.timedelta(hours=offset) - sourceTime = target.timetuple() - - flag = True - self.timeFlag = 2 - - if unit == 'year' or \ - unit == 'yr' or \ - unit == 'y': - if offset == 0: - sourceTime = (yr, 12, 31, hr, mn, sec, wd, yd, isdst) - elif offset == 2: - sourceTime = (yr + 1, mth, dy, hr, mn, sec, wd, yd, isdst) - else: - sourceTime = (yr + offset, 1, 1, 9, 0, 0, wd, yd, isdst) - - flag = True - self.dateFlag = 1 - - if flag == False: - m = self.ptc.CRE_WEEKDAY.match(unit) - if m is not None: - wkdy = m.group() - self.dateFlag = 1 - - if modifier == 'eod': - # Calculate the upcoming weekday - self.modifierFlag = False - (sourceTime, _) = self.parse(wkdy, sourceTime) - sources = self.ptc.buildSources(sourceTime) - self.timeFlag = 2 - - if modifier in sources: - sourceTime = sources[modifier] - - else: - wkdy = self.ptc.WeekdayOffsets[wkdy] - diff = self._CalculateDOWDelta(wd, wkdy, offset, - self.ptc.DOWParseStyle, - self.ptc.CurrentDOWParseStyle) - start = datetime.datetime(yr, mth, dy, 9, 0, 0) - target = start + datetime.timedelta(days=diff) - sourceTime = target.timetuple() - - flag = True - self.dateFlag = 1 - - if not flag: - m = self.ptc.CRE_TIME.match(unit) - if m is not None: - self.modifierFlag = False - (yr, mth, dy, hr, mn, sec, wd, yd, isdst), _ = self.parse(unit) - - start = datetime.datetime(yr, mth, dy, hr, mn, sec) - target = start + datetime.timedelta(days=offset) - sourceTime = target.timetuple() - flag = True - else: - self.modifierFlag = False - - # check if the remaining text is parsable and if so, - # use it as the base time for the modifier source time - t, flag2 = self.parse('%s %s' % (chunk1, unit), sourceTime) - - if flag2 != 0: - sourceTime = t - - sources = self.ptc.buildSources(sourceTime) - - if modifier in sources: - sourceTime = sources[modifier] - flag = True - self.timeFlag = 2 - - # if the word after next is a number, the string is more than likely - # to be "next 4 hrs" which we will have to combine the units with the - # rest of the string - if not flag: - if offset < 0: - # if offset is negative, the unit has to be made negative - unit = '-%s' % unit - - chunk2 = '%s %s' % (unit, chunk2) - - self.modifierFlag = False - - #return '%s %s' % (chunk1, chunk2), sourceTime - return '%s' % chunk2, sourceTime - - def _evalModifier2(self, modifier, chunk1 , chunk2, sourceTime): - """ - Evaluate the C{modifier} string and following text (passed in - as C{chunk1} and C{chunk2}) and if they match any known modifiers - calculate the delta and apply it to C{sourceTime}. - - @type modifier: string - @param modifier: modifier text to apply to C{sourceTime} - @type chunk1: string - @param chunk1: first text chunk that followed modifier (if any) - @type chunk2: string - @param chunk2: second text chunk that followed modifier (if any) - @type sourceTime: struct_time - @param sourceTime: C{struct_time} value to use as the base - - @rtype: tuple - @return: tuple of: remaining text and the modified sourceTime - """ - offset = self.ptc.Modifiers[modifier] - digit = r'\d+' - - self.modifier2Flag = False - - # If the string after the negative modifier starts with digits, - # then it is likely that the string is similar to ' before 3 days' - # or 'evening prior to 3 days'. - # In this case, the total time is calculated by subtracting '3 days' - # from the current date. - # So, we have to identify the quantity and negate it before parsing - # the string. - # This is not required for strings not starting with digits since the - # string is enough to calculate the sourceTime - if chunk2 != '': - if offset < 0: - m = re.match(digit, chunk2.strip()) - if m is not None: - qty = int(m.group()) * -1 - chunk2 = chunk2[m.end():] - chunk2 = '%d%s' % (qty, chunk2) - - sourceTime, flag1 = self.parse(chunk2, sourceTime) - if flag1 == 0: - flag1 = True - else: - flag1 = False - flag2 = False - else: - flag1 = False - - if chunk1 != '': - if offset < 0: - m = re.search(digit, chunk1.strip()) - if m is not None: - qty = int(m.group()) * -1 - chunk1 = chunk1[m.end():] - chunk1 = '%d%s' % (qty, chunk1) - - tempDateFlag = self.dateFlag - tempTimeFlag = self.timeFlag - sourceTime2, flag2 = self.parse(chunk1, sourceTime) - else: - return sourceTime, (flag1 and flag2) - - # if chunk1 is not a datetime and chunk2 is then do not use datetime - # value returned by parsing chunk1 - if not (flag1 == False and flag2 == 0): - sourceTime = sourceTime2 - else: - self.timeFlag = tempTimeFlag - self.dateFlag = tempDateFlag - - return sourceTime, (flag1 and flag2) - - - def _evalString(self, datetimeString, sourceTime=None): - """ - Calculate the datetime based on flags set by the L{parse()} routine - - Examples handled:: - RFC822, W3CDTF formatted dates - HH:MM[:SS][ am/pm] - MM/DD/YYYY - DD MMMM YYYY - - @type datetimeString: string - @param datetimeString: text to try and parse as more "traditional" - date/time text - @type sourceTime: struct_time - @param sourceTime: C{struct_time} value to use as the base - - @rtype: datetime - @return: calculated C{struct_time} value or current C{struct_time} - if not parsed - """ - s = datetimeString.strip() - now = time.localtime() - - # Given string date is a RFC822 date - if sourceTime is None: - sourceTime = _parse_date_rfc822(s) - - if sourceTime is not None: - (yr, mth, dy, hr, mn, sec, wd, yd, isdst, _) = sourceTime - self.dateFlag = 1 - - if (hr != 0) and (mn != 0) and (sec != 0): - self.timeFlag = 2 - - sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) - - # Given string date is a W3CDTF date - if sourceTime is None: - sourceTime = _parse_date_w3dtf(s) - - if sourceTime is not None: - self.dateFlag = 1 - self.timeFlag = 2 - - if sourceTime is None: - s = s.lower() - - # Given string is in the format HH:MM(:SS)(am/pm) - if self.meridianFlag: - if sourceTime is None: - (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now - else: - (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime - - m = self.ptc.CRE_TIMEHMS2.search(s) - if m is not None: - dt = s[:m.start('meridian')].strip() - if len(dt) <= 2: - hr = int(dt) - mn = 0 - sec = 0 - else: - hr, mn, sec = _extract_time(m) - - if hr == 24: - hr = 0 - - sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) - meridian = m.group('meridian').lower() - - # if 'am' found and hour is 12 - force hour to 0 (midnight) - if (meridian in self.ptc.am) and hr == 12: - sourceTime = (yr, mth, dy, 0, mn, sec, wd, yd, isdst) - - # if 'pm' found and hour < 12, add 12 to shift to evening - if (meridian in self.ptc.pm) and hr < 12: - sourceTime = (yr, mth, dy, hr + 12, mn, sec, wd, yd, isdst) - - # invalid time - if hr > 24 or mn > 59 or sec > 59: - sourceTime = now - self.dateFlag = 0 - self.timeFlag = 0 - - self.meridianFlag = False - - # Given string is in the format HH:MM(:SS) - if self.timeStdFlag: - if sourceTime is None: - (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now - else: - (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime - - m = self.ptc.CRE_TIMEHMS.search(s) - if m is not None: - hr, mn, sec = _extract_time(m) - if hr == 24: - hr = 0 - - if hr > 24 or mn > 59 or sec > 59: - # invalid time - sourceTime = now - self.dateFlag = 0 - self.timeFlag = 0 - else: - sourceTime = (yr, mth, dy, hr, mn, sec, wd, yd, isdst) - - self.timeStdFlag = False - - # Given string is in the format 07/21/2006 - if self.dateStdFlag: - sourceTime = self.parseDate(s) - self.dateStdFlag = False - - # Given string is in the format "May 23rd, 2005" - if self.dateStrFlag: - sourceTime = self.parseDateText(s) - self.dateStrFlag = False - - # Given string is a weekday - if self.weekdyFlag: - (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = now - - start = datetime.datetime(yr, mth, dy, hr, mn, sec) - wkdy = self.ptc.WeekdayOffsets[s] - - if wkdy > wd: - qty = self._CalculateDOWDelta(wd, wkdy, 2, - self.ptc.DOWParseStyle, - self.ptc.CurrentDOWParseStyle) - else: - qty = self._CalculateDOWDelta(wd, wkdy, 2, - self.ptc.DOWParseStyle, - self.ptc.CurrentDOWParseStyle) - - target = start + datetime.timedelta(days=qty) - wd = wkdy - - sourceTime = target.timetuple() - self.weekdyFlag = False - - # Given string is a natural language time string like - # lunch, midnight, etc - if self.timeStrFlag: - if s in self.ptc.re_values['now']: - sourceTime = now - else: - sources = self.ptc.buildSources(sourceTime) - - if s in sources: - sourceTime = sources[s] - else: - sourceTime = now - self.dateFlag = 0 - self.timeFlag = 0 - - self.timeStrFlag = False - - # Given string is a natural language date string like today, tomorrow.. - if self.dayStrFlag: - if sourceTime is None: - sourceTime = now - - (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime - - if s in self.ptc.dayOffsets: - offset = self.ptc.dayOffsets[s] - else: - offset = 0 - - start = datetime.datetime(yr, mth, dy, 9, 0, 0) - target = start + datetime.timedelta(days=offset) - sourceTime = target.timetuple() - - self.dayStrFlag = False - - # Given string is a time string with units like "5 hrs 30 min" - if self.unitsFlag: - modifier = '' # TODO - - if sourceTime is None: - sourceTime = now - - m = self.ptc.CRE_UNITS.search(s) - if m is not None: - units = m.group('units') - quantity = s[:m.start('units')] - - sourceTime = self._buildTime(sourceTime, quantity, modifier, units) - self.unitsFlag = False - - # Given string is a time string with single char units like "5 h 30 m" - if self.qunitsFlag: - modifier = '' # TODO - - if sourceTime is None: - sourceTime = now - - m = self.ptc.CRE_QUNITS.search(s) - if m is not None: - units = m.group('qunits') - quantity = s[:m.start('qunits')] - - sourceTime = self._buildTime(sourceTime, quantity, modifier, units) - self.qunitsFlag = False - - # Given string does not match anything - if sourceTime is None: - sourceTime = now - self.dateFlag = 0 - self.timeFlag = 0 - - return sourceTime - - - def parse(self, datetimeString, sourceTime=None): - """ - Splits the given C{datetimeString} into tokens, finds the regex - patterns that match and then calculates a C{struct_time} value from - the chunks. - - If C{sourceTime} is given then the C{struct_time} value will be - calculated from that value, otherwise from the current date/time. - - If the C{datetimeString} is parsed and date/time value found then - the second item of the returned tuple will be a flag to let you know - what kind of C{struct_time} value is being returned:: - - 0 = not parsed at all - 1 = parsed as a C{date} - 2 = parsed as a C{time} - 3 = parsed as a C{datetime} - - @type datetimeString: string - @param datetimeString: date/time text to evaluate - @type sourceTime: struct_time - @param sourceTime: C{struct_time} value to use as the base - - @rtype: tuple - @return: tuple of: modified C{sourceTime} and the result flag - """ - - if sourceTime: - if isinstance(sourceTime, datetime.datetime): - if _debug: - print 'coercing datetime to timetuple' - sourceTime = sourceTime.timetuple() - else: - if not isinstance(sourceTime, time.struct_time) and \ - not isinstance(sourceTime, tuple): - raise Exception('sourceTime is not a struct_time') - - s = datetimeString.strip().lower() - parseStr = '' - totalTime = sourceTime - - if s == '' : - if sourceTime is not None: - return (sourceTime, self.dateFlag + self.timeFlag) - else: - return (time.localtime(), 0) - - self.timeFlag = 0 - self.dateFlag = 0 - - while len(s) > 0: - flag = False - chunk1 = '' - chunk2 = '' - - if _debug: - print 'parse (top of loop): [%s][%s]' % (s, parseStr) - - if parseStr == '': - # Modifier like next\prev.. - m = self.ptc.CRE_MODIFIER.search(s) - if m is not None: - self.modifierFlag = True - if (m.group('modifier') != s): - # capture remaining string - parseStr = m.group('modifier') - chunk1 = s[:m.start('modifier')].strip() - chunk2 = s[m.end('modifier'):].strip() - flag = True - else: - parseStr = s - - if parseStr == '': - # Modifier like from\after\prior.. - m = self.ptc.CRE_MODIFIER2.search(s) - if m is not None: - self.modifier2Flag = True - if (m.group('modifier') != s): - # capture remaining string - parseStr = m.group('modifier') - chunk1 = s[:m.start('modifier')].strip() - chunk2 = s[m.end('modifier'):].strip() - flag = True - else: - parseStr = s - - if parseStr == '': - valid_date = False - for match in self.ptc.CRE_DATE3.finditer(s): - # to prevent "HH:MM(:SS) time strings" expressions from triggering - # this regex, we checks if the month field exists in the searched - # expression, if it doesn't exist, the date field is not valid - if match.group('mthname'): - m = self.ptc.CRE_DATE3.search(s, match.start()) - valid_date = True - break - - # String date format - if valid_date: - self.dateStrFlag = True - self.dateFlag = 1 - if (m.group('date') != s): - # capture remaining string - parseStr = m.group('date') - chunk1 = s[:m.start('date')] - chunk2 = s[m.end('date'):] - s = '%s %s' % (chunk1, chunk2) - flag = True - else: - parseStr = s - - if parseStr == '': - # Standard date format - m = self.ptc.CRE_DATE.search(s) - if m is not None: - self.dateStdFlag = True - self.dateFlag = 1 - if (m.group('date') != s): - # capture remaining string - parseStr = m.group('date') - chunk1 = s[:m.start('date')] - chunk2 = s[m.end('date'):] - s = '%s %s' % (chunk1, chunk2) - flag = True - else: - parseStr = s - - if parseStr == '': - # Natural language day strings - m = self.ptc.CRE_DAY.search(s) - if m is not None: - self.dayStrFlag = True - self.dateFlag = 1 - if (m.group('day') != s): - # capture remaining string - parseStr = m.group('day') - chunk1 = s[:m.start('day')] - chunk2 = s[m.end('day'):] - s = '%s %s' % (chunk1, chunk2) - flag = True - else: - parseStr = s - - if parseStr == '': - # Quantity + Units - m = self.ptc.CRE_UNITS.search(s) - if m is not None: - self.unitsFlag = True - if (m.group('qty') != s): - # capture remaining string - parseStr = m.group('qty') - chunk1 = s[:m.start('qty')].strip() - chunk2 = s[m.end('qty'):].strip() - - if chunk1[-1:] == '-': - parseStr = '-%s' % parseStr - chunk1 = chunk1[:-1] - - s = '%s %s' % (chunk1, chunk2) - flag = True - else: - parseStr = s - - if parseStr == '': - # Quantity + Units - m = self.ptc.CRE_QUNITS.search(s) - if m is not None: - self.qunitsFlag = True - - if (m.group('qty') != s): - # capture remaining string - parseStr = m.group('qty') - chunk1 = s[:m.start('qty')].strip() - chunk2 = s[m.end('qty'):].strip() - - if chunk1[-1:] == '-': - parseStr = '-%s' % parseStr - chunk1 = chunk1[:-1] - - s = '%s %s' % (chunk1, chunk2) - flag = True - else: - parseStr = s - - if parseStr == '': - # Weekday - m = self.ptc.CRE_WEEKDAY.search(s) - if m is not None: - gv = m.group('weekday') - if s not in self.ptc.dayOffsets: - self.weekdyFlag = True - self.dateFlag = 1 - if (gv != s): - # capture remaining string - parseStr = gv - chunk1 = s[:m.start('weekday')] - chunk2 = s[m.end('weekday'):] - s = '%s %s' % (chunk1, chunk2) - flag = True - else: - parseStr = s - - if parseStr == '': - # Natural language time strings - m = self.ptc.CRE_TIME.search(s) - if m is not None: - self.timeStrFlag = True - self.timeFlag = 2 - if (m.group('time') != s): - # capture remaining string - parseStr = m.group('time') - chunk1 = s[:m.start('time')] - chunk2 = s[m.end('time'):] - s = '%s %s' % (chunk1, chunk2) - flag = True - else: - parseStr = s - - if parseStr == '': - # HH:MM(:SS) am/pm time strings - m = self.ptc.CRE_TIMEHMS2.search(s) - if m is not None: - self.meridianFlag = True - self.timeFlag = 2 - if m.group('minutes') is not None: - if m.group('seconds') is not None: - parseStr = '%s:%s:%s %s' % (m.group('hours'), - m.group('minutes'), - m.group('seconds'), - m.group('meridian')) - else: - parseStr = '%s:%s %s' % (m.group('hours'), - m.group('minutes'), - m.group('meridian')) - else: - parseStr = '%s %s' % (m.group('hours'), - m.group('meridian')) - - chunk1 = s[:m.start('hours')] - chunk2 = s[m.end('meridian'):] - - s = '%s %s' % (chunk1, chunk2) - flag = True - - if parseStr == '': - # HH:MM(:SS) time strings - m = self.ptc.CRE_TIMEHMS.search(s) - if m is not None: - self.timeStdFlag = True - self.timeFlag = 2 - if m.group('seconds') is not None: - parseStr = '%s:%s:%s' % (m.group('hours'), - m.group('minutes'), - m.group('seconds')) - chunk1 = s[:m.start('hours')] - chunk2 = s[m.end('seconds'):] - else: - parseStr = '%s:%s' % (m.group('hours'), - m.group('minutes')) - chunk1 = s[:m.start('hours')] - chunk2 = s[m.end('minutes'):] - - s = '%s %s' % (chunk1, chunk2) - flag = True - - # if string does not match any regex, empty string to - # come out of the while loop - if not flag: - s = '' - - if _debug: - print 'parse (bottom) [%s][%s][%s][%s]' % (s, parseStr, chunk1, chunk2) - print 'weekday %s, dateStd %s, dateStr %s, time %s, timeStr %s, meridian %s' % \ - (self.weekdyFlag, self.dateStdFlag, self.dateStrFlag, self.timeStdFlag, self.timeStrFlag, self.meridianFlag) - print 'dayStr %s, modifier %s, modifier2 %s, units %s, qunits %s' % \ - (self.dayStrFlag, self.modifierFlag, self.modifier2Flag, self.unitsFlag, self.qunitsFlag) - - # evaluate the matched string - if parseStr != '': - if self.modifierFlag == True: - t, totalTime = self._evalModifier(parseStr, chunk1, chunk2, totalTime) - # t is the unparsed part of the chunks. - # If it is not date/time, return current - # totalTime as it is; else return the output - # after parsing t. - if (t != '') and (t != None): - tempDateFlag = self.dateFlag - tempTimeFlag = self.timeFlag - (totalTime2, flag) = self.parse(t, totalTime) - - if flag == 0 and totalTime is not None: - self.timeFlag = tempTimeFlag - self.dateFlag = tempDateFlag - - return (totalTime, self.dateFlag + self.timeFlag) - else: - return (totalTime2, self.dateFlag + self.timeFlag) - - elif self.modifier2Flag == True: - totalTime, invalidFlag = self._evalModifier2(parseStr, chunk1, chunk2, totalTime) - - if invalidFlag == True: - self.dateFlag = 0 - self.timeFlag = 0 - - else: - totalTime = self._evalString(parseStr, totalTime) - parseStr = '' - - # String is not parsed at all - if totalTime is None or totalTime == sourceTime: - totalTime = time.localtime() - self.dateFlag = 0 - self.timeFlag = 0 - - return (totalTime, self.dateFlag + self.timeFlag) - - - def inc(self, source, month=None, year=None): - """ - Takes the given C{source} date, or current date if none is - passed, and increments it according to the values passed in - by month and/or year. - - This routine is needed because Python's C{timedelta()} function - does not allow for month or year increments. - - @type source: struct_time - @param source: C{struct_time} value to increment - @type month: integer - @param month: optional number of months to increment - @type year: integer - @param year: optional number of years to increment - - @rtype: datetime - @return: C{source} incremented by the number of months and/or years - """ - yr = source.year - mth = source.month - dy = source.day - - if year: - try: - yi = int(year) - except ValueError: - yi = 0 - - yr += yi - - if month: - try: - mi = int(month) - except ValueError: - mi = 0 - - m = abs(mi) - y = m / 12 # how many years are in month increment - m = m % 12 # get remaining months - - if mi < 0: - mth = mth - m # sub months from start month - if mth < 1: # cross start-of-year? - y -= 1 # yes - decrement year - mth += 12 # and fix month - else: - mth = mth + m # add months to start month - if mth > 12: # cross end-of-year? - y += 1 # yes - increment year - mth -= 12 # and fix month - - yr += y - - # if the day ends up past the last day of - # the new month, set it to the last day - if dy > self.ptc.daysInMonth(mth, yr): - dy = self.ptc.daysInMonth(mth, yr) - - d = source.replace(year=yr, month=mth, day=dy) - - return source + (d - source) - +# Backward compatibility fix. +from . import *
--- a/MoinMoin/support/parsedatetime/parsedatetime_consts.py Mon Sep 05 23:55:33 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1119 +0,0 @@ -#!/usr/bin/env python - -""" -parsedatetime constants and helper functions to determine -regex values from Locale information if present. - -Also contains the internal Locale classes to give some sane -defaults if PyICU is not found. -""" - -__license__ = """ -Copyright (c) 2004-2008 Mike Taylor -Copyright (c) 2006-2008 Darshana Chhajed -Copyright (c) 2007 Bernd Zeimetz <bzed@debian.org> -All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -try: - import PyICU as pyicu -except: - pyicu = None - - -import datetime -import calendar -import time -import re - - -class pdtLocale_en: - """ - en_US Locale constants - - This class will be used to initialize L{Constants} if PyICU is not located. - - Defined as class variables are the lists and strings needed by parsedatetime - to evaluate strings for USA - """ - - localeID = 'en_US' # don't use a unicode string - dateSep = [ u'/', u'.' ] - timeSep = [ u':' ] - meridian = [ u'AM', u'PM' ] - usesMeridian = True - uses24 = False - - Weekdays = [ u'monday', u'tuesday', u'wednesday', - u'thursday', u'friday', u'saturday', u'sunday', - ] - shortWeekdays = [ u'mon', u'tues', u'wed', - u'thu', u'fri', u'sat', u'sun', - ] - Months = [ u'january', u'february', u'march', - u'april', u'may', u'june', - u'july', u'august', u'september', - u'october', u'november', u'december', - ] - shortMonths = [ u'jan', u'feb', u'mar', - u'apr', u'may', u'jun', - u'jul', u'aug', u'sep', - u'oct', u'nov', u'dec', - ] - dateFormats = { 'full': 'EEEE, MMMM d, yyyy', - 'long': 'MMMM d, yyyy', - 'medium': 'MMM d, yyyy', - 'short': 'M/d/yy', - } - timeFormats = { 'full': 'h:mm:ss a z', - 'long': 'h:mm:ss a z', - 'medium': 'h:mm:ss a', - 'short': 'h:mm a', - } - - dp_order = [ u'm', u'd', u'y' ] - - # this will be added to re_consts later - units = { 'seconds': [ 'second', 'sec' ], - 'minutes': [ 'minute', 'min' ], - 'hours': [ 'hour', 'hr' ], - 'days': [ 'day', 'dy' ], - 'weeks': [ 'week', 'wk' ], - 'months': [ 'month', 'mth' ], - 'years': [ 'year', 'yr' ], - } - - # text constants to be used by regex's later - re_consts = { 'specials': 'in|on|of|at', - 'timeseperator': ':', - 'rangeseperator': '-', - 'daysuffix': 'rd|st|nd|th', - 'meridian': 'am|pm|a.m.|p.m.|a|p', - 'qunits': 'h|m|s|d|w|m|y', - 'now': [ 'now' ], - } - - # Used to adjust the returned date before/after the source - modifiers = { 'from': 1, - 'before': -1, - 'after': 1, - 'ago': -1, - 'prior': -1, - 'prev': -1, - 'last': -1, - 'next': 1, - 'previous': -1, - 'in a': 2, - 'end of': 0, - 'eod': 0, - 'eo': 0 - } - - dayoffsets = { 'tomorrow': 1, - 'today': 0, - 'yesterday': -1, - } - - # special day and/or times, i.e. lunch, noon, evening - # each element in the dictionary is a dictionary that is used - # to fill in any value to be replace - the current date/time will - # already have been populated by the method buildSources - re_sources = { 'noon': { 'hr': 12, 'mn': 0, 'sec': 0 }, - 'lunch': { 'hr': 12, 'mn': 0, 'sec': 0 }, - 'morning': { 'hr': 6, 'mn': 0, 'sec': 0 }, - 'breakfast': { 'hr': 8, 'mn': 0, 'sec': 0 }, - 'dinner': { 'hr': 19, 'mn': 0, 'sec': 0 }, - 'evening': { 'hr': 18, 'mn': 0, 'sec': 0 }, - 'midnight': { 'hr': 0, 'mn': 0, 'sec': 0 }, - 'night': { 'hr': 21, 'mn': 0, 'sec': 0 }, - 'tonight': { 'hr': 21, 'mn': 0, 'sec': 0 }, - 'eod': { 'hr': 17, 'mn': 0, 'sec': 0 }, - } - - -class pdtLocale_au: - """ - en_AU Locale constants - - This class will be used to initialize L{Constants} if PyICU is not located. - - Defined as class variables are the lists and strings needed by parsedatetime - to evaluate strings for Australia - """ - - localeID = 'en_AU' # don't use a unicode string - dateSep = [ u'-', u'/' ] - timeSep = [ u':' ] - meridian = [ u'AM', u'PM' ] - usesMeridian = True - uses24 = False - - Weekdays = [ u'monday', u'tuesday', u'wednesday', - u'thursday', u'friday', u'saturday', u'sunday', - ] - shortWeekdays = [ u'mon', u'tues', u'wed', - u'thu', u'fri', u'sat', u'sun', - ] - Months = [ u'january', u'february', u'march', - u'april', u'may', u'june', - u'july', u'august', u'september', - u'october', u'november', u'december', - ] - shortMonths = [ u'jan', u'feb', u'mar', - u'apr', u'may', u'jun', - u'jul', u'aug', u'sep', - u'oct', u'nov', u'dec', - ] - dateFormats = { 'full': 'EEEE, d MMMM yyyy', - 'long': 'd MMMM yyyy', - 'medium': 'dd/MM/yyyy', - 'short': 'd/MM/yy', - } - timeFormats = { 'full': 'h:mm:ss a z', - 'long': 'h:mm:ss a', - 'medium': 'h:mm:ss a', - 'short': 'h:mm a', - } - - dp_order = [ u'd', u'm', u'y' ] - - # this will be added to re_consts later - units = { 'seconds': [ 'second', 'sec' ], - 'minutes': [ 'minute', 'min' ], - 'hours': [ 'hour', 'hr' ], - 'days': [ 'day', 'dy' ], - 'weeks': [ 'week', 'wk' ], - 'months': [ 'month', 'mth' ], - 'years': [ 'year', 'yr' ], - } - - # text constants to be used by regex's later - re_consts = { 'specials': 'in|on|of|at', - 'timeseperator': ':', - 'rangeseperator': '-', - 'daysuffix': 'rd|st|nd|th', - 'meridian': 'am|pm|a.m.|p.m.|a|p', - 'qunits': 'h|m|s|d|w|m|y', - 'now': [ 'now' ], - } - - # Used to adjust the returned date before/after the source - modifiers = { 'from': 1, - 'before': -1, - 'after': 1, - 'ago': 1, - 'prior': -1, - 'prev': -1, - 'last': -1, - 'next': 1, - 'previous': -1, - 'in a': 2, - 'end of': 0, - 'eo': 0, - } - - dayoffsets = { 'tomorrow': 1, - 'today': 0, - 'yesterday': -1, - } - - # special day and/or times, i.e. lunch, noon, evening - # each element in the dictionary is a dictionary that is used - # to fill in any value to be replace - the current date/time will - # already have been populated by the method buildSources - re_sources = { 'noon': { 'hr': 12, 'mn': 0, 'sec': 0 }, - 'lunch': { 'hr': 12, 'mn': 0, 'sec': 0 }, - 'morning': { 'hr': 6, 'mn': 0, 'sec': 0 }, - 'breakfast': { 'hr': 8, 'mn': 0, 'sec': 0 }, - 'dinner': { 'hr': 19, 'mn': 0, 'sec': 0 }, - 'evening': { 'hr': 18, 'mn': 0, 'sec': 0 }, - 'midnight': { 'hr': 0, 'mn': 0, 'sec': 0 }, - 'night': { 'hr': 21, 'mn': 0, 'sec': 0 }, - 'tonight': { 'hr': 21, 'mn': 0, 'sec': 0 }, - 'eod': { 'hr': 17, 'mn': 0, 'sec': 0 }, - } - - -class pdtLocale_es: - """ - es Locale constants - - This class will be used to initialize L{Constants} if PyICU is not located. - - Defined as class variables are the lists and strings needed by parsedatetime - to evaluate strings in Spanish - - Note that I don't speak Spanish so many of the items below are still in English - """ - - localeID = 'es' # don't use a unicode string - dateSep = [ u'/' ] - timeSep = [ u':' ] - meridian = [] - usesMeridian = False - uses24 = True - - Weekdays = [ u'lunes', u'martes', u'mi\xe9rcoles', - u'jueves', u'viernes', u's\xe1bado', u'domingo', - ] - shortWeekdays = [ u'lun', u'mar', u'mi\xe9', - u'jue', u'vie', u's\xe1b', u'dom', - ] - Months = [ u'enero', u'febrero', u'marzo', - u'abril', u'mayo', u'junio', - u'julio', u'agosto', u'septiembre', - u'octubre', u'noviembre', u'diciembre' - ] - shortMonths = [ u'ene', u'feb', u'mar', - u'abr', u'may', u'jun', - u'jul', u'ago', u'sep', - u'oct', u'nov', u'dic' - ] - dateFormats = { 'full': "EEEE d' de 'MMMM' de 'yyyy", - 'long': "d' de 'MMMM' de 'yyyy", - 'medium': "dd-MMM-yy", - 'short': "d/MM/yy", - } - timeFormats = { 'full': "HH'H'mm' 'ss z", - 'long': "HH:mm:ss z", - 'medium': "HH:mm:ss", - 'short': "HH:mm", - } - - dp_order = [ u'd', u'm', u'y' ] - - # this will be added to re_consts later - units = { 'seconds': [ 'second', 'sec' ], - 'minutes': [ 'minute', 'min' ], - 'hours': [ 'hour', 'hr' ], - 'days': [ 'day', 'dy' ], - 'weeks': [ 'week', 'wk' ], - 'months': [ 'month', 'mth' ], - 'years': [ 'year', 'yr' ], - } - - # text constants to be used by regex's later - re_consts = { 'specials': 'in|on|of|at', - 'timeseperator': timeSep, - 'dateseperator': dateSep, - 'rangeseperator': '-', - 'daysuffix': 'rd|st|nd|th', - 'qunits': 'h|m|s|d|w|m|y', - 'now': [ 'now' ], - } - - # Used to adjust the returned date before/after the source - modifiers = { 'from': 1, - 'before': -1, - 'after': 1, - 'ago': 1, - 'prior': -1, - 'prev': -1, - 'last': -1, - 'next': 1, - 'previous': -1, - 'in a': 2, - 'end of': 0, - 'eo': 0, - } - - dayoffsets = { 'tomorrow': 1, - 'today': 0, - 'yesterday': -1, - } - - # special day and/or times, i.e. lunch, noon, evening - # each element in the dictionary is a dictionary that is used - # to fill in any value to be replace - the current date/time will - # already have been populated by the method buildSources - re_sources = { 'noon': { 'hr': 12, 'mn': 0, 'sec': 0 }, - 'lunch': { 'hr': 12, 'mn': 0, 'sec': 0 }, - 'morning': { 'hr': 6, 'mn': 0, 'sec': 0 }, - 'breakfast': { 'hr': 8, 'mn': 0, 'sec': 0 }, - 'dinner': { 'hr': 19, 'mn': 0, 'sec': 0 }, - 'evening': { 'hr': 18, 'mn': 0, 'sec': 0 }, - 'midnight': { 'hr': 0, 'mn': 0, 'sec': 0 }, - 'night': { 'hr': 21, 'mn': 0, 'sec': 0 }, - 'tonight': { 'hr': 21, 'mn': 0, 'sec': 0 }, - 'eod': { 'hr': 17, 'mn': 0, 'sec': 0 }, - } - - -class pdtLocale_de: - """ - de_DE Locale constants - - This class will be used to initialize L{Constants} if PyICU is not located. - - Contributed by Debian parsedatetime package maintainer Bernd Zeimetz <bzed@debian.org> - - Defined as class variables are the lists and strings needed by parsedatetime - to evaluate strings for German - """ - - localeID = 'de_DE' # don't use a unicode string - dateSep = [ u'.' ] - timeSep = [ u':' ] - meridian = [ ] - usesMeridian = False - uses24 = True - - Weekdays = [ u'montag', u'dienstag', u'mittwoch', - u'donnerstag', u'freitag', u'samstag', u'sonntag', - ] - shortWeekdays = [ u'mo', u'di', u'mi', - u'do', u'fr', u'sa', u'so', - ] - Months = [ u'januar', u'februar', u'm\xe4rz', - u'april', u'mai', u'juni', - u'juli', u'august', u'september', - u'oktober', u'november', u'dezember', - ] - shortMonths = [ u'jan', u'feb', u'mrz', - u'apr', u'mai', u'jun', - u'jul', u'aug', u'sep', - u'okt', u'nov', u'dez', - ] - dateFormats = { 'full': u'EEEE, d. MMMM yyyy', - 'long': u'd. MMMM yyyy', - 'medium': u'dd.MM.yyyy', - 'short': u'dd.MM.yy' - } - - timeFormats = { 'full': u'HH:mm:ss v', - 'long': u'HH:mm:ss z', - 'medium': u'HH:mm:ss', - 'short': u'HH:mm' - } - - dp_order = [ u'd', u'm', u'y' ] - - # this will be added to re_consts later - units = { 'seconds': [ 'sekunden', 'sek', 's' ], - 'minutes': [ 'minuten', 'min' , 'm' ], - 'hours': [ 'stunden', 'std', 'h' ], - 'days': [ 'tage', 't' ], - 'weeks': [ 'wochen', 'w' ], - 'months': [ 'monate' ], #the short version would be a capital M, - #as I understand it we can't distinguis - #between m for minutes and M for months. - 'years': [ 'jahre', 'j' ], - } - - # text constants to be used by regex's later - re_consts = { 'specials': 'am|dem|der|im|in|den|zum', - 'timeseperator': ':', - 'rangeseperator': '-', - 'daysuffix': '', - 'qunits': 'h|m|s|t|w|m|j', - 'now': [ 'jetzt' ], - } - - # Used to adjust the returned date before/after the source - #still looking for insight on how to translate all of them to german. - modifiers = { u'from': 1, - u'before': -1, - u'after': 1, - u'vergangener': -1, - u'vorheriger': -1, - u'prev': -1, - u'letzter': -1, - u'n\xe4chster': 1, - u'dieser': 0, - u'previous': -1, - u'in a': 2, - u'end of': 0, - u'eod': 0, - u'eo': 0, - } - - #morgen/abermorgen does not work, see http://code.google.com/p/parsedatetime/issues/detail?id=19 - dayoffsets = { u'morgen': 1, - u'heute': 0, - u'gestern': -1, - u'vorgestern': -2, - u'\xfcbermorgen': 2, - } - - # special day and/or times, i.e. lunch, noon, evening - # each element in the dictionary is a dictionary that is used - # to fill in any value to be replace - the current date/time will - # already have been populated by the method buildSources - re_sources = { u'mittag': { 'hr': 12, 'mn': 0, 'sec': 0 }, - u'mittags': { 'hr': 12, 'mn': 0, 'sec': 0 }, - u'mittagessen': { 'hr': 12, 'mn': 0, 'sec': 0 }, - u'morgen': { 'hr': 6, 'mn': 0, 'sec': 0 }, - u'morgens': { 'hr': 6, 'mn': 0, 'sec': 0 }, - u'fr\e4hst\xe4ck': { 'hr': 8, 'mn': 0, 'sec': 0 }, - u'abendessen': { 'hr': 19, 'mn': 0, 'sec': 0 }, - u'abend': { 'hr': 18, 'mn': 0, 'sec': 0 }, - u'abends': { 'hr': 18, 'mn': 0, 'sec': 0 }, - u'mitternacht': { 'hr': 0, 'mn': 0, 'sec': 0 }, - u'nacht': { 'hr': 21, 'mn': 0, 'sec': 0 }, - u'nachts': { 'hr': 21, 'mn': 0, 'sec': 0 }, - u'heute abend': { 'hr': 21, 'mn': 0, 'sec': 0 }, - u'heute nacht': { 'hr': 21, 'mn': 0, 'sec': 0 }, - u'feierabend': { 'hr': 17, 'mn': 0, 'sec': 0 }, - } - - -pdtLocales = { 'en_US': pdtLocale_en, - 'en_AU': pdtLocale_au, - 'es_ES': pdtLocale_es, - 'de_DE': pdtLocale_de, - } - - -def _initLocale(ptc): - """ - Helper function to initialize the different lists and strings - from either PyICU or one of the internal pdt Locales and store - them into ptc. - """ - - def lcase(x): - return x.lower() - - if pyicu and ptc.usePyICU: - ptc.icuLocale = None - - if ptc.localeID is not None: - ptc.icuLocale = pyicu.Locale(ptc.localeID) - - if ptc.icuLocale is None: - for id in range(0, len(ptc.fallbackLocales)): - ptc.localeID = ptc.fallbackLocales[id] - ptc.icuLocale = pyicu.Locale(ptc.localeID) - - if ptc.icuLocale is not None: - break - - ptc.icuSymbols = pyicu.DateFormatSymbols(ptc.icuLocale) - - # grab ICU list of weekdays, skipping first entry which - # is always blank - wd = map(lcase, ptc.icuSymbols.getWeekdays()[1:]) - swd = map(lcase, ptc.icuSymbols.getShortWeekdays()[1:]) - - # store them in our list with Monday first (ICU puts Sunday first) - ptc.Weekdays = wd[1:] + wd[0:1] - ptc.shortWeekdays = swd[1:] + swd[0:1] - ptc.Months = map(lcase, ptc.icuSymbols.getMonths()) - ptc.shortMonths = map(lcase, ptc.icuSymbols.getShortMonths()) - - # not quite sure how to init this so for now - # set it to none so it will be set to the en_US defaults for now - ptc.re_consts = None - ptc.icu_df = { 'full': pyicu.DateFormat.createDateInstance(pyicu.DateFormat.kFull, ptc.icuLocale), - 'long': pyicu.DateFormat.createDateInstance(pyicu.DateFormat.kLong, ptc.icuLocale), - 'medium': pyicu.DateFormat.createDateInstance(pyicu.DateFormat.kMedium, ptc.icuLocale), - 'short': pyicu.DateFormat.createDateInstance(pyicu.DateFormat.kShort, ptc.icuLocale), - } - ptc.icu_tf = { 'full': pyicu.DateFormat.createTimeInstance(pyicu.DateFormat.kFull, ptc.icuLocale), - 'long': pyicu.DateFormat.createTimeInstance(pyicu.DateFormat.kLong, ptc.icuLocale), - 'medium': pyicu.DateFormat.createTimeInstance(pyicu.DateFormat.kMedium, ptc.icuLocale), - 'short': pyicu.DateFormat.createTimeInstance(pyicu.DateFormat.kShort, ptc.icuLocale), - } - ptc.dateFormats = { 'full': ptc.icu_df['full'].toPattern(), - 'long': ptc.icu_df['long'].toPattern(), - 'medium': ptc.icu_df['medium'].toPattern(), - 'short': ptc.icu_df['short'].toPattern(), - } - ptc.timeFormats = { 'full': ptc.icu_tf['full'].toPattern(), - 'long': ptc.icu_tf['long'].toPattern(), - 'medium': ptc.icu_tf['medium'].toPattern(), - 'short': ptc.icu_tf['short'].toPattern(), - } - else: - if not ptc.localeID in pdtLocales: - for id in range(0, len(ptc.fallbackLocales)): - ptc.localeID = ptc.fallbackLocales[id] - - if ptc.localeID in pdtLocales: - break - - ptc.locale = pdtLocales[ptc.localeID] - ptc.usePyICU = False - - ptc.Weekdays = ptc.locale.Weekdays - ptc.shortWeekdays = ptc.locale.shortWeekdays - ptc.Months = ptc.locale.Months - ptc.shortMonths = ptc.locale.shortMonths - ptc.dateFormats = ptc.locale.dateFormats - ptc.timeFormats = ptc.locale.timeFormats - - # these values are used to setup the various bits - # of the regex values used to parse - # - # check if a local set of constants has been - # provided, if not use en_US as the default - if ptc.localeID in pdtLocales: - ptc.re_sources = pdtLocales[ptc.localeID].re_sources - ptc.re_values = pdtLocales[ptc.localeID].re_consts - - units = pdtLocales[ptc.localeID].units - - ptc.Modifiers = pdtLocales[ptc.localeID].modifiers - ptc.dayOffsets = pdtLocales[ptc.localeID].dayoffsets - - # for now, pull over any missing keys from the US set - for key in pdtLocales['en_US'].re_consts: - if not key in ptc.re_values: - ptc.re_values[key] = pdtLocales['en_US'].re_consts[key] - else: - ptc.re_sources = pdtLocales['en_US'].re_sources - ptc.re_values = pdtLocales['en_US'].re_consts - ptc.Modifiers = pdtLocales['en_US'].modifiers - ptc.dayOffsets = pdtLocales['en_US'].dayoffsets - units = pdtLocales['en_US'].units - - # escape any regex special characters that may be found - wd = tuple(map(re.escape, ptc.Weekdays)) - swd = tuple(map(re.escape, ptc.shortWeekdays)) - mth = tuple(map(re.escape, ptc.Months)) - smth = tuple(map(re.escape, ptc.shortMonths)) - - ptc.re_values['months'] = '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' % mth - ptc.re_values['shortmonths'] = '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' % smth - ptc.re_values['days'] = '%s|%s|%s|%s|%s|%s|%s' % wd - ptc.re_values['shortdays'] = '%s|%s|%s|%s|%s|%s|%s' % swd - - l = [] - for unit in units: - l.append('|'.join(units[unit])) - - ptc.re_values['units'] = '|'.join(l) - ptc.Units = ptc.re_values['units'].split('|') - - -def _initSymbols(ptc): - """ - Helper function to initialize the single character constants - and other symbols needed. - """ - ptc.timeSep = [ u':' ] - ptc.dateSep = [ u'/' ] - ptc.meridian = [ u'AM', u'PM' ] - - ptc.usesMeridian = True - ptc.uses24 = False - - if pyicu and ptc.usePyICU: - am = u'' - pm = u'' - ts = '' - - # ICU doesn't seem to provide directly the - # date or time seperator - so we have to - # figure it out - o = ptc.icu_tf['short'] - s = ptc.timeFormats['short'] - - ptc.usesMeridian = u'a' in s - ptc.uses24 = u'H' in s - - # '11:45 AM' or '11:45' - s = o.format(datetime.datetime(2003, 10, 30, 11, 45)) - - # ': AM' or ':' - s = s.replace('11', '').replace('45', '') - - if len(s) > 0: - ts = s[0] - - if ptc.usesMeridian: - # '23:45 AM' or '23:45' - am = s[1:].strip() - s = o.format(datetime.datetime(2003, 10, 30, 23, 45)) - - if ptc.uses24: - s = s.replace('23', '') - else: - s = s.replace('11', '') - - # 'PM' or '' - pm = s.replace('45', '').replace(ts, '').strip() - - ptc.timeSep = [ ts ] - ptc.meridian = [ am, pm ] - - o = ptc.icu_df['short'] - s = o.format(datetime.datetime(2003, 10, 30, 11, 45)) - s = s.replace('10', '').replace('30', '').replace('03', '').replace('2003', '') - - if len(s) > 0: - ds = s[0] - else: - ds = '/' - - ptc.dateSep = [ ds ] - s = ptc.dateFormats['short'] - l = s.lower().split(ds) - dp_order = [] - - for s in l: - if len(s) > 0: - dp_order.append(s[:1]) - - ptc.dp_order = dp_order - else: - ptc.timeSep = ptc.locale.timeSep - ptc.dateSep = ptc.locale.dateSep - ptc.meridian = ptc.locale.meridian - ptc.usesMeridian = ptc.locale.usesMeridian - ptc.uses24 = ptc.locale.uses24 - ptc.dp_order = ptc.locale.dp_order - - # build am and pm lists to contain - # original case, lowercase and first-char - # versions of the meridian text - - if len(ptc.meridian) > 0: - am = ptc.meridian[0] - ptc.am = [ am ] - - if len(am) > 0: - ptc.am.append(am[0]) - am = am.lower() - ptc.am.append(am) - ptc.am.append(am[0]) - else: - am = '' - ptc.am = [ '', '' ] - - if len(ptc.meridian) > 1: - pm = ptc.meridian[1] - ptc.pm = [ pm ] - - if len(pm) > 0: - ptc.pm.append(pm[0]) - pm = pm.lower() - ptc.pm.append(pm) - ptc.pm.append(pm[0]) - else: - pm = '' - ptc.pm = [ '', '' ] - - -def _initPatterns(ptc): - """ - Helper function to take the different localized bits from ptc and - create the regex strings. - """ - # TODO add code to parse the date formats and build the regexes up from sub-parts - # TODO find all hard-coded uses of date/time seperators - - ptc.RE_DATE4 = r'''(?P<date>(((?P<day>\d\d?)(?P<suffix>%(daysuffix)s)?(,)?(\s)?) - (?P<mthname>(%(months)s|%(shortmonths)s))\s? - (?P<year>\d\d(\d\d)?)? - ) - )''' % ptc.re_values - - # I refactored DATE3 to fix Issue 16 http://code.google.com/p/parsedatetime/issues/detail?id=16 - # I suspect the final line was for a trailing time - but testing shows it's not needed - # ptc.RE_DATE3 = r'''(?P<date>((?P<mthname>(%(months)s|%(shortmonths)s))\s? - # ((?P<day>\d\d?)(\s?|%(daysuffix)s|$)+)? - # (,\s?(?P<year>\d\d(\d\d)?))?)) - # (\s?|$|[^0-9a-zA-Z])''' % ptc.re_values - ptc.RE_DATE3 = r'''(?P<date>( - (((?P<mthname>(%(months)s|%(shortmonths)s))| - ((?P<day>\d\d?)(?P<suffix>%(daysuffix)s)?))(\s)?){1,2} - ((,)?(\s)?(?P<year>\d\d(\d\d)?))? - ) - )''' % ptc.re_values - ptc.RE_MONTH = r'''(\s?|^) - (?P<month>( - (?P<mthname>(%(months)s|%(shortmonths)s)) - (\s?(?P<year>(\d\d\d\d)))? - )) - (\s?|$|[^0-9a-zA-Z])''' % ptc.re_values - ptc.RE_WEEKDAY = r'''(\s?|^) - (?P<weekday>(%(days)s|%(shortdays)s)) - (\s?|$|[^0-9a-zA-Z])''' % ptc.re_values - - ptc.RE_SPECIAL = r'(?P<special>^[%(specials)s]+)\s+' % ptc.re_values - ptc.RE_UNITS = r'''(?P<qty>(-?\d+\s* - (?P<units>((%(units)s)s?)) - ))''' % ptc.re_values - ptc.RE_QUNITS = r'''(?P<qty>(-?\d+\s? - (?P<qunits>%(qunits)s) - (\s?|,|$) - ))''' % ptc.re_values - ptc.RE_MODIFIER = r'''(\s?|^) - (?P<modifier> - (previous|prev|last|next|eod|eo|(end\sof)|(in\sa)))''' % ptc.re_values - ptc.RE_MODIFIER2 = r'''(\s?|^) - (?P<modifier> - (from|before|after|ago|prior)) - (\s?|$|[^0-9a-zA-Z])''' % ptc.re_values - ptc.RE_TIMEHMS = r'''(\s?|^) - (?P<hours>\d\d?) - (?P<tsep>%(timeseperator)s|) - (?P<minutes>\d\d) - (?:(?P=tsep)(?P<seconds>\d\d(?:[.,]\d+)?))?''' % ptc.re_values - ptc.RE_TIMEHMS2 = r'''(?P<hours>(\d\d?)) - ((?P<tsep>%(timeseperator)s|) - (?P<minutes>(\d\d?)) - (?:(?P=tsep) - (?P<seconds>\d\d? - (?:[.,]\d+)?))?)?''' % ptc.re_values - - if 'meridian' in ptc.re_values: - ptc.RE_TIMEHMS2 += r'\s?(?P<meridian>(%(meridian)s))' % ptc.re_values - - dateSeps = ''.join(ptc.dateSep) + '.' - - ptc.RE_DATE = r'''(\s?|^) - (?P<date>(\d\d?[%s]\d\d?([%s]\d\d(\d\d)?)?)) - (\s?|$|[^0-9a-zA-Z])''' % (dateSeps, dateSeps) - ptc.RE_DATE2 = r'[%s]' % dateSeps - ptc.RE_DAY = r'''(\s?|^) - (?P<day>(today|tomorrow|yesterday)) - (\s?|$|[^0-9a-zA-Z])''' % ptc.re_values - ptc.RE_DAY2 = r'''(?P<day>\d\d?)|(?P<suffix>%(daysuffix)s) - ''' % ptc.re_values - ptc.RE_TIME = r'''(\s?|^) - (?P<time>(morning|breakfast|noon|lunch|evening|midnight|tonight|dinner|night|now)) - (\s?|$|[^0-9a-zA-Z])''' % ptc.re_values - ptc.RE_REMAINING = r'\s+' - - # Regex for date/time ranges - ptc.RE_RTIMEHMS = r'''(\s?|^) - (\d\d?)%(timeseperator)s - (\d\d) - (%(timeseperator)s(\d\d))? - (\s?|$)''' % ptc.re_values - ptc.RE_RTIMEHMS2 = r'''(\s?|^) - (\d\d?) - (%(timeseperator)s(\d\d?))? - (%(timeseperator)s(\d\d?))?''' % ptc.re_values - - if 'meridian' in ptc.re_values: - ptc.RE_RTIMEHMS2 += r'\s?(%(meridian)s)' % ptc.re_values - - ptc.RE_RDATE = r'(\d+([%s]\d+)+)' % dateSeps - ptc.RE_RDATE3 = r'''((((%(months)s))\s? - ((\d\d?) - (\s?|%(daysuffix)s|$)+)? - (,\s?\d\d\d\d)?))''' % ptc.re_values - - # "06/07/06 - 08/09/06" - ptc.DATERNG1 = ptc.RE_RDATE + r'\s?%(rangeseperator)s\s?' + ptc.RE_RDATE - ptc.DATERNG1 = ptc.DATERNG1 % ptc.re_values - - # "march 31 - june 1st, 2006" - ptc.DATERNG2 = ptc.RE_RDATE3 + r'\s?%(rangeseperator)s\s?' + ptc.RE_RDATE3 - ptc.DATERNG2 = ptc.DATERNG2 % ptc.re_values - - # "march 1rd -13th" - ptc.DATERNG3 = ptc.RE_RDATE3 + r'\s?%(rangeseperator)s\s?(\d\d?)\s?(rd|st|nd|th)?' - ptc.DATERNG3 = ptc.DATERNG3 % ptc.re_values - - # "4:00:55 pm - 5:90:44 am", '4p-5p' - ptc.TIMERNG1 = ptc.RE_RTIMEHMS2 + r'\s?%(rangeseperator)s\s?' + ptc.RE_RTIMEHMS2 - ptc.TIMERNG1 = ptc.TIMERNG1 % ptc.re_values - - # "4:00 - 5:90 ", "4:55:55-3:44:55" - ptc.TIMERNG2 = ptc.RE_RTIMEHMS + r'\s?%(rangeseperator)s\s?' + ptc.RE_RTIMEHMS - ptc.TIMERNG2 = ptc.TIMERNG2 % ptc.re_values - - # "4-5pm " - ptc.TIMERNG3 = r'\d\d?\s?%(rangeseperator)s\s?' + ptc.RE_RTIMEHMS2 - ptc.TIMERNG3 = ptc.TIMERNG3 % ptc.re_values - - # "4:30-5pm " - ptc.TIMERNG4 = ptc.RE_RTIMEHMS + r'\s?%(rangeseperator)s\s?' + ptc.RE_RTIMEHMS2 - ptc.TIMERNG4 = ptc.TIMERNG4 % ptc.re_values - - -def _initConstants(ptc): - """ - Create localized versions of the units, week and month names - """ - # build weekday offsets - yes, it assumes the Weekday and shortWeekday - # lists are in the same order and Mon..Sun (Python style) - ptc.WeekdayOffsets = {} - - o = 0 - for key in ptc.Weekdays: - ptc.WeekdayOffsets[key] = o - o += 1 - o = 0 - for key in ptc.shortWeekdays: - ptc.WeekdayOffsets[key] = o - o += 1 - - # build month offsets - yes, it assumes the Months and shortMonths - # lists are in the same order and Jan..Dec - ptc.MonthOffsets = {} - - o = 1 - for key in ptc.Months: - ptc.MonthOffsets[key] = o - o += 1 - o = 1 - for key in ptc.shortMonths: - ptc.MonthOffsets[key] = o - o += 1 - - # ptc.DaySuffixes = ptc.re_consts['daysuffix'].split('|') - - -class Constants: - """ - Default set of constants for parsedatetime. - - If PyICU is present, then the class will first try to get PyICU - to return a locale specified by C{localeID}. If either C{localeID} is - None or if the locale does not exist within PyICU, then each of the - locales defined in C{fallbackLocales} is tried in order. - - If PyICU is not present or none of the specified locales can be used, - then the class will initialize itself to the en_US locale. - - if PyICU is not present or not requested, only the locales defined by - C{pdtLocales} will be searched. - """ - def __init__(self, localeID=None, usePyICU=True, fallbackLocales=['en_US']): - self.localeID = localeID - self.fallbackLocales = fallbackLocales - - if 'en_US' not in self.fallbackLocales: - self.fallbackLocales.append('en_US') - - # define non-locale specific constants - - self.locale = None - self.usePyICU = usePyICU - - # starting cache of leap years - # daysInMonth will add to this if during - # runtime it gets a request for a year not found - self._leapYears = [ 1904, 1908, 1912, 1916, 1920, 1924, 1928, 1932, 1936, 1940, 1944, - 1948, 1952, 1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, - 1992, 1996, 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, - 2036, 2040, 2044, 2048, 2052, 2056, 2060, 2064, 2068, 2072, 2076, - 2080, 2084, 2088, 2092, 2096 ] - - self.Second = 1 - self.Minute = 60 * self.Second - self.Hour = 60 * self.Minute - self.Day = 24 * self.Hour - self.Week = 7 * self.Day - self.Month = 30 * self.Day - self.Year = 365 * self.Day - - self.rangeSep = u'-' - - self._DaysInMonthList = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) - - self.BirthdayEpoch = 50 - - # DOWParseStyle controls how we parse "Tuesday" - # If the current day was Thursday and the text to parse is "Tuesday" - # then the following table shows how each style would be returned - # -1, 0, +1 - # - # Current day marked as *** - # - # Sun Mon Tue Wed Thu Fri Sat - # week -1 - # current -1,0 *** - # week +1 +1 - # - # If the current day was Monday and the text to parse is "Tuesday" - # then the following table shows how each style would be returned - # -1, 0, +1 - # - # Sun Mon Tue Wed Thu Fri Sat - # week -1 -1 - # current *** 0,+1 - # week +1 - - self.DOWParseStyle = 1 - - # CurrentDOWParseStyle controls how we parse "Friday" - # If the current day was Friday and the text to parse is "Friday" - # then the following table shows how each style would be returned - # True/False. This also depends on DOWParseStyle. - # - # Current day marked as *** - # - # DOWParseStyle = 0 - # Sun Mon Tue Wed Thu Fri Sat - # week -1 - # current T,F - # week +1 - # - # DOWParseStyle = -1 - # Sun Mon Tue Wed Thu Fri Sat - # week -1 F - # current T - # week +1 - # - # DOWParseStyle = +1 - # - # Sun Mon Tue Wed Thu Fri Sat - # week -1 - # current T - # week +1 F - - self.CurrentDOWParseStyle = False - - # initalize attributes to empty values to ensure - # they are defined - self.re_sources = None - self.re_values = None - self.Modifiers = None - self.dayOffsets = None - self.WeekdayOffsets = None - self.MonthOffsets = None - self.dateSep = None - self.timeSep = None - self.am = None - self.pm = None - self.meridian = None - self.usesMeridian = None - self.uses24 = None - self.dp_order = None - - self.RE_DATE4 = r'' - self.RE_DATE3 = r'' - self.RE_MONTH = r'' - self.RE_WEEKDAY = r'' - self.RE_SPECIAL = r'' - self.RE_UNITS = r'' - self.RE_QUNITS = r'' - self.RE_MODIFIER = r'' - self.RE_MODIFIER2 = r'' - self.RE_TIMEHMS = r'' - self.RE_TIMEHMS2 = r'' - self.RE_DATE = r'' - self.RE_DATE2 = r'' - self.RE_DAY = r'' - self.RE_DAY2 = r'' - self.RE_TIME = r'' - self.RE_REMAINING = r'' - self.RE_RTIMEHMS = r'' - self.RE_RTIMEHMS2 = r'' - self.RE_RDATE = r'' - self.RE_RDATE3 = r'' - self.DATERNG1 = r'' - self.DATERNG2 = r'' - self.DATERNG3 = r'' - self.TIMERNG1 = r'' - self.TIMERNG2 = r'' - self.TIMERNG3 = r'' - self.TIMERNG4 = r'' - - _initLocale(self) - _initConstants(self) - _initSymbols(self) - _initPatterns(self) - - self.re_option = re.IGNORECASE + re.VERBOSE - self.cre_source = { 'CRE_SPECIAL': self.RE_SPECIAL, - 'CRE_UNITS': self.RE_UNITS, - 'CRE_QUNITS': self.RE_QUNITS, - 'CRE_MODIFIER': self.RE_MODIFIER, - 'CRE_MODIFIER2': self.RE_MODIFIER2, - 'CRE_TIMEHMS': self.RE_TIMEHMS, - 'CRE_TIMEHMS2': self.RE_TIMEHMS2, - 'CRE_DATE': self.RE_DATE, - 'CRE_DATE2': self.RE_DATE2, - 'CRE_DATE3': self.RE_DATE3, - 'CRE_DATE4': self.RE_DATE4, - 'CRE_MONTH': self.RE_MONTH, - 'CRE_WEEKDAY': self.RE_WEEKDAY, - 'CRE_DAY': self.RE_DAY, - 'CRE_DAY2': self.RE_DAY2, - 'CRE_TIME': self.RE_TIME, - 'CRE_REMAINING': self.RE_REMAINING, - 'CRE_RTIMEHMS': self.RE_RTIMEHMS, - 'CRE_RTIMEHMS2': self.RE_RTIMEHMS2, - 'CRE_RDATE': self.RE_RDATE, - 'CRE_RDATE3': self.RE_RDATE3, - 'CRE_TIMERNG1': self.TIMERNG1, - 'CRE_TIMERNG2': self.TIMERNG2, - 'CRE_TIMERNG3': self.TIMERNG3, - 'CRE_TIMERNG4': self.TIMERNG4, - 'CRE_DATERNG1': self.DATERNG1, - 'CRE_DATERNG2': self.DATERNG2, - 'CRE_DATERNG3': self.DATERNG3, - } - self.cre_keys = self.cre_source.keys() - - - def __getattr__(self, name): - if name in self.cre_keys: - value = re.compile(self.cre_source[name], self.re_option) - setattr(self, name, value) - return value - else: - raise AttributeError, name - - def daysInMonth(self, month, year): - """ - Take the given month (1-12) and a given year (4 digit) return - the number of days in the month adjusting for leap year as needed - """ - result = None - - if month > 0 and month <= 12: - result = self._DaysInMonthList[month - 1] - - if month == 2: - if year in self._leapYears: - result += 1 - else: - if calendar.isleap(year): - self._leapYears.append(year) - result += 1 - - return result - - def buildSources(self, sourceTime=None): - """ - Return a dictionary of date/time tuples based on the keys - found in self.re_sources. - - The current time is used as the default and any specified - item found in self.re_sources is inserted into the value - and the generated dictionary is returned. - """ - if sourceTime is None: - (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = time.localtime() - else: - (yr, mth, dy, hr, mn, sec, wd, yd, isdst) = sourceTime - - sources = {} - defaults = { 'yr': yr, 'mth': mth, 'dy': dy, - 'hr': hr, 'mn': mn, 'sec': sec, } - - for item in self.re_sources: - values = {} - source = self.re_sources[item] - - for key in defaults.keys(): - if key in source: - values[key] = source[key] - else: - values[key] = defaults[key] - - sources[item] = ( values['yr'], values['mth'], values['dy'], - values['hr'], values['mn'], values['sec'], wd, yd, isdst ) - - return sources -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/support/parsedatetime/pdt_locales/__init__.py Tue Sep 06 00:09:31 2016 +0200 @@ -0,0 +1,30 @@ +# -*- encoding: utf-8 -*- + +""" +pdt_locales + +All of the included locale classes shipped with pdt. +""" + +from __future__ import absolute_import +from .icu import get_icu + +locales = ['de_DE', 'en_AU', 'en_US', 'es', 'nl_NL', 'pt_BR', 'ru_RU'] + +__locale_caches = {} + +__all__ = ['get_icu', 'load_locale'] + + +def load_locale(locale, icu=False): + """ + Return data of locale + :param locale: + :return: + """ + if locale not in locales: + raise NotImplementedError("The locale '%s' is not supported" % locale) + if locale not in __locale_caches: + mod = __import__(__name__, fromlist=[locale], level=0) + __locale_caches[locale] = getattr(mod, locale) + return __locale_caches[locale]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/support/parsedatetime/pdt_locales/base.py Tue Sep 06 00:09:31 2016 +0200 @@ -0,0 +1,199 @@ +from __future__ import unicode_literals + +locale_keys = set([ + 'MonthOffsets', 'Months', 'WeekdayOffsets', 'Weekdays', + 'dateFormats', 'dateSep', 'dayOffsets', 'dp_order', + 'localeID', 'meridian', 'Modifiers', 're_sources', 're_values', + 'shortMonths', 'shortWeekdays', 'timeFormats', 'timeSep', 'units', + 'uses24', 'usesMeridian', 'numbers', 'decimal_mark', 'small', + 'magnitude', 'ignore']) + +localeID = None + +dateSep = ['/', '.'] +timeSep = [':'] +meridian = ['AM', 'PM'] +usesMeridian = True +uses24 = True +WeekdayOffsets = {} +MonthOffsets = {} + +# always lowercase any lookup values - helper code expects that +Weekdays = [ + 'monday', 'tuesday', 'wednesday', 'thursday', + 'friday', 'saturday', 'sunday', +] + +shortWeekdays = [ + 'mon', 'tues|tue', 'wed', 'thu', 'fri', 'sat', 'sun', +] + +Months = [ + 'january', 'february', 'march', 'april', 'may', 'june', 'july', + 'august', 'september', 'october', 'november', 'december', +] + +shortMonths = [ + 'jan', 'feb', 'mar', 'apr', 'may', 'jun', + 'jul', 'aug', 'sep', 'oct', 'nov', 'dec', +] + +# use the same formats as ICU by default +dateFormats = { + 'full': 'EEEE, MMMM d, yyyy', + 'long': 'MMMM d, yyyy', + 'medium': 'MMM d, yyyy', + 'short': 'M/d/yy' +} + +timeFormats = { + 'full': 'h:mm:ss a z', + 'long': 'h:mm:ss a z', + 'medium': 'h:mm:ss a', + 'short': 'h:mm a', +} + +dp_order = ['m', 'd', 'y'] + +# Used to parse expressions like "in 5 hours" +numbers = { + 'zero': 0, + 'one': 1, + 'a': 1, + 'an': 1, + 'two': 2, + 'three': 3, + 'four': 4, + 'five': 5, + 'six': 6, + 'seven': 7, + 'eight': 8, + 'nine': 9, + 'ten': 10, + 'eleven': 11, + 'thirteen': 13, + 'fourteen': 14, + 'fifteen': 15, + 'sixteen': 16, + 'seventeen': 17, + 'eighteen': 18, + 'nineteen': 19, + 'twenty': 20, +} + +decimal_mark = '.' + + +# this will be added to re_values later +units = { + 'seconds': ['second', 'seconds', 'sec', 's'], + 'minutes': ['minute', 'minutes', 'min', 'm'], + 'hours': ['hour', 'hours', 'hr', 'h'], + 'days': ['day', 'days', 'dy', 'd'], + 'weeks': ['week', 'weeks', 'wk', 'w'], + 'months': ['month', 'months', 'mth'], + 'years': ['year', 'years', 'yr', 'y'], +} + + +# text constants to be used by later regular expressions +re_values = { + 'specials': 'in|on|of|at', + 'timeseparator': ':', + 'rangeseparator': '-', + 'daysuffix': 'rd|st|nd|th', + 'meridian': 'am|pm|a.m.|p.m.|a|p', + 'qunits': 'h|m|s|d|w|y', + 'now': ['now'], +} + +# Used to adjust the returned date before/after the source +Modifiers = { + 'from': 1, + 'before': -1, + 'after': 1, + 'ago': -1, + 'prior': -1, + 'prev': -1, + 'last': -1, + 'next': 1, + 'previous': -1, + 'end of': 0, + 'this': 0, + 'eod': 1, + 'eom': 1, + 'eoy': 1, +} + +dayOffsets = { + 'tomorrow': 1, + 'today': 0, + 'yesterday': -1, +} + +# special day and/or times, i.e. lunch, noon, evening +# each element in the dictionary is a dictionary that is used +# to fill in any value to be replace - the current date/time will +# already have been populated by the method buildSources +re_sources = { + 'noon': {'hr': 12, 'mn': 0, 'sec': 0}, + 'afternoon': {'hr': 13, 'mn': 0, 'sec': 0}, + 'lunch': {'hr': 12, 'mn': 0, 'sec': 0}, + 'morning': {'hr': 6, 'mn': 0, 'sec': 0}, + 'breakfast': {'hr': 8, 'mn': 0, 'sec': 0}, + 'dinner': {'hr': 19, 'mn': 0, 'sec': 0}, + 'evening': {'hr': 18, 'mn': 0, 'sec': 0}, + 'midnight': {'hr': 0, 'mn': 0, 'sec': 0}, + 'night': {'hr': 21, 'mn': 0, 'sec': 0}, + 'tonight': {'hr': 21, 'mn': 0, 'sec': 0}, + 'eod': {'hr': 17, 'mn': 0, 'sec': 0}, +} + +small = { + 'zero': 0, + 'one': 1, + 'a': 1, + 'an': 1, + 'two': 2, + 'three': 3, + 'four': 4, + 'five': 5, + 'six': 6, + 'seven': 7, + 'eight': 8, + 'nine': 9, + 'ten': 10, + 'eleven': 11, + 'twelve': 12, + 'thirteen': 13, + 'fourteen': 14, + 'fifteen': 15, + 'sixteen': 16, + 'seventeen': 17, + 'eighteen': 18, + 'nineteen': 19, + 'twenty': 20, + 'thirty': 30, + 'forty': 40, + 'fifty': 50, + 'sixty': 60, + 'seventy': 70, + 'eighty': 80, + 'ninety': 90 +} + +magnitude = { + 'thousand': 1000, + 'million': 1000000, + 'billion': 1000000000, + 'trillion': 1000000000000, + 'quadrillion': 1000000000000000, + 'quintillion': 1000000000000000000, + 'sextillion': 1000000000000000000000, + 'septillion': 1000000000000000000000000, + 'octillion': 1000000000000000000000000000, + 'nonillion': 1000000000000000000000000000000, + 'decillion': 1000000000000000000000000000000000, +} + +ignore = ('and', ',')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/support/parsedatetime/pdt_locales/de_DE.py Tue Sep 06 00:09:31 2016 +0200 @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from .base import * # noqa + +# don't use an unicode string +localeID = 'de_DE' +dateSep = ['.'] +timeSep = [':'] +meridian = [] +usesMeridian = False +uses24 = True +decimal_mark = ',' + +Weekdays = [ + 'montag', 'dienstag', 'mittwoch', + 'donnerstag', 'freitag', 'samstag', 'sonntag', +] +shortWeekdays = ['mo', 'di', 'mi', 'do', 'fr', 'sa', 'so'] +Months = [ + 'januar', 'februar', 'märz', + 'april', 'mai', 'juni', + 'juli', 'august', 'september', + 'oktober', 'november', 'dezember', +] +shortMonths = [ + 'jan', 'feb', 'mrz', 'apr', 'mai', 'jun', + 'jul', 'aug', 'sep', 'okt', 'nov', 'dez', +] + +dateFormats = { + 'full': 'EEEE, d. MMMM yyyy', + 'long': 'd. MMMM yyyy', + 'medium': 'dd.MM.yyyy', + 'short': 'dd.MM.yy', +} + +timeFormats = { + 'full': 'HH:mm:ss v', + 'long': 'HH:mm:ss z', + 'medium': 'HH:mm:ss', + 'short': 'HH:mm', +} + +dp_order = ['d', 'm', 'y'] + +# the short version would be a capital M, +# as I understand it we can't distinguish +# between m for minutes and M for months. +units = { + 'seconds': ['sekunden', 'sek', 's'], + 'minutes': ['minuten', 'min', 'm'], + 'hours': ['stunden', 'std', 'h'], + 'days': ['tag', 'tage', 't'], + 'weeks': ['wochen', 'w'], + 'months': ['monat', 'monate'], + 'years': ['jahr', 'jahre', 'j'], +} + +re_values = re_values.copy() +re_values.update({ + 'specials': 'am|dem|der|im|in|den|zum', + 'timeseparator': ':', + 'rangeseparator': '-', + 'daysuffix': '', + 'qunits': 'h|m|s|t|w|m|j', + 'now': ['jetzt'], +}) + +# Used to adjust the returned date before/after the source +# still looking for insight on how to translate all of them to german. +Modifiers = { + 'from': 1, + 'before': -1, + 'after': 1, + 'vergangener': -1, + 'vorheriger': -1, + 'prev': -1, + 'letzter': -1, + 'nächster': 1, + 'dieser': 0, + 'previous': -1, + 'in a': 2, + 'end of': 0, + 'eod': 0, + 'eo': 0, +} + +# morgen/abermorgen does not work, see +# http://code.google.com/p/parsedatetime/issues/detail?id=19 +dayOffsets = { + 'morgen': 1, + 'heute': 0, + 'gestern': -1, + 'vorgestern': -2, + 'übermorgen': 2, +} + +# special day and/or times, i.e. lunch, noon, evening +# each element in the dictionary is a dictionary that is used +# to fill in any value to be replace - the current date/time will +# already have been populated by the method buildSources +re_sources = { + 'mittag': {'hr': 12, 'mn': 0, 'sec': 0}, + 'mittags': {'hr': 12, 'mn': 0, 'sec': 0}, + 'mittagessen': {'hr': 12, 'mn': 0, 'sec': 0}, + 'morgen': {'hr': 6, 'mn': 0, 'sec': 0}, + 'morgens': {'hr': 6, 'mn': 0, 'sec': 0}, + 'frühstück': {'hr': 8, 'mn': 0, 'sec': 0}, + 'abendessen': {'hr': 19, 'mn': 0, 'sec': 0}, + 'abend': {'hr': 18, 'mn': 0, 'sec': 0}, + 'abends': {'hr': 18, 'mn': 0, 'sec': 0}, + 'mitternacht': {'hr': 0, 'mn': 0, 'sec': 0}, + 'nacht': {'hr': 21, 'mn': 0, 'sec': 0}, + 'nachts': {'hr': 21, 'mn': 0, 'sec': 0}, + 'heute abend': {'hr': 21, 'mn': 0, 'sec': 0}, + 'heute nacht': {'hr': 21, 'mn': 0, 'sec': 0}, + 'feierabend': {'hr': 17, 'mn': 0, 'sec': 0}, +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/support/parsedatetime/pdt_locales/en_AU.py Tue Sep 06 00:09:31 2016 +0200 @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from .base import * # noqa + +# don't use an unicode string +localeID = 'en_AU' +dateSep = ['-', '/'] +uses24 = False + +dateFormats = { + 'full': 'EEEE, d MMMM yyyy', + 'long': 'd MMMM yyyy', + 'medium': 'dd/MM/yyyy', + 'short': 'd/MM/yy', +} + +timeFormats = { + 'long': timeFormats['full'], +} + +dp_order = ['d', 'm', 'y']
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/support/parsedatetime/pdt_locales/en_US.py Tue Sep 06 00:09:31 2016 +0200 @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from .base import * # noqa + +# don't use an unicode string +localeID = 'en_US' +uses24 = False
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/support/parsedatetime/pdt_locales/es.py Tue Sep 06 00:09:31 2016 +0200 @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from .base import * # noqa + +# don't use an unicode string +localeID = 'es' +dateSep = ['/'] +usesMeridian = False +uses24 = True +decimal_mark = ',' + +Weekdays = [ + 'lunes', 'martes', 'miércoles', + 'jueves', 'viernes', 'sábado', 'domingo', +] +shortWeekdays = [ + 'lun', 'mar', 'mié', + 'jue', 'vie', 'sáb', 'dom', +] +Months = [ + 'enero', 'febrero', 'marzo', + 'abril', 'mayo', 'junio', + 'julio', 'agosto', 'septiembre', + 'octubre', 'noviembre', 'diciembre', +] +shortMonths = [ + 'ene', 'feb', 'mar', + 'abr', 'may', 'jun', + 'jul', 'ago', 'sep', + 'oct', 'nov', 'dic', +] +dateFormats = { + 'full': "EEEE d' de 'MMMM' de 'yyyy", + 'long': "d' de 'MMMM' de 'yyyy", + 'medium': "dd-MMM-yy", + 'short': "d/MM/yy", +} + +timeFormats = { + 'full': "HH'H'mm' 'ss z", + 'long': "HH:mm:ss z", + 'medium': "HH:mm:ss", + 'short': "HH:mm", +} + +dp_order = ['d', 'm', 'y']
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/support/parsedatetime/pdt_locales/icu.py Tue Sep 06 00:09:31 2016 +0200 @@ -0,0 +1,149 @@ +# -*- encoding: utf-8 -*- + +""" +pdt_locales + +All of the included locale classes shipped with pdt. +""" +import datetime + +try: + range = xrange +except NameError: + pass + +try: + import PyICU as pyicu +except: + pyicu = None + + +def icu_object(mapping): + return type('_icu', (object,), mapping) + + +def merge_weekdays(base_wd, icu_wd): + result = [] + for left, right in zip(base_wd, icu_wd): + if left == right: + result.append(left) + continue + left = set(left.split('|')) + right = set(right.split('|')) + result.append('|'.join(left | right)) + return result + + +def get_icu(locale): + from . import base + result = dict([(key, getattr(base, key)) + for key in dir(base) if not key.startswith('_')]) + result['icu'] = None + + if pyicu is None: + return icu_object(result) + + if locale is None: + locale = 'en_US' + result['icu'] = icu = pyicu.Locale(locale) + + if icu is None: + return icu_object(result) + + # grab spelled out format of all numbers from 0 to 100 + rbnf = pyicu.RuleBasedNumberFormat(pyicu.URBNFRuleSetTag.SPELLOUT, icu) + result['numbers'].update([(rbnf.format(i), i) for i in range(0, 100)]) + + symbols = result['symbols'] = pyicu.DateFormatSymbols(icu) + + # grab ICU list of weekdays, skipping first entry which + # is always blank + wd = [w.lower() for w in symbols.getWeekdays()[1:]] + swd = [sw.lower() for sw in symbols.getShortWeekdays()[1:]] + + # store them in our list with Monday first (ICU puts Sunday first) + result['Weekdays'] = merge_weekdays(result['Weekdays'], + wd[1:] + wd[0:1]) + result['shortWeekdays'] = merge_weekdays(result['shortWeekdays'], + swd[1:] + swd[0:1]) + result['Months'] = [m.lower() for m in symbols.getMonths()] + result['shortMonths'] = [sm.lower() for sm in symbols.getShortMonths()] + keys = ['full', 'long', 'medium', 'short'] + + createDateInstance = pyicu.DateFormat.createDateInstance + createTimeInstance = pyicu.DateFormat.createTimeInstance + icu_df = result['icu_df'] = { + 'full': createDateInstance(pyicu.DateFormat.kFull, icu), + 'long': createDateInstance(pyicu.DateFormat.kLong, icu), + 'medium': createDateInstance(pyicu.DateFormat.kMedium, icu), + 'short': createDateInstance(pyicu.DateFormat.kShort, icu), + } + icu_tf = result['icu_tf'] = { + 'full': createTimeInstance(pyicu.DateFormat.kFull, icu), + 'long': createTimeInstance(pyicu.DateFormat.kLong, icu), + 'medium': createTimeInstance(pyicu.DateFormat.kMedium, icu), + 'short': createTimeInstance(pyicu.DateFormat.kShort, icu), + } + + result['dateFormats'] = {} + result['timeFormats'] = {} + for x in keys: + result['dateFormats'][x] = icu_df[x].toPattern() + result['timeFormats'][x] = icu_tf[x].toPattern() + + am = pm = ts = '' + + # ICU doesn't seem to provide directly the date or time separator + # so we have to figure it out + o = result['icu_tf']['short'] + s = result['timeFormats']['short'] + + result['usesMeridian'] = 'a' in s + result['uses24'] = 'H' in s + + # '11:45 AM' or '11:45' + s = o.format(datetime.datetime(2003, 10, 30, 11, 45)) + + # ': AM' or ':' + s = s.replace('11', '').replace('45', '') + + if len(s) > 0: + ts = s[0] + + if result['usesMeridian']: + # '23:45 AM' or '23:45' + am = s[1:].strip() + s = o.format(datetime.datetime(2003, 10, 30, 23, 45)) + + if result['uses24']: + s = s.replace('23', '') + else: + s = s.replace('11', '') + + # 'PM' or '' + pm = s.replace('45', '').replace(ts, '').strip() + + result['timeSep'] = [ts] + result['meridian'] = [am, pm] if am and pm else [] + + o = result['icu_df']['short'] + s = o.format(datetime.datetime(2003, 10, 30, 11, 45)) + s = s.replace('10', '').replace('30', '').replace( + '03', '').replace('2003', '') + + if len(s) > 0: + ds = s[0] + else: + ds = '/' + + result['dateSep'] = [ds] + s = result['dateFormats']['short'] + l = s.lower().split(ds) + dp_order = [] + + for s in l: + if len(s) > 0: + dp_order.append(s[:1]) + + result['dp_order'] = dp_order + return icu_object(result)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/support/parsedatetime/pdt_locales/nl_NL.py Tue Sep 06 00:09:31 2016 +0200 @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from .base import * # noqa + +# don't use an unicode string +localeID = 'nl_NL' +dateSep = ['-', '/'] +timeSep = [':'] +meridian = [] +usesMeridian = False +uses24 = True +decimal_mark = ',' + +Weekdays = [ + 'maandag', 'dinsdag', 'woensdag', 'donderdag', + 'vrijdag', 'zaterdag', 'zondag', +] +shortWeekdays = [ + 'ma', 'di', 'wo', 'do', 'vr', 'za', 'zo', +] +Months = [ + 'januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', + 'augustus', 'september', 'oktober', 'november', 'december', +] +shortMonths = [ + 'jan', 'feb', 'mar', 'apr', 'mei', 'jun', + 'jul', 'aug', 'sep', 'okt', 'nov', 'dec', +] +dateFormats = { + 'full': 'EEEE, dd MMMM yyyy', + 'long': 'dd MMMM yyyy', + 'medium': 'dd-MM-yyyy', + 'short': 'dd-MM-yy', +} + +timeFormats = { + 'full': 'HH:mm:ss v', + 'long': 'HH:mm:ss z', + 'medium': 'HH:mm:ss', + 'short': 'HH:mm', +} + +dp_order = ['d', 'm', 'y'] + +# the short version would be a capital M, +# as I understand it we can't distinguish +# between m for minutes and M for months. +units = { + 'seconds': ['secunden', 'sec', 's'], + 'minutes': ['minuten', 'min', 'm'], + 'hours': ['uren', 'uur', 'h'], + 'days': ['dagen', 'dag', 'd'], + 'weeks': ['weken', 'w'], + 'months': ['maanden', 'maand'], + 'years': ['jaar', 'jaren', 'j'], +} + +re_values = re_values.copy() +re_values.update({ + 'specials': 'om', + 'timeseparator': ':', + 'rangeseparator': '-', + 'daysuffix': ' |de', + 'qunits': 'h|m|s|d|w|m|j', + 'now': ['nu'], +}) + +# Used to adjust the returned date before/after the source +# still looking for insight on how to translate all of them to german. +Modifiers = { + 'vanaf': 1, + 'voor': -1, + 'na': 1, + 'vorige': -1, + 'eervorige': -1, + 'prev': -1, + 'laastste': -1, + 'volgende': 1, + 'deze': 0, + 'vorige': -1, + 'over': 2, + 'eind van': 0, +} + +# morgen/abermorgen does not work, see +# http://code.google.com/p/parsedatetime/issues/detail?id=19 +dayOffsets = { + 'morgen': 1, + 'vandaag': 0, + 'gisteren': -1, + 'eergisteren': -2, + 'overmorgen': 2, +} + +# special day and/or times, i.e. lunch, noon, evening +# each element in the dictionary is a dictionary that is used +# to fill in any value to be replace - the current date/time will +# already have been populated by the method buildSources +re_sources = { + 'middag': {'hr': 12, 'mn': 0, 'sec': 0}, + 'vanmiddag': {'hr': 12, 'mn': 0, 'sec': 0}, + 'lunch': {'hr': 12, 'mn': 0, 'sec': 0}, + 'morgen': {'hr': 6, 'mn': 0, 'sec': 0}, + "'s morgens": {'hr': 6, 'mn': 0, 'sec': 0}, + 'ontbijt': {'hr': 8, 'mn': 0, 'sec': 0}, + 'avondeten': {'hr': 19, 'mn': 0, 'sec': 0}, + 'avond': {'hr': 18, 'mn': 0, 'sec': 0}, + 'avonds': {'hr': 18, 'mn': 0, 'sec': 0}, + 'middernacht': {'hr': 0, 'mn': 0, 'sec': 0}, + 'nacht': {'hr': 21, 'mn': 0, 'sec': 0}, + 'nachts': {'hr': 21, 'mn': 0, 'sec': 0}, + 'vanavond': {'hr': 21, 'mn': 0, 'sec': 0}, + 'vannacht': {'hr': 21, 'mn': 0, 'sec': 0}, +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/support/parsedatetime/pdt_locales/pt_BR.py Tue Sep 06 00:09:31 2016 +0200 @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from .base import * # noqa + +# don't use an unicode string +localeID = 'pt_BR' +dateSep = ['/'] +usesMeridian = False +uses24 = True +decimal_mark = ',' + +Weekdays = [ + 'segunda-feira', 'terça-feira', 'quarta-feira', + 'quinta-feira', 'sexta-feira', 'sábado', 'domingo', +] +shortWeekdays = [ + 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb', 'dom', +] +Months = [ + 'janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', + 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro' +] +shortMonths = [ + 'jan', 'fev', 'mar', 'abr', 'mai', 'jun', + 'jul', 'ago', 'set', 'out', 'nov', 'dez' +] +dateFormats = { + 'full': "EEEE, d' de 'MMMM' de 'yyyy", + 'long': "d' de 'MMMM' de 'yyyy", + 'medium': "dd-MM-yy", + 'short': "dd/MM/yyyy", +} + +timeFormats = { + 'full': "HH'H'mm' 'ss z", + 'long': "HH:mm:ss z", + 'medium': "HH:mm:ss", + 'short': "HH:mm", +} + +dp_order = ['d', 'm', 'y'] + +units = { + 'seconds': ['segundo', 'seg', 's'], + 'minutes': ['minuto', 'min', 'm'], + 'days': ['dia', 'dias', 'd'], + 'months': ['mês', 'meses'], +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/support/parsedatetime/pdt_locales/ru_RU.py Tue Sep 06 00:09:31 2016 +0200 @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from .base import * # noqa + +# don't use an unicode string +localeID = 'ru_RU' +dateSep = ['-', '.'] +timeSep = [':'] +meridian = [] +usesMeridian = False +uses24 = True + +Weekdays = [ + 'понедельник', 'вторник', 'среда', 'четверг', + 'пятница', 'суббота', 'воскресенье', +] +shortWeekdays = [ + 'пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс', +] +# library does not know how to conjugate words +# библиотека не умеет спрягать слова +Months = [ + 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', + 'августа', 'сентября', 'октября', 'ноября', 'декабря', +] +shortMonths = [ + 'явн', 'фев', 'мрт', 'апр', 'май', 'июн', + 'июл', 'авг', 'сен', 'окт', 'нбр', 'дек', +] +dateFormats = { + 'full': 'EEEE, dd MMMM yyyy', + 'long': 'dd MMMM yyyy', + 'medium': 'dd-MM-yyyy', + 'short': 'dd-MM-yy', +} + +timeFormats = { + 'full': 'HH:mm:ss v', + 'long': 'HH:mm:ss z', + 'medium': 'HH:mm:ss', + 'short': 'HH:mm', +} + +dp_order = ['d', 'm', 'y'] + +decimal_mark = '.' + +units = { + 'seconds': ['секунда', 'секунды', 'секунд', 'сек', 'с'], + 'minutes': ['минута', 'минуты', 'минут', 'мин', 'м'], + 'hours': ['час', 'часов', 'часа', 'ч'], + 'days': ['день', 'дней', 'д'], + 'weeks': ['неделя', 'недели', 'н'], + 'months': ['месяц', 'месяца', 'мес'], + 'years': ['год', 'года', 'годы', 'г'], +} + +re_values = re_values.copy() +re_values.update({ + 'specials': 'om', + 'timeseparator': ':', + 'rangeseparator': '-', + 'daysuffix': 'ого|ой|ий|тье', + 'qunits': 'д|мес|г|ч|н|м|с', + 'now': ['сейчас'], +}) + +Modifiers = { + 'после': 1, + 'назад': -1, + 'предыдущий': -1, + 'последний': -1, + 'далее': 1, + 'ранее': -1, +} + +dayOffsets = { + 'завтра': 1, + 'сегодня': 0, + 'вчера': -1, + 'позавчера': -2, + 'послезавтра': 2, +} + +re_sources = { + 'полдень': {'hr': 12, 'mn': 0, 'sec': 0}, + 'день': {'hr': 13, 'mn': 0, 'sec': 0}, + 'обед': {'hr': 12, 'mn': 0, 'sec': 0}, + 'утро': {'hr': 6, 'mn': 0, 'sec': 0}, + 'завтрак': {'hr': 8, 'mn': 0, 'sec': 0}, + 'ужин': {'hr': 19, 'mn': 0, 'sec': 0}, + 'вечер': {'hr': 18, 'mn': 0, 'sec': 0}, + 'полночь': {'hr': 0, 'mn': 0, 'sec': 0}, + 'ночь': {'hr': 21, 'mn': 0, 'sec': 0}, +} + +small = { + 'ноль': 0, + 'один': 1, + 'два': 2, + 'три': 3, + 'четыре': 4, + 'пять': 5, + 'шесть': 6, + 'семь': 7, + 'восемь': 8, + 'девять': 9, + 'десять': 10, + 'одиннадцать': 11, + 'двенадцать': 12, + 'тринадцать': 13, + 'четырнадцать': 14, + 'пятнадцать': 15, + 'шестнадцать': 16, + 'семнадцать': 17, + 'восемнадцать': 18, + 'девятнадцать': 19, + 'двадцать': 20, + 'тридцать': 30, + 'сорок': 40, + 'пятьдесят': 50, + 'шестьдесят': 60, + 'семьдесят': 70, + 'восемьдесят': 80, + 'девяносто': 90, +} + +numbers = { + 'ноль': 0, + 'один': 1, + 'два': 2, + 'три': 3, + 'четыре': 4, + 'пять': 5, + 'шесть': 6, + 'семь': 7, + 'восемь': 8, + 'девять': 9, + 'десять': 10, + 'одиннадцать': 11, + 'двенадцать': 12, + 'тринадцать': 13, + 'четырнадцать': 14, + 'пятнадцать': 15, + 'шестнадцать': 16, + 'семнадцать': 17, + 'восемнадцать': 18, + 'девятнадцать': 19, + 'двадцать': 20, +} + +magnitude = { + 'тысяча': 1000, + 'миллион': 1000000, + 'миллиард': 1000000000, + 'триллион': 1000000000000, + 'квадриллион': 1000000000000000, + 'квинтиллион': 1000000000000000000, + 'секстиллион': 1000000000000000000000, + 'септиллион': 1000000000000000000000000, + 'октиллион': 1000000000000000000000000000, + 'нониллион': 1000000000000000000000000000000, + 'дециллион': 1000000000000000000000000000000000, +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/support/parsedatetime/warns.py Tue Sep 06 00:09:31 2016 +0200 @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" +parsedatetime/warns.py + +All subclasses inherited from `Warning` class + +""" +from __future__ import absolute_import + +import warnings + + +class pdtDeprecationWarning(DeprecationWarning): + pass + + +class pdtPendingDeprecationWarning(PendingDeprecationWarning): + pass + + +class pdt20DeprecationWarning(pdtPendingDeprecationWarning): + pass + + +warnings.simplefilter('default', pdtDeprecationWarning) +warnings.simplefilter('ignore', pdtPendingDeprecationWarning)