Esempio n. 1
0
def guessdatetimefstr(dtime_list, locale, default_day=None, in_future=True):
    """
    :type dtime_list: list
    :type locale: dict
    :type default_day: datetime.datetime
    :param in_future: if set, shortdate(time) events will be set in the future
    :type in_future: bool
    :rtype: datetime.datetime
    """
    # if now() is called as default param, mocking with freezegun won't work
    if default_day is None:
        default_day = dt.datetime.now().date()
    # TODO rename in guessdatetimefstrLIST or something saner altogether

    def timefstr_day(dtime_list, timeformat, **kwargs):
        if locale['timeformat'] == '%H:%M' and dtime_list[0] == '24:00':
            a_date = dt.datetime.combine(default_day, dt.time(0))
            dtime_list.pop(0)
        else:
            a_date = timefstr(dtime_list, timeformat)
            a_date = dt.datetime(*(default_day.timetuple()[:3] +
                                   a_date.timetuple()[3:5]))
        return a_date

    def datetimefwords(dtime_list, _, **kwargs):
        if len(dtime_list) > 0 and dtime_list[0].lower() == 'now':
            dtime_list.pop(0)
            return dt.datetime.now()
        raise ValueError

    def datefstr_year(dtime_list, dtformat, infer_year):
        return datetimefstr(dtime_list, dtformat, default_day, infer_year,
                            in_future)

    dtstart = None
    for fun, dtformat, all_day, infer_year in [
        (datefstr_year, locale['datetimeformat'], False, True),
        (datefstr_year, locale['longdatetimeformat'], False, False),
        (timefstr_day, locale['timeformat'], False, False),
        (datetimefstr_weekday, locale['timeformat'], False, False),
        (datefstr_year, locale['dateformat'], True, True),
        (datefstr_year, locale['longdateformat'], True, False),
        (datefstr_weekday, None, True, False),
        (datetimefwords, None, False, False),
    ]:
        # if a `short` format contains a year, treat it as a `long` format
        if infer_year and '97' in dt.datetime(1997, 10, 11).strftime(dtformat):
            infer_year = False
        try:
            dtstart = fun(dtime_list, dtformat, infer_year=infer_year)
        except (ValueError, DateTimeParseError):
            pass
        else:
            return dtstart, all_day
    raise DateTimeParseError(
        "Could not parse \"{}\".\nPlease check your configuration or run "
        "`khal printformats` to see if this does match your configured "
        "[long](date|time|datetime)format.\nIf you suspect a bug, please "
        "file an issue at https://github.com/pimutils/khal/issues/ "
        "".format(dtime_list))
Esempio n. 2
0
def guesstimedeltafstr(delta_string):
    """parses a timedelta from a string

    :param delta_string: string encoding time-delta, e.g. '1h 15m'
    :type delta_string: str
    :rtype: datetime.timedelta
    """

    tups = re.split(r'(-?\d+)', delta_string)
    if not re.match(r'^\s*$', tups[0]):
        raise ValueError('Invalid beginning of timedelta string "%s": "%s"' %
                         (delta_string, tups[0]))
    tups = tups[1:]
    res = dt.timedelta()

    for num, unit in zip(tups[0::2], tups[1::2]):
        try:
            numint = int(num)
        except ValueError:
            raise DateTimeParseError(
                'Invalid number in timedelta string "%s": "%s"' %
                (delta_string, num))

        ulower = unit.lower().strip()
        if ulower == 'd' or ulower == 'day' or ulower == 'days':
            res += dt.timedelta(days=numint)
        elif ulower == 'h' or ulower == 'hour' or ulower == 'hours':
            res += dt.timedelta(hours=numint)
        elif (ulower == 'm' or ulower == 'minute' or ulower == 'minutes'
              or ulower == 'min'):
            res += dt.timedelta(minutes=numint)
        elif (ulower == 's' or ulower == 'second' or ulower == 'seconds'
              or ulower == 'sec'):
            res += dt.timedelta(seconds=numint)
        else:
            raise ValueError('Invalid unit in timedelta string "%s": "%s"' %
                             (delta_string, unit))

    return res
Esempio n. 3
0
def eventinfofstr(info_string,
                  locale,
                  default_event_duration,
                  default_dayevent_duration,
                  adjust_reasonably=False,
                  localize=False):
    """parses a string of the form START [END | DELTA] [TIMEZONE] [SUMMARY] [::
    DESCRIPTION] into a dictionary with keys: dtstart, dtend, timezone, allday,
    summary, description

    :param info_string:
    :type info_string: string fitting the form
    :param locale:
    :type locale: locale
    :param adjust_reasonably:
    :type adjust_reasonably: passed on to guessrangefstr
    :rtype: dictionary

    """
    description = None
    if " :: " in info_string:
        info_string, description = info_string.split(' :: ')

    parts = info_string.split(' ')
    summary = None
    start = None
    end = None
    tz = None
    allday = False
    for i in reversed(range(1, len(parts) + 1)):
        try:
            start, end, allday = guessrangefstr(
                ' '.join(parts[0:i]),
                locale,
                default_event_duration,
                default_dayevent_duration,
                adjust_reasonably=adjust_reasonably,
            )
        except (ValueError, DateTimeParseError):
            continue
        if start is not None and end is not None:
            try:
                # next element is a valid Olson db timezone string
                tz = pytz.timezone(parts[i])
                i += 1
            except (pytz.UnknownTimeZoneError, UnicodeDecodeError, IndexError):
                tz = None
            summary = ' '.join(parts[i:])
            break

    if start is None or end is None:
        raise DateTimeParseError(
            "Could not parse \"{}\".\nPlease check your configuration or run "
            "`khal printformats` to see if this does match your configured "
            "[long](date|time|datetime)format.\nIf you suspect a bug, please "
            "file an issue at https://github.com/pimutils/khal/issues/ "
            "".format(info_string))

    if tz is None:
        tz = locale['default_timezone']

    if allday:
        start = start.date()
        end = end.date()

    info = {}
    info["dtstart"] = start
    info["dtend"] = end
    info["summary"] = summary if summary else None
    info["description"] = description
    info["timezone"] = tz if not allday else None
    info["allday"] = allday
    return info
Esempio n. 4
0
def guessrangefstr(
    daterange,
    locale,
    default_timedelta_date=dt.timedelta(days=1),
    default_timedelta_datetime=dt.timedelta(hours=1),
    adjust_reasonably=False,
):
    """parses a range string

    :param daterange: date1 [date2 | timedelta]
    :type daterange: str or list
    :param locale:
    :returns: start and end of the date(time) range  and if
        this is an all-day time range or not,
        **NOTE**: the end is *exclusive* if this is an allday event
    :rtype: (datetime, datetime, bool)

    """
    range_list = daterange
    if isinstance(daterange, str):
        range_list = daterange.split(' ')

    if range_list == ['week']:
        today_weekday = dt.datetime.today().weekday()
        start = dt.datetime.today() - dt.timedelta(
            days=(today_weekday - locale['firstweekday']))
        end = start + dt.timedelta(days=8)
        return start, end, True

    for i in reversed(range(1, len(range_list) + 1)):
        start = ' '.join(range_list[:i])
        end = ' '.join(range_list[i:])
        allday = False
        try:
            # figuring out start
            split = start.split(" ")
            start, allday = guessdatetimefstr(split, locale)
            if len(split) != 0:
                continue

            # and end
            if len(end) == 0:
                if allday:
                    end = start + default_timedelta_date
                else:
                    end = start + default_timedelta_datetime
            elif end.lower() == 'eod':
                end = dt.datetime.combine(start.date(), dt.time.max)
            elif end.lower() == 'week':
                start -= dt.timedelta(days=(start.weekday() -
                                            locale['firstweekday']))
                end = start + dt.timedelta(days=8)
            else:
                try:
                    delta = guesstimedeltafstr(end)
                    if allday and delta.total_seconds() % (3600 * 24):
                        # TODO better error class, no logging in here
                        logger.fatal(
                            "Cannot give delta containing anything but whole days for allday events"
                        )
                        raise FatalError()
                    elif delta.total_seconds() == 0:
                        logger.fatal(
                            "Events that last no time are not allowed")
                        raise FatalError()

                    end = start + delta
                except (ValueError, DateTimeParseError):
                    split = end.split(" ")
                    end, end_allday = guessdatetimefstr(
                        split,
                        locale,
                        default_day=start.date(),
                        in_future=False)
                    if len(split) != 0:
                        continue
                    if allday:
                        end += dt.timedelta(days=1)

            if adjust_reasonably:
                if allday:
                    # test if end's year is this year, but start's year is not
                    today = dt.datetime.today()
                    if end.year == today.year and start.year != today.year:
                        end = dt.datetime(start.year, *end.timetuple()[1:6])

                    if end < start:
                        end = dt.datetime(end.year + 1, *end.timetuple()[1:6])

                if end < start:
                    end = dt.datetime(*start.timetuple()[0:3] +
                                      end.timetuple()[3:5])
                if end < start:
                    end = end + dt.timedelta(days=1)
            return start, end, allday
        except (ValueError, DateTimeParseError):
            pass

    raise DateTimeParseError(
        "Could not parse \"{}\".\nPlease check your configuration or run "
        "`khal printformats` to see if this does match your configured "
        "[long](date|time|datetime)format.\nIf you suspect a bug, please "
        "file an issue at https://github.com/pimutils/khal/issues/ "
        "".format(daterange))