From 64892380606729b022daeadd5d7b82b4795964a3 Mon Sep 17 00:00:00 2001 From: Tobias Brox Date: Sat, 27 Nov 2021 11:14:49 +0100 Subject: timezones are difficult in python - tried to get rid of pytz dependency, but ended up filing https://github.com/collective/icalendar/issues/333 https://github.com/collective/icalendar/issues/335 https://github.com/collective/icalendar/issues/336 instead --- calendar-cli.py | 55 +++++++++++++++++++++++++++++++++++++++---------------- setup.py | 2 +- tests/tests.sh | 2 ++ 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/calendar-cli.py b/calendar-cli.py index 198bb83..5bd0990 100755 --- a/calendar-cli.py +++ b/calendar-cli.py @@ -3,14 +3,19 @@ """ https://github.com/tobixen/calendar-cli/ - high-level cli against caldav servers. -Copyright (C) 2013-2020 Tobias Brox and other contributors. +Copyright (C) 2013-2021 Tobias Brox and other contributors. See https://www.gnu.org/licenses/gpl-3.0.en.html for license information. """ import argparse -import pytz import tzlocal +## we still need to use pytz, see https://github.com/collective/icalendar/issues/333 +#try: +# import zoneinfo +#except: +# from backports import zoneinfo +import pytz import time from datetime import datetime, timedelta, date from datetime import time as time_ @@ -29,6 +34,9 @@ import urllib3 from getpass import getpass from six import PY3 +UTC = pytz.utc +#UTC = zoneinfo.ZoneInfo('UTC') + def to_normal_str(text): if PY3 and text and not isinstance(text, str): text = text.decode('utf-8') @@ -48,7 +56,7 @@ try: except NameError: unicode = str -__version__ = "0.12.1" +__version__ = "0.13.0" __author__ = "Tobias Brox" __author_short__ = "tobixen" __copyright__ = "Copyright 2013-2021, Tobias Brox and contributors" @@ -92,14 +100,35 @@ def _now(): """ python datetime is ... crap! """ - return datetime.utcnow().replace(tzinfo=pytz.utc) + return datetime.utcnow().replace(tzinfo=UTC) def _tz(timezone=None): - if timezone: + if timezone is None: + try: + ## should not be needed - but see + ## https://github.com/collective/icalendar/issues/333 + return pytz.timezone(tzlocal.get_localzone_name()) + except: + ## if the tzlocal version is old + return tzlocal.get_localzone() + + elif not hasattr(timezone, 'utcoffset'): + ## See https://github.com/collective/icalendar/issues/333 + #return zoneinfo.ZoneInfo(timezone) return pytz.timezone(timezone) else: - return tzlocal.get_localzone() + return timezone +def _localize(ts, timezone=None): + """ + assume a naive timestamp should be with the given timezone, + convert a timestamp with timezone to the given timezone + """ + tz = _tz(timezone) + if ts.tzinfo is None: + return ts.replace(tzinfo=tz) + else: + return ts.astimezone(tz) ## global constant ## (todo: this doesn't really work out that well, leap seconds/days are not considered, and we're missing the month unit) @@ -363,7 +392,7 @@ def calendar_add(caldav_conn, args): event.add('dtstart', _date(dtstart.date())) event.add('dtend', _date(dtend.date())) else: - dtstart = _tz(args.timezone).localize(dtstart) + dtstart = dtstart.replace(tzinfo=_tz(args.timezone)) event.add('dtstart', dtstart) ## TODO: handle duration and end-time as options. default 3600s by now. event.add('dtend', dtstart + timedelta(0,event_duration_secs)) @@ -480,12 +509,12 @@ def calendar_agenda(caldav_conn, args): if args.from_time: search_dtstart = dateutil.parser.parse(args.from_time) - search_dtstart = _tz(args.timezone).localize(search_dtstart) + search_dtstart = _localize(search_dtstart, args.timezone) else: search_dtstart = _now() if args.to_time: search_dtend = dateutil.parser.parse(args.to_time) - search_dtend = _tz(args.timezone).localize(search_dtend) + search_dtend = _localize(search_dtend, args.timezone) elif args.agenda_mins: search_dtend = search_dtstart + timedelta(minutes=args.agenda_mins) elif args.agenda_days: @@ -512,13 +541,7 @@ def calendar_agenda(caldav_conn, args): dtstart = event.dtstart.value if hasattr(event, 'dtstart') else _now() if not isinstance(dtstart, datetime): dtstart = datetime(dtstart.year, dtstart.month, dtstart.day) - if not dtstart.tzinfo: - try: - dtstart = tzinfo.localize(dtstart) - except AttributeError: - dtstart.astimezone(tzinfo) - ## convert into timezone given in args: - dtstart = dtstart.astimezone(_tz(args.timezone)) + dtstart = _localize(dtstart, tzinfo) events.append({'dtstart': dtstart, 'instance': event}) ## changed to use the "key"-parameter at 2019-09-18, as needed for python3. diff --git a/setup.py b/setup.py index 55002c1..0d097c1 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ setup( install_requires=[ 'icalendar', 'caldav>=0.8.1', - 'pytz', + 'pytz', ## pytz is supposed to be obsoleted, but see https://github.com/collective/icalendar/issues/333 'tzlocal', 'six' ], diff --git a/tests/tests.sh b/tests/tests.sh index 2a3e784..5b6b1af 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -8,6 +8,8 @@ set -e error() { echo "$1" + echo "sleeping 3" + sleep 3 exit 255 } -- cgit v1.2.3