diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c4d50a9..188134fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file. ### Bug Fixes - Fix after-login redirects (the "?next=" query parameter) - Update Check.status field when user edits timeout & grace settings +- Use timezone-aware datetimes with croniter, avoid ambiguities around DST ## 1.3.0 - 2018-11-21 diff --git a/hc/api/models.py b/hc/api/models.py index 3eccd9a4..dd2100f7 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -15,6 +15,7 @@ from django.utils import timezone from hc.api import transports from hc.lib import emails import requests +import pytz STATUSES = ( ("up", "Up"), @@ -115,12 +116,15 @@ class Check(models.Model): if self.kind == "simple": return self.last_ping + self.timeout - # The complex case, next ping is expected based on cron schedule - with timezone.override(self.tz): - last_naive = timezone.make_naive(self.last_ping) - it = croniter(self.schedule, last_naive) - next_naive = it.get_next(datetime) - return timezone.make_aware(next_naive, is_dst=True) + # The complex case, next ping is expected based on cron schedule. + # Don't convert to naive datetimes (and so avoid ambiguities around + # DST transitions). + # croniter does handle timezone-aware datetimes. + + zone = pytz.timezone(self.tz) + last_local = timezone.localtime(self.last_ping, zone) + it = croniter(self.schedule, last_local) + return it.next(datetime) def get_status(self, now=None): """ Return "up" if the check is up or in grace, otherwise "down". """ diff --git a/hc/front/views.py b/hc/front/views.py index 43e68e4d..d465a2f5 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -28,7 +28,7 @@ from hc.front.schemas import telegram_callback from hc.front.templatetags.hc_extras import (num_down_title, down_title, sortchecks) from hc.lib import jsonschema -from pytz import all_timezones +import pytz from pytz.exceptions import UnknownTimeZoneError import requests @@ -130,7 +130,7 @@ def my_checks(request): "now": timezone.now(), "tags": pairs, "ping_endpoint": settings.PING_ENDPOINT, - "timezones": all_timezones, + "timezones": pytz.all_timezones, "num_available": request.team.check_limit - len(checks), "sort": request.profile.sort, "selected_tags": selected_tags, @@ -325,13 +325,11 @@ def cron_preview(request): tz = request.POST.get("tz") ctx = {"tz": tz, "dates": []} try: - with timezone.override(tz): - now_naive = timezone.make_naive(timezone.now()) - it = croniter(schedule, now_naive) - for i in range(0, 6): - naive = it.get_next(datetime) - aware = timezone.make_aware(naive, is_dst=True) - ctx["dates"].append((naive, aware)) + zone = pytz.timezone(tz) + now_local = timezone.localtime(timezone.now(), zone) + it = croniter(schedule, now_local) + for i in range(0, 6): + ctx["dates"].append(it.get_next(datetime)) except UnknownTimeZoneError: ctx["bad_tz"] = True except: @@ -420,7 +418,7 @@ def details(request, code): "page": "details", "check": check, "channels": channels, - "timezones": all_timezones + "timezones": pytz.all_timezones } return render(request, "front/details.html", ctx) diff --git a/requirements.txt b/requirements.txt index fc1708fe..0b7ade8d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -croniter -Django==2.1.3 +croniter==0.3.26 +Django==2.1.4 django_compressor==2.2 psycopg2==2.7.5 pytz==2018.7 diff --git a/templates/front/cron_preview.html b/templates/front/cron_preview.html index 5abb2826..a7d17b41 100644 --- a/templates/front/cron_preview.html +++ b/templates/front/cron_preview.html @@ -11,11 +11,13 @@ {% else %}
Expected Ping Dates | |||||
---|---|---|---|---|---|
{{ naive|date:"M j, H:i" }} | -{{ aware|naturaltime }} | -{{ aware|date:"c" }} | + {% timezone tz %} +{{ d|date:"M j, H:i" }} | + {% endtimezone %} +{{ d|naturaltime }} | +{{ d|date:"c" }} |