From ceca12940bd5f1cf49cca137735c3c892bf3ef84 Mon Sep 17 00:00:00 2001 From: Klaas van Schelven Date: Mon, 28 Jul 2025 10:24:36 +0200 Subject: [PATCH] Breadcrumb timestamps: display harmonized w/ rest of application in the correct timezone, with smaller milis According to the spec, this should work because: > The timestamp of the breadcrumb. Recommended. A timestamp representing when > the breadcrumb occurred. The format is either a string as defined in [RFC > 3339](https://tools.ietf.org/html/rfc3339) or a numeric (integer or float) > value representing the number of seconds that have elapsed since the [Unix > epoch](https://en.wikipedia.org/wiki/Unix_time). Breadcrumbs are most useful > when they include a timestamp, as it creates a timeline leading up to an > event. --- issues/templates/issues/breadcrumbs.html | 3 +-- issues/views.py | 15 +++-------- theme/templatetags/issues.py | 32 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/issues/templates/issues/breadcrumbs.html b/issues/templates/issues/breadcrumbs.html index 48f6143..2fc7dbd 100644 --- a/issues/templates/issues/breadcrumbs.html +++ b/issues/templates/issues/breadcrumbs.html @@ -49,8 +49,7 @@ {{ breadcrumb.message }} - {{ breadcrumb.timestamp }} - {# {{ breadcrumb.timestamp|date:"G:i T" and milis }} #} + {{ breadcrumb.timestamp|timestamp_with_millis }} diff --git a/issues/views.py b/issues/views.py index 990793a..f3096c1 100644 --- a/issues/views.py +++ b/issues/views.py @@ -7,8 +7,6 @@ from django.db.models import Q from django.utils import timezone from django.shortcuts import render, get_object_or_404, redirect from django.http import HttpResponseRedirect, HttpResponseNotAllowed -from django.utils.safestring import mark_safe -from django.template.defaultfilters import date from django.urls import reverse from django.core.exceptions import PermissionDenied from django.http import Http404 @@ -31,6 +29,7 @@ from events.ua_stuff import get_contexts_enriched_with_ua from compat.timestamp import format_timestamp from projects.models import ProjectMembership from tags.search import search_issues, search_events, search_events_optimized +from theme.templatetags.issues import timestamp_with_millis from .models import Issue, IssueQuerysetStateManager, IssueStateManager, TurningPoint, TurningPointKind from .forms import CommentForm @@ -555,12 +554,6 @@ def issue_event_breadcrumbs(request, issue, event_pk=None, digest_order=None, na }) -def _date_with_milis_html(timestamp): - return mark_safe( - date(timestamp, "j M G:i:s") + "." + - '' + date(timestamp, "u")[:3] + '') - - def _first_last(qs_with_digest_order): # this was once implemented with Min/Max, but just doing 2 queries is (on sqlite at least) much faster. first = qs_with_digest_order.order_by("digest_order").values_list("digest_order", flat=True).first() @@ -604,9 +597,9 @@ def issue_event_details(request, issue, event_pk=None, digest_order=None, nav=No ("mechanism", get_path(get_main_exception(parsed_data), "mechanism", "type")), ("issue_id", issue.id), - ("timestamp", _date_with_milis_html(event.timestamp)), - ("ingested at", _date_with_milis_html(event.ingested_at)), - ("digested at", _date_with_milis_html(event.digested_at)), + ("timestamp", timestamp_with_millis(event.timestamp)), + ("ingested at", timestamp_with_millis(event.ingested_at)), + ("digested at", timestamp_with_millis(event.digested_at)), ("digest order", event.digest_order), ("remote_addr", event.remote_addr), ] diff --git a/theme/templatetags/issues.py b/theme/templatetags/issues.py index 98e67b1..9f0032a 100644 --- a/theme/templatetags/issues.py +++ b/theme/templatetags/issues.py @@ -1,3 +1,4 @@ +from datetime import datetime import re from django import template from pygments import highlight @@ -5,6 +6,9 @@ from pygments.formatters import HtmlFormatter from django.utils.html import escape from django.utils.safestring import mark_safe +from django.template.defaultfilters import date + +from compat.timestamp import parse_timestamp from bugsink.pygments_extensions import guess_lexer_for_filename, lexer_for_platform @@ -235,3 +239,31 @@ def format_var(value): def incomplete(value): # needed to disinguish between 'has an incomplete' attr (set by us) and 'contains an incomplete key' (event-data) return hasattr(value, "incomplete") + + +def _date_with_milis_html(timestamp): + return mark_safe( + '' + + date(timestamp, "j M G:i:s") + "." + + '' + date(timestamp, "u")[:3] + '') + + +@register.filter +def timestamp_with_millis(value): + """ + Timestamp formatting with milliseconds; robust for datetime.datetime, as well as the strings/floats/ints that may + show up in the event data. + """ + if isinstance(value, datetime): + dt = value + + else: + try: + dt = parse_timestamp(value) + except Exception: + return value + + if dt is None: + return value + + return _date_with_milis_html(dt)