diff --git a/src/pretix/presale/templates/pretixpresale/base.html b/src/pretix/presale/templates/pretixpresale/base.html
index 42bb2950a2..b5856fba10 100644
--- a/src/pretix/presale/templates/pretixpresale/base.html
+++ b/src/pretix/presale/templates/pretixpresale/base.html
@@ -36,7 +36,7 @@
{% block custom_header %}{% endblock %}
-
+
{% block above %}
{% endblock %}
diff --git a/src/pretix/presale/views/__init__.py b/src/pretix/presale/views/__init__.py
index 2a4ecac6bf..b7c29bd9fa 100644
--- a/src/pretix/presale/views/__init__.py
+++ b/src/pretix/presale/views/__init__.py
@@ -133,8 +133,9 @@ class CartMixin:
try:
first_expiry = min(p.expires for p in positions) if positions else now()
- minutes_left = max(first_expiry - now(), timedelta()).seconds // 60
- seconds_left = max(first_expiry - now(), timedelta()).seconds % 60
+ total_seconds_left = max(first_expiry - now(), timedelta()).total_seconds()
+ minutes_left = total_seconds_left // 60
+ seconds_left = total_seconds_left % 60
except AttributeError:
first_expiry = None
minutes_left = None
diff --git a/src/pretix/static/pretixpresale/js/ui/cart.js b/src/pretix/static/pretixpresale/js/ui/cart.js
index 014cbeed24..d5b9b98ad2 100644
--- a/src/pretix/static/pretixpresale/js/ui/cart.js
+++ b/src/pretix/static/pretixpresale/js/ui/cart.js
@@ -3,6 +3,22 @@ var cart = {
_deadline: null,
_deadline_interval: null,
_deadline_call: 0,
+ _time_offset: 0,
+
+ _get_now: function () {
+ return moment().add(cart._time_offset, 'ms');
+ },
+
+ _calc_offset: function () {
+ if (typeof window.performance === "undefined") {
+ return;
+ }
+ var perf = window.performance.timing;
+ var server_time = Math.round(parseFloat($("body").attr("data-now")) * 1000);
+ // We use requestStart as we don't know how latency is distributed and we rather want to err on the safe side
+ var client_time = perf.requestStart;
+ cart._time_offset = server_time - client_time;
+ },
draw_deadline: function () {
function pad(n, width, z) {
@@ -16,8 +32,9 @@ var cart = {
// Language files are not loaded yet, don't run during the first seconds
return;
}
- var diff_minutes = Math.floor(cart._deadline.diff(moment()) / 1000 / 60);
- var diff_seconds = Math.floor(cart._deadline.diff(moment()) / 1000 % 60);
+ var now = cart._get_now();
+ var diff_minutes = Math.floor(cart._deadline.diff(now) / 1000 / 60);
+ var diff_seconds = Math.floor(cart._deadline.diff(now) / 1000 % 60);
if (diff_minutes < 0) {
$("#cart-deadline").text(gettext("The items in your cart are no longer reserved for you."));
$("#cart-deadline-short").text(
@@ -40,6 +57,7 @@ var cart = {
"use strict";
cart._deadline = moment($("#cart-deadline").attr("data-expires"));
cart._deadline_interval = window.setInterval(cart.draw_deadline, 500);
+ cart._calc_offset();
cart.draw_deadline();
}
};