From 026d512215c02d282653191cfc5c5e9d8e982334 Mon Sep 17 00:00:00 2001 From: Thomas Schwinge Date: Thu, 5 Dec 2019 09:52:42 +0100 Subject: [PATCH 1/3] CreateEventFromVOBJ: if there is no dtend, use the duration if available --- gcalcli/ics.py | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/gcalcli/ics.py b/gcalcli/ics.py index 3da9f23..e86c19f 100644 --- a/gcalcli/ics.py +++ b/gcalcli/ics.py @@ -79,20 +79,10 @@ def CreateEventFromVOBJ( print('Location.....%s' % ve.location.value) event['location'] = ve.location.value - if not hasattr(ve, 'dtstart') or not hasattr(ve, 'dtend'): - printer.err_msg('Error: event does not have a dtstart and dtend!\n') + if not hasattr(ve, 'dtstart'): + printer.err_msg('Error: event does not have a dtstart!\n') return EventData(body=None, source=ve) - if verbose: - if ve.dtstart.value: - print('Start........%s' % ve.dtstart.value.isoformat()) - if ve.dtend.value: - print('End..........%s' % ve.dtend.value.isoformat()) - if ve.dtstart.value: - print('Local Start..%s' % localize_datetime(ve.dtstart.value)) - if ve.dtend.value: - print('Local End....%s' % localize_datetime(ve.dtend.value)) - if hasattr(ve, 'rrule'): if verbose: print('Recurrence...%s' % ve.rrule.value) @@ -100,6 +90,10 @@ def CreateEventFromVOBJ( event['recurrence'] = ['RRULE:' + ve.rrule.value] if hasattr(ve, 'dtstart') and ve.dtstart.value: + if verbose: + print('Start........%s' % ve.dtstart.value.isoformat()) + print('Local Start..%s' % localize_datetime(ve.dtstart.value)) + # XXX # Timezone madness! Note that we're using the timezone for the # calendar being added to. This is OK if the event is in the @@ -123,15 +117,35 @@ def CreateEventFromVOBJ( # NOTE: Reminders added by GoogleCalendarInterface caller. - # Can only have an end if we have a start, but not the other - # way around apparently... If there is no end, use the start + # Can only have an end if we have a start, but not the other way around + # apparently... If there is no end, use the duration if available, or + # the start otherwise. if hasattr(ve, 'dtend') and ve.dtend.value: + if verbose: + print('End..........%s' % ve.dtend.value.isoformat()) + print('Local End....%s' % localize_datetime(ve.dtend.value)) + end = ve.dtend.value.isoformat() if isinstance(ve.dtend.value, datetime): event['end'] = {'dateTime': end, 'timeZone': default_tz} else: event['end'] = {'date': end} + elif hasattr(ve, 'duration') and ve.duration.value: + if verbose: + print('Duration.....%s' % ve.duration.value) + end = ve.dtstart.value + ve.duration.value + if verbose: + print('Calculated End........%s' % end.isoformat()) + print('Calculated Local End..%s' % localize_datetime(end)) + # Decide based on dtstart; that's what we base our calculation on. + # TODO: correct? + if isinstance(ve.dtstart.value, datetime): + event['end'] = {'dateTime': end.isoformat(), + 'timeZone': default_tz} + else: + event['end'] = {'date': end.isoformat()} + else: event['end'] = event['start'] From 2e35e2f90c21e5f63c21cd060662390b53846a2d Mon Sep 17 00:00:00 2001 From: David Barnett Date: Wed, 25 Sep 2024 11:27:53 -0600 Subject: [PATCH 2/3] cleanup: simplify some ics.py code related to dtend cases --- gcalcli/ics.py | 103 ++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 57 deletions(-) diff --git a/gcalcli/ics.py b/gcalcli/ics.py index e86c19f..c1f0649 100644 --- a/gcalcli/ics.py +++ b/gcalcli/ics.py @@ -3,7 +3,7 @@ from dataclasses import dataclass import importlib.util import io -from datetime import datetime +from datetime import datetime, timedelta import pathlib import tempfile from typing import Any, NamedTuple, Optional @@ -79,7 +79,7 @@ def CreateEventFromVOBJ( print('Location.....%s' % ve.location.value) event['location'] = ve.location.value - if not hasattr(ve, 'dtstart'): + if not hasattr(ve, 'dtstart') or not ve.dtstart.value: printer.err_msg('Error: event does not have a dtstart!\n') return EventData(body=None, source=ve) @@ -89,65 +89,54 @@ def CreateEventFromVOBJ( event['recurrence'] = ['RRULE:' + ve.rrule.value] - if hasattr(ve, 'dtstart') and ve.dtstart.value: + if verbose: + print('Start........%s' % ve.dtstart.value.isoformat()) + print('Local Start..%s' % localize_datetime(ve.dtstart.value)) + + # XXX + # Timezone madness! Note that we're using the timezone for the calendar + # being added to. This is OK if the event is in the same timezone. This + # needs to be changed to use the timezone from the DTSTART and DTEND values. + # Problem is, for example, the TZID might be "Pacific Standard Time" and + # Google expects a timezone string like "America/Los_Angeles". Need to find + # a way in python to convert to the more specific timezone string. + # XXX + # print ve.dtstart.params['X-VOBJ-ORIGINAL-TZID'][0] + # print default_tz + # print dir(ve.dtstart.value.tzinfo) + # print vars(ve.dtstart.value.tzinfo) + + start = ve.dtstart.value.isoformat() + if isinstance(ve.dtstart.value, datetime): + event['start'] = {'dateTime': start, 'timeZone': default_tz} + else: + event['start'] = {'date': start} + + # All events must have a start, but explicit end is optional. + # If there is no end, use the duration if available, or the start otherwise. + if hasattr(ve, 'dtend') and ve.dtend.value: + end = ve.dtend.value if verbose: - print('Start........%s' % ve.dtstart.value.isoformat()) - print('Local Start..%s' % localize_datetime(ve.dtstart.value)) - - # XXX - # Timezone madness! Note that we're using the timezone for the - # calendar being added to. This is OK if the event is in the - # same timezone. This needs to be changed to use the timezone - # from the DTSTART and DTEND values. Problem is, for example, - # the TZID might be "Pacific Standard Time" and Google expects - # a timezone string like "America/Los_Angeles". Need to find a - # way in python to convert to the more specific timezone - # string. - # XXX - # print ve.dtstart.params['X-VOBJ-ORIGINAL-TZID'][0] - # print default_tz - # print dir(ve.dtstart.value.tzinfo) - # print vars(ve.dtstart.value.tzinfo) - - start = ve.dtstart.value.isoformat() - if isinstance(ve.dtstart.value, datetime): - event['start'] = {'dateTime': start, 'timeZone': default_tz} + print('End..........%s' % end.isoformat()) + print('Local End....%s' % localize_datetime(end)) + else: # using duration instead of end + if hasattr(ve, 'duration') and ve.duration.value: + duration = ve.duration.value else: - event['start'] = {'date': start} - - # NOTE: Reminders added by GoogleCalendarInterface caller. - - # Can only have an end if we have a start, but not the other way around - # apparently... If there is no end, use the duration if available, or - # the start otherwise. - if hasattr(ve, 'dtend') and ve.dtend.value: - if verbose: - print('End..........%s' % ve.dtend.value.isoformat()) - print('Local End....%s' % localize_datetime(ve.dtend.value)) - - end = ve.dtend.value.isoformat() - if isinstance(ve.dtend.value, datetime): - event['end'] = {'dateTime': end, 'timeZone': default_tz} - else: - event['end'] = {'date': end} + duration = timedelta(minutes=0) + if verbose: + print('Duration.....%s' % duration) + end = ve.dtstart.value + duration + if verbose: + print('Calculated End........%s' % end.isoformat()) + print('Calculated Local End..%s' % localize_datetime(end)) - elif hasattr(ve, 'duration') and ve.duration.value: - if verbose: - print('Duration.....%s' % ve.duration.value) - end = ve.dtstart.value + ve.duration.value - if verbose: - print('Calculated End........%s' % end.isoformat()) - print('Calculated Local End..%s' % localize_datetime(end)) - # Decide based on dtstart; that's what we base our calculation on. - # TODO: correct? - if isinstance(ve.dtstart.value, datetime): - event['end'] = {'dateTime': end.isoformat(), - 'timeZone': default_tz} - else: - event['end'] = {'date': end.isoformat()} + if isinstance(end, datetime): + event['end'] = {'dateTime': end.isoformat(), 'timeZone': default_tz} + else: + event['end'] = {'date': end.isoformat()} - else: - event['end'] = event['start'] + # NOTE: Reminders added by GoogleCalendarInterface caller. if hasattr(ve, 'description') and ve.description.value.strip(): descr = ve.description.value.strip() From a4d5a2372bf1f05699e93db42d0db559e8480194 Mon Sep 17 00:00:00 2001 From: David Barnett Date: Wed, 25 Sep 2024 11:27:53 -0600 Subject: [PATCH 3/3] ical: fall back to 30m duration + info message for evs w/o end/duration --- ChangeLog | 1 + gcalcli/ics.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 05a2a7a..f448dd1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,7 @@ v4.5.0 reading an rc file you needed * The `import` command now dumps events it couldn't import into a tmp rej.ics file in a tmp directory for convenient retries + * `import` can also handle events w/o a dtend, using duration if available * Determine date format to use based on system locale's in "When" inputs * Respect locally-installed certificates (ajkessel) * Re-add a `--noauth_local_server` to provide instructions for authenticating diff --git a/gcalcli/ics.py b/gcalcli/ics.py index c1f0649..d7d7924 100644 --- a/gcalcli/ics.py +++ b/gcalcli/ics.py @@ -123,7 +123,11 @@ def CreateEventFromVOBJ( if hasattr(ve, 'duration') and ve.duration.value: duration = ve.duration.value else: - duration = timedelta(minutes=0) + printer.msg( + "Falling back to 30m duration for imported event w/o " + "explicit duration or end.\n" + ) + duration = timedelta(minutes=30) if verbose: print('Duration.....%s' % duration) end = ve.dtstart.value + duration