def string_to_number(text):
    '''
    Used to evaluate string that are numbers
    #### Examples
    1. `four hundred` == `4 * 100` == `400`
    2. `sixty three thousand` == `(60+3) * 1000` == `63000`
    3. `one hundred thirty five` == `1 * 100 + (30+5)` == 135`
    4. `three hundred fifty two thousand seven hundred sixty one` = `(3 * 100 + (50 + 2)) * 1000 + 7 * 100 + (60 + 1)` == `352,761`
    '''
    if type(text) is str:
        try:
            # the text may already be a number.
            float(text.replace(',', ''))
            return float(text)
        except ValueError:
            text = text.lower()
            s = LONG.sub(
                lambda m: ' * %s' % NUMBERS.get(m.groupdict().get('n')), text)
            s = NUM.sub(
                lambda m: "(%s) " % '+'.join(
                    map(lambda n: str(NUMBERS.get(n)),
                        m.group().strip().split(' '))), s)
            if re.search('/[a-z]/', s):
                raise TimestringInvalid("Invalid characters in number string.")
            try:
                return eval(s)
            except SyntaxError:
                raise TimestringInvalid("Invalid number string.")
    else:
        return text
示例#2
0
def string_to_number(text):
    '''
    Used to evaluate string that are numbers
    #### Examples
    1. `four hundred` == `4 * 100` == `400`
    2. `sixty three thousand` == `(60+3) * 1000` == `63000`
    3. `one hundred thirty five` == `1 * 100 + (30+5)` == 135`
    4. `three hundred fifty two thousand seven hundred sixty one` = `(3 * 100 + (50 + 2)) * 1000 + 7 * 100 + (60 + 1)` == `352,761`
    '''
    if type(text) is str:
        try:
            # the text may already be a number.
            float(text.replace(',', ''))
            return float(text)
        except ValueError:
            text = text.lower()
            r = dict(one=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, fourty=40, fifty=50, sixty=60, seventy=70, eighty=80, ninety=90, hundred=100)
            s = re.sub('(?P<s>\s)(?P<n>hundred|thousand)', lambda m: ' * %s' % r.get(m.groupdict().get('n')), text)
            s = re.sub('((one|two|twenty|twelve|three|thirty|thirteen|four(teen|ty)?|five|fif(teen|ty)|six(teen|ty)?|seven(teen|ty)?|eight(een|y)?|nine(teen|ty)?|ten|eleven)\s?)+', lambda m: "(%s) " % '+'.join(map(lambda n: str(r.get(n)), m.group().strip().split(' '))), s)
            if re.search('/[a-z]/', s):
                raise TimestringInvalid("Invalid characters in number string.")
            try:
                return eval(s)
            except SyntaxError:
                raise TimestringInvalid("Invalid number string.")
    else:
        return text
示例#3
0
    def plus(self, duration: Union[str, int, float, timedelta]):
        """
        :return a new Date adjusted by the duration
        :param duration: int or float number of seconds or string of number and
         time unit. The number can begin with '-' to indicate subtraction
        """
        if self.date == 'infinity':
            return
        if isinstance(duration, timedelta):
            return Date(self.date + duration)
        if isinstance(duration, (str, unicode)):
            duration = duration.lower().strip()
            res = TIMESTRING_RE.search(duration)
            if res:
                res = res.groupdict()
            sign = -1 if duration.startswith('-') else 1
            num = res.get('num')
            unit = res.get('delta') or res.get('delta_2')
            return self.plus_(num, unit, sign)
        elif isinstance(duration, (float, int)):
            new = copy(self)
            new.date = new.date + timedelta(seconds=duration)
            return new

        raise TimestringInvalid('Invalid type for plus(): %s' %
                                (type(duration)))
示例#4
0
    def adjust(self, to):
        '''
        Adjusts the time from kwargs to timedelta
        **Will change this object**

        return new copy of self
        '''
        if self.date == 'infinity':
            return
        new = copy(self)
        if type(to) in (str, unicode):
            to = to.lower()
            res = TIMESTRING_RE.search(to)
            if res:
                rgroup = res.groupdict()
                if (rgroup.get('delta') or rgroup.get('delta_2')):
                    i = int(text2num(rgroup.get(
                        'num', 'one'))) * (-1 if to.startswith('-') else 1)
                    delta = (rgroup.get('delta')
                             or rgroup.get('delta_2')).lower()
                    if delta.startswith('y'):
                        try:
                            new.date = new.date.replace(year=(new.date.year +
                                                              i))
                        except ValueError:
                            # day is out of range for month
                            new.date = new.date + timedelta(days=(365 * i))
                    elif delta.startswith('month'):
                        if (new.date.month + i) > 12:
                            new.date = new.date.replace(month=(i - (i / 12)),
                                                        year=(new.date.year +
                                                              1 + (i / 12)))
                        elif (new.date.month + i) < 1:
                            new.date = new.date.replace(month=12,
                                                        year=(new.date.year -
                                                              1))
                        else:
                            new.date = new.date.replace(month=(new.date.month +
                                                               i))
                    elif delta.startswith('q'):
                        # NP
                        pass
                    elif delta.startswith('w'):
                        new.date = new.date + timedelta(days=(7 * i))
                    elif delta.startswith('s'):
                        new.date = new.date + timedelta(seconds=i)
                    else:
                        new.date = new.date + timedelta(
                            **{
                                ('days' if delta.startswith('d') else 'hours' if delta.startswith('h') else 'minutes' if delta.startswith('m') else 'seconds'):
                                i
                            })
                    return new
        else:
            new.date = new.date + timedelta(seconds=int(to))
            return new

        raise TimestringInvalid('Invalid addition request')
示例#5
0
    def plus_(self, num: Union[str, int, float], unit: str, sign: int = 1):
        assert sign in [-1, 1]
        mag = get_num(num)
        n = sign * mag
        whole = int(n)
        fraction = n - whole

        unit = unit.lower().strip()
        new_date = copy(self.date)
        if unit.startswith('y'):
            try:
                new_date = new_date.replace(year=new_date.year + whole)
                new_date += timedelta(days=365 * fraction)
            except ValueError:  # Leap date in a non-leap year
                new_date += timedelta(days=365 * n)
        elif unit.startswith('month'):
            try:
                month = new_date.month + whole
                new_date = new_date.replace(year=new_date.year + month // 12,
                                            month=abs(month) % 12)
                new_date += timedelta(days=30 * fraction)
            except ValueError:  # No such day in that month
                new_date += timedelta(days=30 * n)

        elif unit.startswith('q'):
            # TODO This section is not working
            q1 = datetime(new_date.year, 1, 1)
            q2 = datetime(new_date.year, 4, 1)
            q3 = datetime(new_date.year, 7, 1)
            q4 = datetime(new_date.year, 10, 1)
            if q1 <= new_date < q2:
                if n == -1:
                    new_date = datetime(new_date.year - 1, 10, 1)
                else:
                    new_date = q2
            elif q2 <= new_date < q3:
                pass
            elif q3 <= new_date < q4:
                pass
            else:
                pass
            new_date += timedelta(days=91 * n)

        else:
            _unit = TIMEDELTA_UNITS.get(unit[0])
            if _unit:
                new_date += timedelta(**{_unit: n})
            else:
                raise TimestringInvalid('Unknown time unit: ' + unit)

        return Date(new_date)
示例#6
0
def get_num(num):
    """
    :param num: int, float or string repersenting a number, such as '1.5' or
    'one'
    """
    if isinstance(num, (int, float)):
        return num

    if 'couple' in (num or ''):
        return 2

    try:
        return float(num)
    except ValueError:
        try:
            return w2n.word_to_num(num or 'one')
        except ValueError:
            raise TimestringInvalid('Unknown number: %s' % num)
示例#7
0
    def __init__(self,
                 date,
                 offset=None,
                 start_of_week=None,
                 tz=None,
                 verbose=False):
        if isinstance(date, Date):
            self.date = copy(date.date)
            return

        # The original request
        self._original = date
        if tz:
            tz = pytz.timezone(str(tz))

        if date == 'infinity':
            self.date = 'infinity'

        elif date == 'now':
            self.date = datetime.now()

        elif type(date) in (str, unicode) and re.match(
                r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+-\d{2}", date):
            self.date = datetime.strptime(
                date[:-3],
                "%Y-%m-%d %H:%M:%S.%f") - timedelta(hours=int(date[-3:]))

        else:
            # Determinal starting date.
            if type(date) in (str, unicode):
                """The date is a string and needs to be converted into a <dict> for processesing
                """
                _date = date.lower()
                res = TIMESTRING_RE.search(_date.strip())
                if res:
                    date = res.groupdict()
                    if verbose:
                        print(
                            "Matches:\n", ''.join([
                                "\t%s: %s\n" % (k, v) for k, v in date.items()
                                if v
                            ]))
                else:
                    raise TimestringInvalid('Invalid date string >> %s' % date)

                date = dict((k, v if type(v) is str else v)
                            for k, v in date.items() if v)
                #print(_date, dict(map(lambda a: (a, date.get(a)), filter(lambda a: date.get(a), date))))

            if isinstance(date, dict):
                # Initial date.
                new_date = datetime(*time.localtime()[:3])
                if tz and tz.zone != "UTC":
                    #
                    # The purpose here is to adjust what day it is based on the timezeone
                    #
                    ts = datetime.now()
                    # Daylight savings === second Sunday in March and reverts to standard time on the first Sunday in November
                    # Monday is 0 and Sunday is 6.
                    # 14 days - dst_start.weekday()
                    dst_start = datetime(
                        ts.year, 3, 1, 2, 0,
                        0) + timedelta(13 - datetime(ts.year, 3, 1).weekday())
                    dst_end = datetime(
                        ts.year, 11, 1, 2, 0,
                        0) + timedelta(6 - datetime(ts.year, 11, 1).weekday())

                    ts = ts + tz.utcoffset(new_date,
                                           is_dst=(dst_start < ts < dst_end))
                    new_date = datetime(ts.year, ts.month, ts.day)

                if date.get('unixtime'):
                    new_date = datetime.fromtimestamp(int(
                        date.get('unixtime')))

                # !number of (days|...) (ago)?
                elif date.get('num') and (date.get('delta')
                                          or date.get('delta_2')):
                    if date.get('num', '').find('couple') > -1:
                        i = 2 * int(1 if date.get('ago', True)
                                    or date.get('ref') == 'last' else -1)
                    else:
                        i = int(text2num(date.get(
                            'num', 'one'))) * int(1 if date.get('ago') or (
                                date.get('ref', '') or '') == 'last' else -1)

                    delta = (date.get('delta') or date.get('delta_2')).lower()
                    if delta.startswith('y'):
                        try:
                            new_date = new_date.replace(year=(new_date.year -
                                                              i))
                        # day is out of range for month
                        except ValueError:
                            new_date = new_date - timedelta(days=(365 * i))
                    elif delta.startswith('month'):
                        try:
                            new_date = new_date.replace(month=(new_date.month -
                                                               i))
                        # day is out of range for month
                        except ValueError:
                            new_date = new_date - timedelta(days=(30 * i))

                    elif delta.startswith('q'):
                        '''
                        This section is not working...
                        Most likely need a generator that will take me to the right quater.
                        '''
                        q1, q2, q3, q4 = datetime(
                            new_date.year, 1,
                            1), datetime(new_date.year, 4, 1), datetime(
                                new_date.year, 7,
                                1), datetime(new_date.year, 10, 1)
                        if q1 <= new_date < q2:
                            # We are in Q1
                            if i == -1:
                                new_date = datetime(new_date.year - 1, 10, 1)
                            else:
                                new_date = q2
                        elif q2 <= new_date < q3:
                            # We are in Q2
                            pass
                        elif q3 <= new_date < q4:
                            # We are in Q3
                            pass
                        else:
                            # We are in Q4
                            pass
                        new_date = new_date - timedelta(days=(91 * i))

                    elif delta.startswith('w'):
                        new_date = new_date - timedelta(days=(i * 7))

                    else:
                        new_date = new_date - timedelta(
                            **{
                                ('days' if delta.startswith('d') else 'hours' if delta.startswith('h') else 'minutes' if delta.startswith('m') else 'seconds'):
                                i
                            })

                # !dow
                if [
                        date.get(key) for key in ('day', 'day_2', 'day_3')
                        if date.get(key)
                ]:
                    dow = max([
                        date.get(key) for key in ('day', 'day_2', 'day_3')
                        if date.get(key)
                    ])
                    iso = dict(monday=1,
                               tuesday=2,
                               wednesday=3,
                               thursday=4,
                               friday=5,
                               saturday=6,
                               sunday=7,
                               mon=1,
                               tue=2,
                               tues=2,
                               wed=3,
                               wedn=3,
                               thu=4,
                               thur=4,
                               fri=5,
                               sat=6,
                               sun=7).get(dow)
                    if iso:
                        # determin which direction
                        if date.get('ref') not in ('this', 'next'):
                            days = iso - new_date.isoweekday() - (
                                7 if iso >= new_date.isoweekday() else 0)
                        else:
                            days = iso - new_date.isoweekday() + (
                                7 if iso < new_date.isoweekday() else 0)

                        new_date = new_date + timedelta(days=days)

                    elif dow == 'yesterday':
                        new_date = new_date - timedelta(days=1)
                    elif dow == 'tomorrow':
                        new_date = new_date + timedelta(days=1)

                # !year
                year = [
                    int(CLEAN_NUMBER.sub('', date[key]))
                    for key in ('year', 'year_2', 'year_3', 'year_4', 'year_5',
                                'year_6') if date.get(key)
                ]
                if year:
                    year = max(year)
                    if len(str(year)) != 4:
                        year += 2000 if year <= 40 else 1900
                    try:
                        new_date = new_date.replace(year=year)
                    except Exception:
                        new_date = new_date.replace(year=year, day=28)

                # !month
                month = [
                    date.get(key) for key in ('month', 'month_1', 'month_2',
                                              'month_3', 'month_4')
                    if date.get(key)
                ]
                if month:
                    new_date = new_date.replace(day=1)
                    new_date = new_date.replace(
                        month=int(max(month)) if re.match('^\d+$', max(month))
                        else dict(january=1,
                                  february=2,
                                  march=3,
                                  april=4,
                                  june=6,
                                  july=7,
                                  august=8,
                                  september=9,
                                  october=10,
                                  november=11,
                                  december=12,
                                  jan=1,
                                  feb=2,
                                  mar=3,
                                  apr=4,
                                  may=5,
                                  jun=6,
                                  jul=7,
                                  aug=8,
                                  sep=9,
                                  sept=9,
                                  oct=10,
                                  nov=11,
                                  dec=12).get(max(month), new_date.month))

                # !day
                day = [
                    date.get(key) for key in ('date', 'date_2', 'date_3')
                    if date.get(key)
                ]
                if day:
                    new_date = new_date.replace(day=int(max(day)))

                # !daytime
                if date.get('daytime'):
                    if date['daytime'].find('this time') >= 1:
                        new_date = new_date.replace(
                            hour=datetime(*time.localtime()[:5]).hour,
                            minute=datetime(*time.localtime()[:5]).minute)
                    else:
                        new_date = new_date.replace(hour=dict(
                            morning=9,
                            noon=12,
                            afternoon=15,
                            evening=18,
                            night=21,
                            nighttime=21,
                            midnight=24).get(date.get('daytime'), 12))
                    # No offset because the hour was set.
                    offset = False

                # !hour
                hour = [
                    date.get(key) for key in ('hour', 'hour_2', 'hour_3')
                    if date.get(key)
                ]
                if hour:
                    new_date = new_date.replace(hour=int(max(hour)))
                    am = [
                        date.get(key) for key in ('am', 'am_1')
                        if date.get(key)
                    ]
                    if am and max(am) in ('p', 'pm'):
                        h = int(max(hour))
                        if h < 12:
                            new_date = new_date.replace(hour=h + 12)
                    # No offset because the hour was set.
                    offset = False

                    #minute
                    minute = [
                        date.get(key) for key in ('minute', 'minute_2')
                        if date.get(key)
                    ]
                    if minute:
                        new_date = new_date.replace(minute=int(max(minute)))

                    #second
                    seconds = date.get('seconds', 0)
                    if seconds:
                        new_date = new_date.replace(second=int(seconds))

                self.date = new_date

            elif type(date) in (int, long, float) and re.match(
                    '^\d{10}$', str(date)):
                self.date = datetime.fromtimestamp(int(date))

            elif isinstance(date, datetime):
                self.date = date

            elif date is None:
                self.date = datetime.now()

            else:
                # Set to the current date Y, M, D, H0, M0, S0
                self.date = datetime(*time.localtime()[:3])

            if tz:
                self.date = self.date.replace(tzinfo=tz)

            # end if type(date) is types.DictType: and self.date.hour == 0:
            if offset and isinstance(offset, dict):
                self.date = self.date.replace(**offset)
示例#8
0
    def __init__(self,
                 start,
                 end=None,
                 offset=None,
                 start_of_week=0,
                 tz=None,
                 verbose=False):
        """`start` can be type <class timestring.Date> or <type str>
        """
        self._dates = []
        pgoffset = None

        if start is None:
            raise TimestringInvalid("Range object requires a start valie")

        if not isinstance(start, (Date, datetime)):
            start = str(start)
        if end and not isinstance(end, (Date, datetime)):
            end = str(end)

        if start and end:
            """start and end provided
            """
            self._dates = (Date(start, tz=tz), Date(end, tz=tz))

        elif start == 'infinity':
            # end was not provided
            self._dates = (Date('infinity'), Date('infinity'))

        elif re.search(r'(\s(and|to)\s)', start):
            """Both sides where provided in the start
            """
            start = re.sub('^(between|from)\s', '', start.lower())
            # Both arguments found in start variable
            r = tuple(re.split(r'(\s(and|to)\s)', start.strip()))
            self._dates = (Date(r[0], tz=tz), Date(r[-1], tz=tz))

        elif re.match(
                r"(\[|\()((\"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+)?(\+|\-)\d{2}\")|infinity),((\"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+)?(\+|\-)\d{2}\")|infinity)(\]|\))",
                start):
            """postgresql tsrange and tstzranges support
            """
            start, end = tuple(
                re.sub('[^\w\s\-\:\.\+\,]', '', start).split(','))
            self._dates = (Date(start), Date(end))

        else:
            now = datetime.now()
            # no tz info but offset provided, we are UTC so convert

            if re.search(r"(\+|\-)\d{2}$", start):
                # postgresql tsrange and tstzranges
                pgoffset = re.search(r"(\+|\-)\d{2}$",
                                     start).group() + " hours"

            # tz info provided
            if tz:
                now = now.replace(tzinfo=pytz.timezone(str(tz)))

            # Parse
            res = TIMESTRING_RE.search(start)
            if res:
                group = res.groupdict()
                if verbose:
                    print(
                        dict(
                            map(lambda a: (a, group.get(a)),
                                filter(lambda a: group.get(a), group))))
                if (group.get('delta') or group.get('delta_2')) is not None:
                    delta = (group.get('delta')
                             or group.get('delta_2')).lower()

                    # always start w/ today
                    start = Date("today", offset=offset, tz=tz)

                    # make delta
                    di = "%s %s" % (str(int(group['num'] or 1)), delta)

                    # this           [   x  ]
                    if group['ref'] == 'this':

                        if delta.startswith('y'):
                            start = Date(datetime(now.year, 1, 1),
                                         offset=offset,
                                         tz=tz)

                        # month
                        elif delta.startswith('month'):
                            start = Date(datetime(now.year, now.month, 1),
                                         offset=offset,
                                         tz=tz)

                        # week
                        elif delta.startswith('w'):
                            start = Date("today", offset=offset, tz=tz) - (str(
                                Date("today", tz=tz).date.weekday()) + ' days')

                        # day
                        elif delta.startswith('d'):
                            start = Date("today", offset=offset, tz=tz)

                        # hour
                        elif delta.startswith('h'):
                            start = Date("today",
                                         offset=dict(hour=now.hour + 1),
                                         tz=tz)

                        # minute, second
                        elif delta.startswith('m') or delta.startswith('s'):
                            start = Date("now", tz=tz)

                        else:
                            raise TimestringInvalid(
                                "Not a valid time reference")

                        end = start + di

                    #next          x [      ]
                    elif group['ref'] == 'next':
                        if int(group['num'] or 1) > 1:
                            di = "%s %s" % (str(int(group['num'] or 1) - 1),
                                            delta)
                        end = start + di

                    # ago             [     ] x
                    elif group.get('ago') or group['ref'] == 'last' and int(
                            group['num'] or 1) == 1:
                        #if group['ref'] == 'last' and int(group['num'] or 1) == 1:
                        #    start = start - ('1 ' + delta)
                        end = start - di

                    # last & no ref   [    x]
                    else:
                        # need to include today with this reference
                        if not (delta.startswith('h') or delta.startswith('m')
                                or delta.startswith('s')):
                            start = Range('today', offset=offset, tz=tz).end
                        end = start - di

                elif group.get('month_1'):
                    # a single month of this yeear
                    start = Date(start, offset=offset, tz=tz)
                    start = start.replace(day=1)
                    end = start + '1 month'

                elif group.get('year_5'):
                    # a whole year
                    start = Date(start, offset=offset, tz=tz)
                    start = start.replace(day=1, month=1)
                    end = start + '1 year'

                else:
                    # after all else, we set the end to + 1 day
                    start = Date(start, offset=offset, tz=tz)
                    end = start + '1 day'

            else:
                raise TimestringInvalid("Invalid timestring request")

            if end is None:
                # no end provided, so assume 24 hours
                end = start + '24 hours'

            if start > end:
                # flip them if this is so
                start, end = copy(end), copy(start)

            if pgoffset:
                start = start - pgoffset
                if end != 'infinity':
                    end = end - pgoffset

            self._dates = (start, end)

        if self._dates[0] > self._dates[1]:
            self._dates = (self._dates[0], self._dates[1] + '1 day')
示例#9
0
    def __init__(self,
                 date=None,
                 offset: dict = None,
                 tz: str = None,
                 now: datetime = None,
                 verbose=False,
                 context=None):
        self._original = date
        if tz:
            tz = pytz.timezone(str(tz))
        else:
            tz = None

        if not now:
            now = datetime.now(tz)

        if isinstance(date, Date):
            self.date = copy(date.date)

        elif isinstance(date, datetime):
            self.date = date

        elif date == 'now' or date is None:
            self.date = now

        elif date == 'infinity':
            self.date = 'infinity'

        elif isinstance(date, (str, unicode)) and re.match(
                r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+-\d{2}", date):
            self.date = datetime.strptime(
                date[:-3],
                "%Y-%m-%d %H:%M:%S.%f") - timedelta(hours=int(date[-3:]))

        elif isinstance(date, (str, unicode, dict)):
            if type(date) in (str, unicode):
                # Convert the string to a dict
                _date = date.lower()
                res = TIMESTRING_RE.search(_date.strip())

                if res:
                    date = res.groupdict()
                    if verbose:
                        print(
                            "Matches:\n", ''.join([
                                "\t%s: %s\n" % (k, v) for k, v in date.items()
                                if v
                            ]))
                else:
                    raise TimestringInvalid('Invalid date string: %s' % date)

                date = dict((k, v if type(v) is str else v)
                            for k, v in date.items() if v)

            new_date = copy(now)

            # TODO Refactor
            if isinstance(date, dict):  # This will always be True
                unit = date.get('delta') or date.get('delta')
                num = date.get('num')

                if date.get('unixtime'):
                    new_date = datetime.fromtimestamp(int(
                        date.get('unixtime')))

                # Number of (days|...) [ago]
                elif num and unit:
                    unit = unit.lower()
                    if date.get('ago') or context == Context.PREV or date.get(
                            'prev'):
                        sign = -1
                    elif date.get('in') or date.get(
                            'from_now') or context == Context.NEXT or date.get(
                                'next'):
                        sign = 1
                    else:
                        raise TimestringInvalid(
                            'Missing relationship such as "ago" or "from now"')

                    new_date = Date(new_date).plus_(num, unit, sign).date

                weekday = date.get('weekday')
                relative_day = date.get('relative_day')
                if weekday:
                    new_date = new_date.replace(hour=0,
                                                minute=0,
                                                second=0,
                                                microsecond=0)
                    iso = WEEKDAY_ORDINALS.get(weekday)
                    if iso is not None:
                        days = iso - new_date.isoweekday()
                        if date.get('prev') or context == Context.PREV:
                            if iso >= new_date.isoweekday():
                                days -= 7
                        elif not (days == 0 and context
                                  in [Context.PAST, Context.FUTURE]):
                            if iso <= new_date.isoweekday():
                                days += 7
                        new_date += timedelta(days=days)
                elif relative_day:
                    days = RELATIVE_DAYS.get(re.sub(r'\s+', ' ', relative_day))
                    if days:
                        new_date += timedelta(days=days)
                    new_date = new_date.replace(hour=0,
                                                minute=0,
                                                second=0,
                                                microsecond=0)

                # !year
                year = [
                    int(CLEAN_NUMBER.sub('', date[key]))
                    for key in ('year', 'year_2', 'year_3', 'year_4', 'year_5',
                                'year_6') if date.get(key)
                ]
                if year:
                    if date.get('recurrence'):
                        TimestringInvalid('"next" %s' % year)
                    year = max(year)
                    if len(str(year)) != 4:
                        year += 2000 if year <= 40 else 1900
                    new_date = new_date.replace(year=year)

                # !month
                month = [
                    date.get(key) for key in ('month', 'month_1', 'month_2',
                                              'month_3', 'month_4', 'month_5')
                    if date.get(key)
                ]
                if month:
                    month_ = max(month)
                    if month_.isdigit():
                        month_ord = int(month_)
                        if not 1 <= month_ord <= 12:
                            raise TimestringInvalid(
                                'Month not in range 1..12:' + month_)
                    else:
                        month_ord = MONTH_ORDINALS.get(month_, new_date.month)

                    new_date = new_date.replace(month=int(month_ord))

                    if year == []:
                        if date.get('next') or context == Context.NEXT:
                            if month_ord <= now.month:
                                new_date = new_date.replace(
                                    year=new_date.year + 1)
                        elif date.get('prev') or context == Context.PREV:
                            if month_ord >= now.month:
                                new_date = new_date.replace(
                                    year=new_date.year - 1)
                        elif month_ord < now.month and not year:
                            new_date = new_date.replace(year=new_date.year + 1)

                # !day
                day = [
                    date.get(key)
                    for key in ('date', 'date_2', 'date_3', 'date_4')
                    if date.get(key)
                ]
                if day:
                    if date.get('recurrence'):
                        TimestringInvalid('"next" %s' % day)
                    new_date = new_date.replace(day=int(max(day)))

                # !daytime
                daytime = date.get('daytime')
                if daytime:
                    if 'this time' not in daytime:
                        _hour = DAYTIMES.get(date.get('daytime'), 12)
                        new_date = new_date.replace(hour=_hour,
                                                    minute=0,
                                                    second=0,
                                                    microsecond=0)
                    # No offset because the hour was set.
                    offset = False

                # !hour
                hour = [
                    date.get(key) for key in ('hour', 'hour_2', 'hour_3')
                    if date.get(key)
                ]
                if hour:
                    new_date = new_date.replace(hour=int(max(hour)),
                                                minute=0,
                                                second=0)
                    am = [
                        date.get(key) for key in ('am', 'am_1')
                        if date.get(key)
                    ]
                    if am and max(am) in ('p', 'pm'):
                        h = int(max(hour))
                        if h < 12:
                            new_date = new_date.replace(hour=h + 12)
                    # No offset because the hour was set.
                    offset = False

                    minute = [
                        date.get(key) for key in ('minute', 'minute_2')
                        if date.get(key)
                    ]
                    if minute:
                        new_date = new_date.replace(minute=int(max(minute)))

                    seconds = date.get('seconds', 0)
                    if seconds:
                        new_date = new_date.replace(second=int(seconds))

                    new_date = new_date.replace(microsecond=0)

                    if not day and not relative_day and new_date < now:
                        new_date += timedelta(days=1)

                if year != [] and not month and weekday is None and not day:
                    new_date = new_date.replace(month=1)
                if (year != []
                        or month) and weekday is None and not (day or hour):
                    new_date = new_date.replace(day=1)
                if not hour and daytime is None and not unit:
                    new_date = new_date.replace(hour=0, minute=0, second=0)

            self.date = new_date

        else:
            raise TimestringInvalid('Invalid type for constructing Date')

        if offset and isinstance(offset, dict):
            self.date = self.date.replace(**offset)
示例#10
0
    def __init__(self, start: Union[int, str, long, float, datetime, Date],
                 end: Union[datetime, Date] = None, offset: dict = None,
                 week_start: int = 1, tz: str = None,
                 verbose=False, context: Context = None):
        """`start` can be type <class timestring.Date> or <type str>
        """
        self._dates = []
        pgoffset = None
        if tz:
            tz = pytz.timezone(str(tz))

        if start is None:
            raise TimestringInvalid("Range object requires a start value")

        if not isinstance(start, (Date, datetime)):
            start = str(start)
        if end and not isinstance(end, (Date, datetime)):
            end = str(end)

        if start and end:
            self._dates = (Date(start, tz=tz), Date(end, tz=tz))

        elif start == 'infinity':
            self._dates = (Date('infinity'), Date('infinity'))

        elif re.search(r'(\s(and|to)\s)', start):
            # Both sides are provided in string "start"
            start = re.sub('^(between|from)\s', '', start.lower())
            r = tuple(re.split(r'(\s(and|to)\s)', start.strip()))
            start = Date(r[0], tz=tz)
            self._dates = start, Date(r[-1], now=start.date)

        elif POSTGRES_RANGE_RE.match(start):
            # Postgresql tsrange and tstzranges support
            start, end = re.sub('[^\w\s\-\:\.\+\,]', '', start).split(',')
            self._dates = Date(start), Date(end)

        else:
            now = datetime.now(tz)

            if re.search(r"(\+|\-)\d{2}$", start):
                # postgresql tsrange and tstzranges
                pgoffset = re.search(r"(\+|\-)\d{2}$", start).group() + " hours"

            # Parse
            res = TIMESTRING_RE.search(start)
            if res:
                group = res.groupdict()

                def g(*keys):
                    return next((group.get(k) for k in keys
                                 if group.get(k) is not None),
                                None)

                if verbose:
                    print(dict(map(lambda a: (a, group.get(a)), filter(lambda a: group.get(a), group))))

                if not group['this']:
                    if group['since']:
                        context = Context.PREV
                    if group['until'] or group['by']:
                        context = Context.NEXT

                delta = group.get('delta') or group.get('delta_2')
                if delta:
                    delta = delta.lower().strip()
                    num = group['num']
                    start = Date("now", offset=offset, tz=tz)
                    end = None

                    # ago                               [     ](     )x
                    # from now                         x(     )[     ]
                    # in                               x(     )[     ]
                    if group['ago'] or group['from_now'] or group['in']:
                        n = get_num(num or 1)
                        whole = int(n)
                        fraction = n - whole
                        if verbose:
                            print('ago or from_now or in')
                        start = Date(res.string)
                        if not re.match('(hour|minute|second)s?', delta):
                            if not fraction:
                                start = start.replace(hour=0, minute=0, second=0, microsecond=0)
                            end = start.plus_(1, 'day')
                        elif delta.startswith('hour'):
                            if not fraction:
                                start = start.replace(minute=0, second=0, microsecond=0)
                            end = start + '1 hour'
                        elif delta.startswith('minute'):
                            if not fraction:
                                start = start.replace(second=0, microsecond=0)
                            end = start + '1 minute'
                        else:
                            end = start + '1 second'

                    # "next 2 weeks", "the next hour"   x[     ][     ]
                    elif group['next'] and (group['num'] or group['article']):
                        if verbose:
                            print('next and (num or article)')
                        end = start.plus_(num, delta)

                    # "next week"                       (  x  )[      ]
                    elif group['next'] or (not group['this'] and context == Context.NEXT):
                        if verbose:
                            print('next or (not this and Context.NEXT)')
                        this = Range('this ' + delta,
                                     offset=offset,
                                     tz=tz,
                                     week_start=week_start)
                        if delta.startswith('weekend'):
                            if 'now' in this:
                                start, end = this.plus_(num, delta)
                            else:
                                start, end = this
                        else:
                            start = this.end
                            end = start.plus_(num, delta)

                    # "last 2 weeks", "the last hour"   [     ][     ]x
                    elif group['prev'] and (group['num'] or group['article']):
                        if verbose:
                            print('prev and (num or article)')

                        end = start.plus_(num, delta, -1)

                    # "last week"                       [     ](  x  )
                    elif group['prev']:
                        if verbose:
                            print('prev')
                        this = Range('this ' + delta,
                                     offset=offset,
                                     tz=tz,
                                     week_start=week_start)

                        start = this.start.plus_(num, delta, -1)
                        end = this.end.plus_(num, delta, -1)

                    # "1 year", "10 days" till now
                    elif num:
                        if verbose:
                            print('num')

                        end = start.plus_(num, delta, -1)

                    # this                             [   x  ]
                    elif group['this'] or not group['recurrence']:
                        if verbose:
                            print('this or not recurrence')
                        start = Date(res.string, tz=tz)

                        if delta.startswith('y'):
                            start = start.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)

                        elif delta.startswith('mo'):
                            start = start.replace(day=1, hour=0, minute=0, second=0, microsecond=0)

                        # weekend
                        elif delta.startswith('weekend'):
                            days = (WEEKEND_START_DAY - start.isoweekday + 7) % 7
                            start = Date(now + timedelta(days=days))
                            start = start.replace(hour=WEEKEND_START_HOUR,
                                                  minute=0,
                                                  second=0,
                                                  microsecond=0)
                            days = (WEEKEND_END_DAY + 7 - WEEKEND_START_DAY) % 7
                            end = Date(start.date + timedelta(days=days))
                            end = end.replace(hour=WEEKEND_END_HOUR)
                            start = start.replace(**offset or {})
                            end = end.replace(**offset or {})

                        # week
                        elif delta.startswith('w'):
                            start.date -= timedelta(days=start.isoweekday - week_start % 7)
                            start = start.replace(hour=0, minute=0, second=0, microsecond=0)

                        elif delta.startswith('d'):
                            start = start.replace(hour=0, minute=0, second=0, microsecond=0)

                        elif delta.startswith('h'):
                            start = start.replace(minute=0, second=0, microsecond=0)

                        elif delta.startswith('m'):
                            start = start.replace(second=0, microsecond=0)

                        elif delta.startswith('s'):
                            start = start.replace(microsecond=0)

                        else:
                            raise TimestringInvalid("Not a valid time reference")

                        if offset:
                            start = start.replace(**offset)
                        if not end:
                            end = start.plus_(num, delta)

                elif group['relative_day'] or group['weekday']:
                    if verbose:
                        print('relative_day or weekday')
                    start = Date(res.string, offset=offset, tz=tz, context=context)
                    end = start + '1 day'

                elif group.get('month_1'):
                    if verbose:
                        print('month_1')
                    start = Date(res.string, offset=offset, tz=tz, context=context)
                    start = start.replace(hour=0, minute=0, second=0)
                    end = start + '1 month'

                elif group['date_5'] or group['date_6']:
                    if verbose:
                        print('date_5 or date_6')
                    start = Date(res.string, offset=offset, tz=tz)
                    year = g('year', 'year_2', 'year_3', 'year_4', 'year_5', 'year_6')
                    month = g('month', 'month_2', 'month_3', 'month_4', 'month_5')
                    day = g('date', 'date_2', 'date_3', 'date_4')

                    if day:
                        end = start + '1 day'
                    elif month:
                        end = start + '1 month'
                    elif year is not None:
                        end = start + '1 year'
                    else:
                        end = start

                if not isinstance(start, Date):
                    start = Date(now)

                if group['time_2']:
                    if verbose:
                        print('time_2')
                    temp = Date(res.string, offset=offset, now=start, tz=tz).date
                    start = start.replace(hour=temp.hour,
                                          minute=temp.minute,
                                          second=temp.second)

                    hour = g('hour', 'hour_2', 'hour_3')
                    minute = g('minute', 'minute_2')
                    second = g('seconds')

                    if second:
                        end = start + '1 second'
                    elif minute:
                        end = start + '1 minute'
                    elif hour:
                        end = start + '1 hour'
                    else:
                        end = start

                if group['since']:
                    end = now
                elif group['until'] or group['by']:
                    end = start
                    start = now

                if start <= now <= end:
                    if context == Context.PAST:
                        end = now
                    elif context == Context.FUTURE:
                        start = now

            else:
                raise TimestringInvalid('Invalid range: %s' % start)

            if end is None:
                # no end provided, so assume 24 hours
                if isinstance(start, str):
                    start = Date(start)
                end = start + '24 hours'

            if start > end:
                start, end = copy(end), copy(start)

            if pgoffset:
                start = start - pgoffset
                if end != 'infinity':
                    end = end - pgoffset

            self._dates = (start, end)

        if self._dates[0] > self._dates[1]:
            self._dates = (self._dates[0], self._dates[1] + '1 day')