New consistent representation of date ranges with times (Z#23184657)

This commit is contained in:
Raphael Michel
2025-03-18 14:40:38 +01:00
committed by GitHub
parent a22cc56944
commit 7652f48dc1
15 changed files with 106 additions and 79 deletions

View File

@@ -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)

View File

@@ -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 }} &middot;
{% 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 }}

View File

@@ -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 %}

View File

@@ -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>

View File

@@ -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>

View File

@@ -429,11 +429,7 @@
{% endif %}
{% if line.subevent %}
<br/>
<span class="fa fa-calendar fa-fw"></span> {{ line.subevent.name }} &middot; {{ 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 }} &middot; {{ 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>

View File

@@ -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">

View File

@@ -42,11 +42,7 @@
{% if form.pos.subevent %}
<p>
<span class="fa fa-calendar" aria-hidden="true"></span>
{{ form.pos.subevent.name }} &middot; {{ 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 }} &middot; {{ form.pos.subevent.get_date_range_display_with_times_as_html }}
</p>
{% endif %}
{% include "pretixpresale/event/fragment_addon_choice.html" with form=form %}

View File

@@ -52,13 +52,7 @@
</label>
<div class="col-md-9 form-control-text">
<ul class="addon-list">
{{ form.position.subevent.name }} &middot; {{ 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 }} &middot; {{ form.position.subevent.get_date_range_display_with_times_as_html }}
</ul>
</div>
</div>

View File

@@ -130,13 +130,7 @@
</label>
<div class="col-md-9 form-control-text">
<ul class="addon-list">
{{ pos.subevent.name }} &middot; {{ 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 }} &middot; {{ pos.subevent.get_date_range_display_with_times_as_html }}
</ul>
</div>
</div>

View File

@@ -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 %}
&middot; <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>

View File

@@ -21,11 +21,7 @@
</label>
<div class="col-md-9 form-control-text">
<ul class="addon-list">
{{ position.subevent.name }} &middot; {{ 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 }} &middot; {{ position.subevent.get_date_range_display_with_times_as_html }}
</ul>
</div>
</div>

View File

@@ -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"))))

View File

@@ -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;

View File

@@ -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:0021: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:0021: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:0002: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:0002: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):