forked from CGM_Public/pretix_original
New consistent representation of date ranges with times (Z#23184657)
This commit is contained in:
@@ -60,6 +60,7 @@ from django.urls import reverse
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import format_html
|
||||
from django.utils.timezone import make_aware, now
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
from django_scopes import ScopedManager, scopes_disabled
|
||||
@@ -171,7 +172,7 @@ class EventMixin:
|
||||
self.date_to.astimezone(tz), ("D" if short else "l")
|
||||
)
|
||||
|
||||
def get_date_range_display(self, tz=None, force_show_end=False, as_html=False) -> str:
|
||||
def get_date_range_display(self, tz=None, force_show_end=False, as_html=False, try_to_show_times=False) -> str:
|
||||
"""
|
||||
Returns a formatted string containing the start date and the end date
|
||||
of the event with respect to the current locale and to the ``show_date_to``
|
||||
@@ -180,36 +181,48 @@ class EventMixin:
|
||||
tz = tz or self.timezone
|
||||
if (not self.settings.show_date_to and not force_show_end) or not self.date_to:
|
||||
df, dt = self.date_from, self.date_from
|
||||
show_times = try_to_show_times
|
||||
else:
|
||||
df, dt = self.date_from, self.date_to
|
||||
return daterange(df.astimezone(tz), dt.astimezone(tz), as_html)
|
||||
show_times = try_to_show_times and self.settings.show_times and (
|
||||
# Show times if start and end are on the same day ("08:00-10:00")
|
||||
dt.astimezone(tz).date() == df.astimezone(tz).date() or
|
||||
# Show times if start and end are on consecutive days and less than 24h ("23:00-03:00")
|
||||
(dt.astimezone(tz).date() == df.astimezone(tz).date() + timedelta(days=1) and
|
||||
dt.astimezone(tz).time() < df.astimezone(tz).time())
|
||||
)
|
||||
d = daterange(df.astimezone(tz), dt.astimezone(tz), as_html)
|
||||
|
||||
if show_times:
|
||||
if (not self.settings.show_date_to and not force_show_end) or not self.date_to:
|
||||
time_str = _date(self.date_from.astimezone(tz), "TIME_FORMAT")
|
||||
else:
|
||||
time_str = '{}–{}'.format(
|
||||
_date(self.date_from.astimezone(tz), "TIME_FORMAT"),
|
||||
_date(self.date_to.astimezone(tz), "TIME_FORMAT"),
|
||||
)
|
||||
|
||||
if as_html:
|
||||
d = format_html(
|
||||
d + ' <time datetime="{}" data-timezone="{}" data-time-short>{}</time>',
|
||||
self.date_from.isoformat(),
|
||||
str(self.timezone),
|
||||
time_str,
|
||||
)
|
||||
else:
|
||||
d = d + ' ' + time_str
|
||||
|
||||
return d
|
||||
|
||||
def get_date_range_display_with_times(self) -> str: # Helper for usage from templates
|
||||
return self.get_date_range_display(try_to_show_times=True)
|
||||
|
||||
def get_date_range_display_with_times_as_html(self) -> str: # Helper for usage from templates
|
||||
return self.get_date_range_display(try_to_show_times=True, as_html=True)
|
||||
|
||||
def get_date_range_display_as_html(self, tz=None, force_show_end=False) -> str:
|
||||
return self.get_date_range_display(tz, force_show_end, as_html=True)
|
||||
|
||||
def get_time_range_display(self, tz=None, force_show_end=False) -> str:
|
||||
"""
|
||||
Returns a formatted string containing the start time and sometimes the end time
|
||||
of the event with respect to the current locale and to the ``show_date_to``
|
||||
setting. Dates are not shown. This is usually used in combination with get_date_range_display
|
||||
"""
|
||||
tz = tz or self.timezone
|
||||
|
||||
show_date_to = self.date_to and (self.settings.show_date_to or force_show_end) and (
|
||||
# Show date to if start and end are on the same day ("08:00-10:00")
|
||||
self.date_to.astimezone(tz).date() == self.date_from.astimezone(tz).date() or
|
||||
# Show date to if start and end are on consecutive days and less than 24h ("23:00-03:00")
|
||||
(self.date_to.astimezone(tz).date() == self.date_from.astimezone(tz).date() + timedelta(days=1) and
|
||||
self.date_to.astimezone(tz).time() < self.date_from.astimezone(tz).time())
|
||||
# Do not show end time if this is a 5-day event because there's no way to make it understandable
|
||||
)
|
||||
if show_date_to:
|
||||
return '{} – {}'.format(
|
||||
_date(self.date_from.astimezone(tz), "TIME_FORMAT"),
|
||||
_date(self.date_to.astimezone(tz), "TIME_FORMAT"),
|
||||
)
|
||||
return _date(self.date_from.astimezone(tz), "TIME_FORMAT")
|
||||
|
||||
@property
|
||||
def timezone(self):
|
||||
return pytz_deprecation_shim.timezone(self.settings.timezone)
|
||||
|
||||
@@ -15,10 +15,7 @@
|
||||
{{ event.name }}
|
||||
<br>
|
||||
{% if event.has_subevents and ev.name|upper != event.name|upper %}{{ ev.name }}<br>{% endif %}
|
||||
{{ ev.get_date_range_display }}
|
||||
{% if event.settings.show_times %}
|
||||
{{ ev.date_from|date:"TIME_FORMAT" }}
|
||||
{% endif %}
|
||||
{{ ev.get_date_range_display_with_times }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -107,10 +104,7 @@
|
||||
{% if groupkey.2.name|upper != event.name|upper %}
|
||||
{{ groupkey.2.name }} ·
|
||||
{% endif %}
|
||||
{{ groupkey.2.get_date_range_display }}
|
||||
{% if event.settings.show_times %}
|
||||
{{ groupkey.2.date_from|date:"TIME_FORMAT" }}
|
||||
{% endif %}
|
||||
{{ groupkey.2.get_date_range_display_with_times }}
|
||||
{% if groupkey.2.location %}
|
||||
<br>
|
||||
{{ groupkey.2.location|oneline }}
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
<td>{{ e.item }}{% if e.variation %} – {{ e.variation }}{% endif %}</td>
|
||||
{% if request.event.has_subevents and not checkinlist.subevent %}
|
||||
<td>
|
||||
{{ e.subevent.name }} – {{ e.subevent.get_date_range_display }} {{ e.subevent.date_from|date:"TIME_FORMAT" }}
|
||||
{{ e.subevent.name }} – {{ e.subevent.get_date_range_display_with_times }}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% if seats %}
|
||||
|
||||
@@ -127,8 +127,7 @@
|
||||
{% if request.event.has_subevents %}
|
||||
{% if cl.subevent %}
|
||||
<td>
|
||||
{{ cl.subevent.name }} – {{ cl.subevent.get_date_range_display }}
|
||||
{{ cl.subevent.date_from|date:"TIME_FORMAT" }}
|
||||
{{ cl.subevent.name }} – {{ cl.subevent.get_date_range_display_with_times }}
|
||||
</td>
|
||||
{% else %}
|
||||
<td>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
</td>
|
||||
{% if request.event.has_subevents %}
|
||||
<td>
|
||||
{{ q.subevent.name }} – {{ q.subevent.get_date_range_display }} {{ q.subevent.date_from|date:"TIME_FORMAT" }}
|
||||
{{ q.subevent.name }} – {{ q.subevent.get_date_range_display_with_times }}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}</td>
|
||||
|
||||
@@ -429,11 +429,7 @@
|
||||
{% endif %}
|
||||
{% if line.subevent %}
|
||||
<br/>
|
||||
<span class="fa fa-calendar fa-fw"></span> {{ line.subevent.name }} · {{ line.subevent.get_date_range_display }}
|
||||
{% if event.settings.show_times %}
|
||||
<span class="fa fa-clock-o"></span>
|
||||
{{ line.subevent.date_from|date:"TIME_FORMAT" }}
|
||||
{% endif %}
|
||||
<span class="fa fa-calendar fa-fw"></span> {{ line.subevent.name }} · {{ line.subevent.get_date_range_display_with_times }}
|
||||
{% endif %}
|
||||
{% if line.used_membership %}
|
||||
<br /><span class="fa fa-id-card fa-fw" aria-hidden="true"></span>
|
||||
|
||||
@@ -188,10 +188,7 @@
|
||||
</td>
|
||||
{% if request.event.has_subevents %}
|
||||
<td>
|
||||
{{ v.subevent.name }} – {{ v.subevent.get_date_range_display }}
|
||||
{% if request.event.settings.show_times %}
|
||||
{{ v.subevent.date_from|date:"TIME_FORMAT" }}
|
||||
{% endif %}
|
||||
{{ v.subevent.name }} – {{ v.subevent.get_date_range_display_with_times }}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td class="text-right flip">
|
||||
|
||||
@@ -42,11 +42,7 @@
|
||||
{% if form.pos.subevent %}
|
||||
<p>
|
||||
<span class="fa fa-calendar" aria-hidden="true"></span>
|
||||
{{ form.pos.subevent.name }} · {{ form.pos.subevent.get_date_range_display_as_html }}
|
||||
{% if form.pos.event.settings.show_times %}
|
||||
<span class="fa fa-clock-o" aria-hidden="true"></span>
|
||||
{{ form.pos.subevent.date_from|date:"TIME_FORMAT" }}
|
||||
{% endif %}
|
||||
{{ form.pos.subevent.name }} · {{ form.pos.subevent.get_date_range_display_with_times_as_html }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% include "pretixpresale/event/fragment_addon_choice.html" with form=form %}
|
||||
|
||||
@@ -52,13 +52,7 @@
|
||||
</label>
|
||||
<div class="col-md-9 form-control-text">
|
||||
<ul class="addon-list">
|
||||
{{ form.position.subevent.name }} · {{ form.position.subevent.get_date_range_display_as_html }}
|
||||
{% if form.position.event.settings.show_times %}
|
||||
<span data-time="{{ form.position.subevent.date_from.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
||||
<span class="fa fa-clock-o" aria-hidden="true"></span>
|
||||
{{ form.position.subevent.date_from|date:"TIME_FORMAT" }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{{ form.position.subevent.name }} · {{ form.position.subevent.get_date_range_display_with_times_as_html }}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -130,13 +130,7 @@
|
||||
</label>
|
||||
<div class="col-md-9 form-control-text">
|
||||
<ul class="addon-list">
|
||||
{{ pos.subevent.name }} · {{ pos.subevent.get_date_range_display_as_html }}
|
||||
{% if pos.event.settings.show_times %}
|
||||
<span data-time="{{ pos.subevent.date_from.isoformat }}" data-timezone="{{ request.event.timezone }}">
|
||||
<span class="fa fa-clock-o" aria-hidden="true"></span>
|
||||
{{ pos.subevent.date_from|date:"TIME_FORMAT" }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{{ pos.subevent.name }} · {{ pos.subevent.get_date_range_display_with_times_as_html }}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -62,12 +62,7 @@
|
||||
<span class="fa fa-calendar fa-fw" aria-hidden="true"></span> {{ line.subevent.name }}
|
||||
<br>
|
||||
<span class="text-muted" aria-hidden="true">
|
||||
{{ line.subevent.get_date_range_display }}
|
||||
{% if event.settings.show_times %}
|
||||
· <span data-time="{{ line.subevent.date_from.isoformat }}" data-timezone="{{ request.event.timezone }}" data-time-short>
|
||||
{{ line.subevent.get_time_range_display }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{{ line.subevent.get_date_range_display_with_times_as_html }}
|
||||
</span>
|
||||
<span class="sr-only">
|
||||
<time datetime="{% if event.settings.show_times %}{{ line.subevent.date_from.isoformat }}{% else %}{{ line.subevent.date_from|date:"Y-m-d" }}{% endif %}">{{ line.subevent.get_date_from_display }}</time>
|
||||
|
||||
@@ -21,11 +21,7 @@
|
||||
</label>
|
||||
<div class="col-md-9 form-control-text">
|
||||
<ul class="addon-list">
|
||||
{{ position.subevent.name }} · {{ position.subevent.get_date_range_display_as_html }}
|
||||
{% if position.event.settings.show_times %}
|
||||
<span class="fa fa-clock-o" aria-hidden="true"></span>
|
||||
{{ position.subevent.date_from|date:"TIME_FORMAT" }}
|
||||
{% endif %}
|
||||
{{ position.subevent.name }} · {{ position.subevent.get_date_range_display_with_times_as_html }}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -533,8 +533,8 @@ $(function () {
|
||||
form_handlers($("body"));
|
||||
|
||||
var local_tz = moment.tz.guess()
|
||||
$("span[data-timezone], small[data-timezone]").each(function() {
|
||||
var t = moment.tz($(this).attr("data-time"), $(this).attr("data-timezone"))
|
||||
$("span[data-timezone], small[data-timezone], time[data-timezone]").each(function() {
|
||||
var t = moment.tz($(this).attr("datetime") || $(this).attr("data-time"), $(this).attr("data-timezone"))
|
||||
var tz = moment.tz.zone($(this).attr("data-timezone"))
|
||||
var tpl = '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner text-nowrap"></div></div>';
|
||||
|
||||
@@ -544,6 +544,7 @@ $(function () {
|
||||
});
|
||||
if (t.tz(tz.name).format() !== t.tz(local_tz).format()) {
|
||||
var $add = $("<span>")
|
||||
$add.append(" ")
|
||||
$add.append($("<span>").addClass("fa fa-globe"))
|
||||
if ($(this).is("[data-time-short]")) {
|
||||
$add.append($("<em>").text(" " + t.tz(local_tz).format($("body").attr("data-timeformat"))))
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
position: relative;
|
||||
padding-left: 1.4em;
|
||||
text-indent: 0;
|
||||
.fa, svg {
|
||||
& > .fa, & > svg, & > dd > .fa, & > dd > svg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: .22em;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
import datetime
|
||||
import sys
|
||||
import time
|
||||
import zoneinfo
|
||||
from datetime import date, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
@@ -2458,6 +2459,57 @@ class EventTest(TestCase):
|
||||
item.save()
|
||||
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
|
||||
|
||||
@classscope(attr='organizer')
|
||||
def test_date_range_display(self):
|
||||
tz = zoneinfo.ZoneInfo("Europe/Berlin")
|
||||
sets = (
|
||||
(
|
||||
datetime.datetime(2025, 3, 9, 21, 0, 0, tzinfo=tz),
|
||||
datetime.datetime(2025, 3, 9, 22, 0, 0, tzinfo=tz),
|
||||
'Sun, March 9th, 2025',
|
||||
'<time datetime="2025-03-09">Sun, March 9th, 2025</time>',
|
||||
'Sun, March 9th, 2025 20:00–21:00',
|
||||
'<time datetime="2025-03-09">Sun, March 9th, 2025</time> '
|
||||
'<time datetime="2025-03-09T21:00:00+01:00" data-timezone="UTC" data-time-short>20:00–21:00</time>'
|
||||
),
|
||||
(
|
||||
datetime.datetime(2025, 3, 9, 21, 0, 0, tzinfo=tz),
|
||||
datetime.datetime(2025, 3, 10, 3, 0, 0, tzinfo=tz),
|
||||
'March 9th – 10th, 2025',
|
||||
'<time datetime="2025-03-09">March 9th</time> – <time datetime="2025-03-10">10th, 2025</time>',
|
||||
'March 9th – 10th, 2025 20:00–02:00',
|
||||
'<time datetime="2025-03-09">March 9th</time> – <time datetime="2025-03-10">10th, 2025</time> '
|
||||
'<time datetime="2025-03-09T21:00:00+01:00" data-timezone="UTC" data-time-short>20:00–02:00</time>'
|
||||
),
|
||||
(
|
||||
datetime.datetime(2025, 3, 9, 21, 0, 0, tzinfo=tz),
|
||||
datetime.datetime(2025, 3, 12, 14, 0, 0, tzinfo=tz),
|
||||
'March 9th – 12th, 2025',
|
||||
'<time datetime="2025-03-09">March 9th</time> – <time datetime="2025-03-12">12th, 2025</time>',
|
||||
'March 9th – 12th, 2025',
|
||||
'<time datetime="2025-03-09">March 9th</time> – <time datetime="2025-03-12">12th, 2025</time>',
|
||||
),
|
||||
(
|
||||
datetime.datetime(2025, 3, 9, 21, 0, 0, tzinfo=tz),
|
||||
None,
|
||||
'Sun, March 9th, 2025',
|
||||
'<time datetime="2025-03-09">Sun, March 9th, 2025</time>',
|
||||
'Sun, March 9th, 2025 20:00',
|
||||
'<time datetime="2025-03-09">Sun, March 9th, 2025</time> '
|
||||
'<time datetime="2025-03-09T21:00:00+01:00" data-timezone="UTC" data-time-short>20:00</time>'
|
||||
),
|
||||
)
|
||||
|
||||
for i, (df, dt, expected, expected_html, expected_with_times, expected_with_times_html) in enumerate(sets):
|
||||
event = Event.objects.create(
|
||||
organizer=self.organizer, name='Dummy', slug=f'dummy{i}',
|
||||
date_from=df, date_to=dt,
|
||||
)
|
||||
assert event.get_date_range_display() == expected
|
||||
assert event.get_date_range_display(as_html=True) == expected_html
|
||||
assert event.get_date_range_display(try_to_show_times=True) == expected_with_times
|
||||
assert event.get_date_range_display(try_to_show_times=True, as_html=True) == expected_with_times_html
|
||||
|
||||
|
||||
class SubEventTest(TestCase):
|
||||
def setUp(self):
|
||||
|
||||
Reference in New Issue
Block a user