Skip to content

Commit

Permalink
fix: Fix non-UTC timestamps
Browse files Browse the repository at this point in the history
Fixes a bug where all `datetime` timestamps in an event payload were serialized as if they were UTC timestamps, even if they were non-UTC timestamps, completely ignoring the timezone. Now, we respect the timezone by setting the timezone offset if it is present.

Fixes #3453.
  • Loading branch information
szokeasaurusrex committed Aug 27, 2024
1 parent 269d96d commit d7669f9
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 3 deletions.
9 changes: 7 additions & 2 deletions sentry_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import threading
import time
from collections import namedtuple
from datetime import datetime
from datetime import datetime, timezone
from decimal import Decimal
from functools import partial, partialmethod, wraps
from numbers import Real
Expand Down Expand Up @@ -226,8 +226,13 @@ def to_timestamp(value):


def format_timestamp(value):
"""Formats a timestamp in RFC 3339 format.
Any datetime objects with a non-UTC timezone are converted to UTC, so that all timestamps are formatted in UTC.
"""
# type: (datetime) -> str
return value.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
utctime = value.astimezone(timezone.utc)
return utctime.strftime("%Y-%m-%dT%H:%M:%S.%fZ")


def event_hint_with_exc_info(exc_info=None):
Expand Down
33 changes: 32 additions & 1 deletion tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import threading
import re
import sys
from datetime import timedelta
from datetime import timedelta, datetime
from unittest import mock

import pytest
Expand All @@ -13,6 +13,7 @@
Components,
Dsn,
env_to_bool,
format_timestamp,
get_current_thread_meta,
get_default_release,
get_error_message,
Expand Down Expand Up @@ -950,3 +951,33 @@ def target():
thread.start()
thread.join()
assert (main_thread.ident, main_thread.name) == results.get(timeout=1)


@pytest.mark.parametrize(
("input_timestamp", "expected_output"),
(
("2021-01-01T12:00:00Z", "2021-01-01T12:00:00.000000Z"), # UTC time
(
"2021-01-01T12:00:00+02:00",
"2021-01-01T10:00:00.000000Z",
), # Non-UTC time
(
"2021-01-01T12:00:00-07:00",
"2021-01-01T19:00:00.000000Z",
), # Another non-UTC time
),
)
def test_format_timestamp(input_timestamp, expected_output):
datetime_object = datetime.fromisoformat(input_timestamp)
formatted = format_timestamp(datetime_object)

assert formatted == expected_output


def test_format_timestamp_naive():
datetime_object = datetime.fromisoformat("2021-01-01T12:00:00")
timestamp_regex = r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}Z"

# Ensure that some timestamp is returned, without error. We currently treat these as local time, but this is an
# implementation detail which we should not assert here.
assert re.fullmatch(timestamp_regex, format_timestamp(datetime_object))

0 comments on commit d7669f9

Please sign in to comment.