forked from GithubBackups/healthchecks
Use timezone-aware datetimes with croniter, avoid conversions to and from naive datetimes. This avoids ambiguities around DST transitions and properly solves #196
This commit is contained in:
parent
e21801f44e
commit
cf08f54c30
@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
- Fix after-login redirects (the "?next=" query parameter)
|
- Fix after-login redirects (the "?next=" query parameter)
|
||||||
- Update Check.status field when user edits timeout & grace settings
|
- 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
|
## 1.3.0 - 2018-11-21
|
||||||
|
@ -15,6 +15,7 @@ from django.utils import timezone
|
|||||||
from hc.api import transports
|
from hc.api import transports
|
||||||
from hc.lib import emails
|
from hc.lib import emails
|
||||||
import requests
|
import requests
|
||||||
|
import pytz
|
||||||
|
|
||||||
STATUSES = (
|
STATUSES = (
|
||||||
("up", "Up"),
|
("up", "Up"),
|
||||||
@ -115,12 +116,15 @@ class Check(models.Model):
|
|||||||
if self.kind == "simple":
|
if self.kind == "simple":
|
||||||
return self.last_ping + self.timeout
|
return self.last_ping + self.timeout
|
||||||
|
|
||||||
# The complex case, next ping is expected based on cron schedule
|
# The complex case, next ping is expected based on cron schedule.
|
||||||
with timezone.override(self.tz):
|
# Don't convert to naive datetimes (and so avoid ambiguities around
|
||||||
last_naive = timezone.make_naive(self.last_ping)
|
# DST transitions).
|
||||||
it = croniter(self.schedule, last_naive)
|
# croniter does handle timezone-aware datetimes.
|
||||||
next_naive = it.get_next(datetime)
|
|
||||||
return timezone.make_aware(next_naive, is_dst=True)
|
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):
|
def get_status(self, now=None):
|
||||||
""" Return "up" if the check is up or in grace, otherwise "down". """
|
""" Return "up" if the check is up or in grace, otherwise "down". """
|
||||||
|
@ -28,7 +28,7 @@ from hc.front.schemas import telegram_callback
|
|||||||
from hc.front.templatetags.hc_extras import (num_down_title, down_title,
|
from hc.front.templatetags.hc_extras import (num_down_title, down_title,
|
||||||
sortchecks)
|
sortchecks)
|
||||||
from hc.lib import jsonschema
|
from hc.lib import jsonschema
|
||||||
from pytz import all_timezones
|
import pytz
|
||||||
from pytz.exceptions import UnknownTimeZoneError
|
from pytz.exceptions import UnknownTimeZoneError
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ def my_checks(request):
|
|||||||
"now": timezone.now(),
|
"now": timezone.now(),
|
||||||
"tags": pairs,
|
"tags": pairs,
|
||||||
"ping_endpoint": settings.PING_ENDPOINT,
|
"ping_endpoint": settings.PING_ENDPOINT,
|
||||||
"timezones": all_timezones,
|
"timezones": pytz.all_timezones,
|
||||||
"num_available": request.team.check_limit - len(checks),
|
"num_available": request.team.check_limit - len(checks),
|
||||||
"sort": request.profile.sort,
|
"sort": request.profile.sort,
|
||||||
"selected_tags": selected_tags,
|
"selected_tags": selected_tags,
|
||||||
@ -325,13 +325,11 @@ def cron_preview(request):
|
|||||||
tz = request.POST.get("tz")
|
tz = request.POST.get("tz")
|
||||||
ctx = {"tz": tz, "dates": []}
|
ctx = {"tz": tz, "dates": []}
|
||||||
try:
|
try:
|
||||||
with timezone.override(tz):
|
zone = pytz.timezone(tz)
|
||||||
now_naive = timezone.make_naive(timezone.now())
|
now_local = timezone.localtime(timezone.now(), zone)
|
||||||
it = croniter(schedule, now_naive)
|
it = croniter(schedule, now_local)
|
||||||
for i in range(0, 6):
|
for i in range(0, 6):
|
||||||
naive = it.get_next(datetime)
|
ctx["dates"].append(it.get_next(datetime))
|
||||||
aware = timezone.make_aware(naive, is_dst=True)
|
|
||||||
ctx["dates"].append((naive, aware))
|
|
||||||
except UnknownTimeZoneError:
|
except UnknownTimeZoneError:
|
||||||
ctx["bad_tz"] = True
|
ctx["bad_tz"] = True
|
||||||
except:
|
except:
|
||||||
@ -420,7 +418,7 @@ def details(request, code):
|
|||||||
"page": "details",
|
"page": "details",
|
||||||
"check": check,
|
"check": check,
|
||||||
"channels": channels,
|
"channels": channels,
|
||||||
"timezones": all_timezones
|
"timezones": pytz.all_timezones
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, "front/details.html", ctx)
|
return render(request, "front/details.html", ctx)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
croniter
|
croniter==0.3.26
|
||||||
Django==2.1.3
|
Django==2.1.4
|
||||||
django_compressor==2.2
|
django_compressor==2.2
|
||||||
psycopg2==2.7.5
|
psycopg2==2.7.5
|
||||||
pytz==2018.7
|
pytz==2018.7
|
||||||
|
@ -11,11 +11,13 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<table id="cron-preview-table" class="table">
|
<table id="cron-preview-table" class="table">
|
||||||
<tr><th id="cron-preview-title" colspan="3">Expected Ping Dates</th></tr>
|
<tr><th id="cron-preview-title" colspan="3">Expected Ping Dates</th></tr>
|
||||||
{% for naive, aware in dates %}
|
{% for d in dates %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ naive|date:"M j, H:i" }}</td>
|
{% timezone tz %}
|
||||||
<td>{{ aware|naturaltime }}</td>
|
<td>{{ d|date:"M j, H:i" }}</td>
|
||||||
<td class="hidden-xs">{{ aware|date:"c" }}</td>
|
{% endtimezone %}
|
||||||
|
<td>{{ d|naturaltime }}</td>
|
||||||
|
<td class="hidden-xs">{{ d|date:"c" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user