コード例 #1
0
ファイル: utils.py プロジェクト: mkobel/khal
def rrulefstr(repeat, until, locale):
    if repeat in ["daily", "weekly", "monthly", "yearly"]:
        rrule_settings = {'freq': repeat}
        if until:
            until_date = None
            for fun, dformat in [(datetimefstr, locale['datetimeformat']),
                                 (datetimefstr, locale['longdatetimeformat']),
                                 (timefstr, locale['timeformat']),
                                 (datetimefstr, locale['dateformat']),
                                 (datetimefstr, locale['longdateformat'])]:
                try:
                    until_date = fun(until.split(' '), dformat)
                    break
                except ValueError:
                    pass
            if until_date is None:
                logger.fatal("Cannot parse until date: '{}'\nPlease have a look "
                             "at the documentation.".format(until))
                raise FatalError()
            rrule_settings['until'] = until_date

        return rrule_settings
    else:
        logger.fatal("Invalid value for the repeat option. \
                Possible values are: daily, weekly, monthly or yearly")
        raise FatalError()
コード例 #2
0
def new_from_args(collection, calendar_name, conf, dtstart=None, dtend=None,
                  summary=None, description=None, allday=None, location=None,
                  categories=None, repeat=None, until=None, alarms=None,
                  timezone=None, format=None, env=None):
    """Create a new event from arguments and add to vdirs"""
    try:
        event = utils.new_event(
            locale=conf['locale'], location=location, categories=categories,
            repeat=repeat, until=until, alarms=alarms, dtstart=dtstart,
            dtend=dtend, summary=summary, description=description, timezone=timezone,
        )
    except ValueError as error:
        raise FatalError(error)
    event = Event.fromVEvents(
        [event], calendar=calendar_name, locale=conf['locale'])

    try:
        collection.new(event)
    except ReadOnlyCalendarError:
        raise FatalError(
            'ERROR: Cannot modify calendar "{}" as it is read-only'.format(calendar_name)
        )

    if conf['default']['print_new'] == 'event':
        if format is None:
            format = conf['view']['event_format']
        echo(event.format(format, dt.datetime.now(), env=env))
    elif conf['default']['print_new'] == 'path':
        path = os.path.join(
            collection._calendars[event.calendar]['path'],
            event.href
        )
        echo(path)
    return event
コード例 #3
0
ファイル: controllers.py プロジェクト: sdx23/khal
def calendar(collection, agenda_format=None, notstarted=False, once=False, daterange=None,
             day_format=None,
             locale=None,
             conf=None,
             firstweekday=0,
             weeknumber=False,
             monthdisplay='firstday',
             hmethod='fg',
             default_color='',
             multiple='',
             color='',
             highlight_event_days=0,
             full=False,
             bold_for_light_color=True,
             env=None,
             ):
    term_width, _ = get_terminal_size()
    lwidth = 27 if conf['locale']['weeknumbers'] == 'right' else 25
    rwidth = term_width - lwidth - 4

    try:
        start, end = start_end_from_daterange(
            daterange, locale,
            default_timedelta_date=conf['default']['timedelta'],
            default_timedelta_datetime=conf['default']['timedelta'],
        )
    except ValueError as error:
        raise FatalError(error)

    event_column = khal_list(
        collection,
        daterange,
        conf=conf,
        agenda_format=agenda_format,
        day_format=day_format,
        once=once,
        notstarted=notstarted,
        width=rwidth,
        env=env,
    )
    calendar_column = calendar_display.vertical_month(
        month=start.month,
        year=start.year,
        count=max(3, (end.year - start.year) * 12 + end.month - start.month + 1),
        firstweekday=firstweekday, weeknumber=weeknumber,
        monthdisplay=monthdisplay,
        collection=collection,
        hmethod=hmethod,
        default_color=default_color,
        multiple=multiple,
        color=color,
        highlight_event_days=highlight_event_days,
        locale=locale,
        bold_for_light_color=bold_for_light_color)
    return merge_columns(calendar_column, event_column, width=lwidth)
コード例 #4
0
def rrulefstr(repeat, until, locale):
    if repeat in ["daily", "weekly", "monthly", "yearly"]:
        rrule_settings = {'freq': repeat}
        if until:
            until_dt, is_date = guessdatetimefstr(until.split(' '), locale)
            rrule_settings['until'] = until_dt
        return rrule_settings
    else:
        logger.fatal("Invalid value for the repeat option. \
                Possible values are: daily, weekly, monthly or yearly")
        raise FatalError()
コード例 #5
0
ファイル: controllers.py プロジェクト: GeoffreyFrogeye/khal
def khal_list(collection,
              daterange=None,
              conf=None,
              agenda_format=None,
              day_format=None,
              once=False,
              notstarted=False,
              width=False,
              env=None,
              datepoint=None):
    assert daterange is not None or datepoint is not None
    """returns a list of all events in `daterange`"""
    # because empty strings are also Falsish
    if agenda_format is None:
        agenda_format = conf['view']['agenda_event_format']

    if daterange is not None:
        if day_format is None:
            day_format = conf['view']['agenda_day_format']
        start, end = start_end_from_daterange(
            daterange,
            conf['locale'],
            default_timedelta_date=conf['default']['timedelta'],
            default_timedelta_datetime=conf['default']['timedelta'],
        )
        logger.debug('Getting all events between {} and {}'.format(start, end))

    elif datepoint is not None:
        if not datepoint:
            datepoint = ['now']
        try:
            start, allday = parse_datetime.guessdatetimefstr(
                datepoint,
                conf['locale'],
                dt.date.today(),
            )
        except ValueError:
            raise FatalError('Invalid value of `{}` for a datetime'.format(
                ' '.join(datepoint)))
        if allday:
            logger.debug('Got date {}'.format(start))
            raise FatalError('Please supply a datetime, not a date.')
        end = start + dt.timedelta(seconds=1)
        if day_format is None:
            day_format = style(
                start.strftime(conf['locale']['longdatetimeformat']),
                bold=True,
            )
        logger.debug('Getting all events between {} and {}'.format(start, end))

    event_column = []
    once = set() if once else None
    if env is None:
        env = {}

    original_start = conf['locale']['local_timezone'].localize(start)
    while start < end:
        if start.date() == end.date():
            day_end = end
        else:
            day_end = dt.datetime.combine(start.date(), dt.time.max)
        current_events = get_events_between(
            collection,
            locale=conf['locale'],
            agenda_format=agenda_format,
            start=start,
            end=day_end,
            notstarted=notstarted,
            original_start=original_start,
            env=env,
            seen=once,
            width=width,
        )
        if day_format and (conf['default']['show_all_days'] or current_events):
            event_column.append(
                format_day(start.date(), day_format, conf['locale']))
        event_column.extend(current_events)
        start = dt.datetime(*start.date().timetuple()[:3]) + dt.timedelta(
            days=1)

    if event_column == []:
        event_column = [style('No events', bold=True)]
    return event_column
コード例 #6
0
ファイル: controllers.py プロジェクト: GeoffreyFrogeye/khal
def get_events_between(collection,
                       locale,
                       start,
                       end,
                       agenda_format=None,
                       notstarted=False,
                       env=None,
                       width=None,
                       seen=None,
                       original_start=None):
    """returns a list of events scheduled between start and end. Start and end
    are strings or datetimes (of some kind).

    :param collection:
    :type collection: khalendar.CalendarCollection
    :param start: the start datetime
    :param end: the end datetime
    :param agenda_format: a format string that can be used in python string formatting
    :type  agenda_format: str
    :param env: a collection of "static" values like calendar names and color
    :type env: dict
    :param nostarted: True if each event should start after start (instead of
    be active between start and end)
    :type nostarted: bool
    :param original_start: start datetime to compare against of notstarted is set
    :type original_start: datetime.datetime
    :returns: a list to be printed as the agenda for the given days
    :rtype: list(str)
    """
    assert not (notstarted and not original_start)

    event_list = []
    if env is None:
        env = {}
    assert start
    assert end
    start_local = locale['local_timezone'].localize(start)
    end_local = locale['local_timezone'].localize(end)

    start = start_local.replace(tzinfo=None)
    end = end_local.replace(tzinfo=None)

    events = sorted(collection.get_localized(start_local, end_local))
    events_float = sorted(collection.get_floating(start, end))
    events = sorted(events + events_float)
    for event in events:
        # yes the logic could be simplified, but I believe it's easier
        # to understand what's going on here this way
        if notstarted:
            if event.allday and event.start < original_start.date():
                continue
            elif not event.allday and event.start_local < original_start:
                continue
        if seen is not None and event.uid in seen:
            continue

        try:
            event_string = event.format(agenda_format,
                                        relative_to=(start, end),
                                        env=env)
        except KeyError as error:
            raise FatalError(error)

        if width:
            event_list += utils.color_wrap(event_string, width)
        else:
            event_list.append(event_string)
        if seen is not None:
            seen.add(event.uid)

    return event_list
コード例 #7
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))
コード例 #8
0
ファイル: aux.py プロジェクト: bradleyjones/khal
def construct_event(date_list,
                    timeformat,
                    dateformat,
                    longdateformat,
                    datetimeformat,
                    longdatetimeformat,
                    default_timezone,
                    defaulttimelen=60,
                    defaultdatelen=1,
                    encoding='utf-8',
                    description=None,
                    location=None,
                    repeat=None,
                    _now=datetime.now,
                    **kwargs):
    """takes a list of strings and constructs a vevent from it

    :param encoding: the encoding of your terminal, should be a valid encoding
    :type encoding: str
    :param _now: function that returns now, used for testing

    the parts of the list can be either of these:
        * datetime datetime description
            start and end datetime specified, if no year is given, this year
            is used, if the second datetime has no year, the same year as for
            the first datetime object will be used, unless that would make
            the event end before it begins, in which case the next year is
            used
        * datetime time description
            end date will be same as start date, unless that would make the
            event end before it has started, then the next day is used as
            end date
        * datetime description
            event will last for defaulttime
        * time time description
            event starting today at the first time and ending today at the
            second time, unless that would make the event end before it has
            started, then the next day is used as end date
        * time description
            event starting today at time, lasting for the default length
        * date date description
            all day event starting on the first and ending on the last event
        * date description
            all day event starting at given date and lasting for default length

    datetime should match datetimeformat or longdatetimeformat
    time should match timeformat

    where description is the unused part of the list
    see tests for examples

    """
    today = datetime.today()

    all_day = False

    # looking for start datetime
    try:
        # first two elements are a date and a time
        dtstart = datetimefstr(date_list, datetimeformat, longdatetimeformat)
    except ValueError:
        try:
            # first element is a time
            dtstart = timefstr(date_list, timeformat)
        except ValueError:
            try:
                # first element is a date (and since second isn't a time this
                # is an all-day-event
                dtstart = datetimefstr(date_list, dateformat, longdateformat)
                all_day = True
            except ValueError:
                logger.fatal("Cannot parse: '{}'\nPlease have a look at "
                             "the documentation.".format(' '.join(date_list)))
                raise FatalError()

    # now looking for the end
    if all_day:
        try:
            # second element must be a date, too
            dtend = datetimefstr(date_list, dateformat, longdateformat)
            dtend = dtend + timedelta(days=1)
        except ValueError:
            # if it isn't we expect it to be the summary and use defaultdatelen
            # as event length
            dtend = dtstart + timedelta(days=defaultdatelen)
        # test if dtend's year is this year, but dtstart's year is not
        if dtend.year == today.year and dtstart.year != today.year:
            dtend = datetime(dtstart.year, *dtend.timetuple()[1:6])

        if dtend < dtstart:
            dtend = datetime(dtend.year + 1, *dtend.timetuple()[1:6])

    else:
        try:
            # next element datetime
            dtend = datetimefstr(date_list, datetimeformat, longdatetimeformat,
                                 dtstart.year)
        except ValueError:
            try:
                # next element time only
                dtend = timefstr(date_list, timeformat)
                dtend = datetime(*(dtstart.timetuple()[:3] +
                                   dtend.timetuple()[3:5]))
            except ValueError:
                dtend = dtstart + timedelta(minutes=defaulttimelen)

    if dtend < dtstart:
        dtend = datetime(*dtstart.timetuple()[0:3] + dtend.timetuple()[3:5])
    if dtend < dtstart:
        dtend = dtend + timedelta(days=1)
    if all_day:
        dtstart = dtstart.date()
        dtend = dtend.date()

    else:
        try:
            # next element is a valid Olson db timezone string
            dtstart = pytz.timezone(date_list[0]).localize(dtstart)
            dtend = pytz.timezone(date_list[0]).localize(dtend)
            date_list.pop(0)
        except (pytz.UnknownTimeZoneError, UnicodeDecodeError):
            dtstart = default_timezone.localize(dtstart)
            dtend = default_timezone.localize(dtend)

    event = icalendar.Event()
    text = to_unicode(' '.join(date_list), encoding)
    if not description or not location:
        summary = text.split(' :: ', 1)[0]
        try:
            description = text.split(' :: ', 1)[1]
        except IndexError:
            pass
    else:
        summary = text

    if description:
        event.add('description', description)
    if location:
        event.add('location', location)
    if repeat:
        if repeat in ["daily", "weekly", "monthly", "yearly"]:
            event.add('rrule', {'freq': repeat})
        else:
            logger.fatal("Invalid value for the repeat option. \
                    Possible values are: daily, weekly, monthly or yearly")
            raise FatalError()

    event.add('dtstart', dtstart)
    event.add('dtend', dtend)
    event.add('dtstamp', _now())
    event.add('summary', summary)
    event.add('uid', generate_random_uid())  # TODO add proper UID
    return event
コード例 #9
0
def guessrangefstr(
        daterange,
        locale,
        adjust_reasonably=False,
        default_timedelta_date=timedelta(days=1),
        default_timedelta_datetime=timedelta(hours=1),
):
    """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 = datetime.today().weekday()
        start = datetime.today() - timedelta(days=(today_weekday -
                                                   locale['firstweekday']))
        end = start + 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
            if len(start) == 0:
                raise  # used to be: start = datetime_fillin(end=False)
            else:
                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 = datetime.combine(start.date(), time.max)
            elif end.lower() == 'week':
                start -= timedelta(days=(start.weekday() -
                                         locale['firstweekday']))
                end = start + 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:
                    split = end.split(" ")
                    end, end_allday = guessdatetimefstr(
                        split, locale, default_day=start.date())
                    if len(split) != 0:
                        continue
                    if allday:
                        end += timedelta(days=1)

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

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

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

    raise ValueError('Could not parse `{}` as a daterange'.format(daterange))
コード例 #10
0
ファイル: controllers.py プロジェクト: PsychicNoodles/khal
def new_from_args(collection,
                  calendar_name,
                  conf,
                  dtstart=None,
                  dtend=None,
                  summary=None,
                  description=None,
                  allday=None,
                  location=None,
                  categories=None,
                  repeat=None,
                  until=None,
                  alarms=None,
                  timezone=None,
                  url=None,
                  format=None,
                  json=None,
                  env=None):
    """Create a new event from arguments and add to vdirs"""
    if isinstance(categories, str):
        categories = [category.strip() for category in categories.split(',')]
    try:
        event = new_vevent(
            locale=conf['locale'],
            location=location,
            categories=categories,
            repeat=repeat,
            until=until,
            alarms=alarms,
            dtstart=dtstart,
            dtend=dtend,
            summary=summary,
            description=description,
            timezone=timezone,
            url=url,
        )
    except ValueError as error:
        raise FatalError(error)
    event = Event.fromVEvents([event],
                              calendar=calendar_name,
                              locale=conf['locale'])

    try:
        collection.new(event)
    except ReadOnlyCalendarError:
        raise FatalError(
            f'ERROR: Cannot modify calendar "{calendar_name}" as it is read-only'
        )

    if conf['default']['print_new'] == 'event':
        if json is None or len(json) == 0:
            if format is None:
                format = conf['view']['event_format']
            formatter = human_formatter(format)
        else:
            formatter = json_formatter(json)
        echo(formatter(event.attributes(dt.datetime.now(), env=env)))
    elif conf['default']['print_new'] == 'path':
        path = os.path.join(collection._calendars[event.calendar]['path'],
                            event.href)
        echo(path)
    return event