Merge pull request !2796 from rhodecode-enterprise-ce fix/RCCE-287_Notification-screen-gives-exception-after-migration

fix: fixes age function where comparing a timezone-aware and a naive datetime object could cause an exception.
This commit is contained in:
Andrii Verbytskyi 2025-08-07 17:20:06 +00:00
commit 28f4814da9
4 changed files with 46 additions and 6 deletions

View file

@ -31,6 +31,7 @@ Fixes
^^^^^
- ssh: Fixed an issue where the app.service_api.token field could be left empty in the configuration. The application will now automatically populate this field if it is missing.
- timezone: Fixed an issue in the age function where comparing a timezone-aware and a naive datetime object could cause an exception.
Upgrade notes
^^^^^^^^^^^^^

View file

@ -330,11 +330,24 @@ def age(prevdate, now=None, show_short_version=False, show_suffix=True, short_fo
def get_year(prevdate):
return prevdate.year
def _normalize_datetime_pair(prevdate, now):
"""
Ensure both datetime objects are the same kind (either both naive or both timezone-aware).
If not, comparing them will raise a TypeError due to mismatched timezone awareness.
"""
if prevdate.tzinfo or now.tzinfo:
utc = datetime.timezone.utc
return prevdate.replace(tzinfo=utc), now.replace(tzinfo=utc)
return prevdate, now # no timezone
now = now or datetime.datetime.now()
order = ["year", "month", "day", "hour", "minute", "second"]
deltas = {}
future = False
prevdate, now = _normalize_datetime_pair(prevdate, now)
if prevdate > now:
now_old = now
now = prevdate

View file

@ -3,8 +3,8 @@ var AgeModule = (function () {
return {
age: function(prevdate, now, show_short_version, show_suffix, short_format) {
var prevdate = moment(prevdate);
var now = now || moment().utc();
var prevdate = moment(prevdate).utc();
var now = now ? moment(now).utc() : moment().utc();
var show_short_version = show_short_version || false;
var show_suffix = show_suffix || true;

View file

@ -177,6 +177,32 @@ def test_age(age_args, expected, kw, baseapp):
assert translate(age(n + delt(**age_args), now=n, **kw)) == expected
@pytest.mark.parametrize(
"prevdate_fn, now_fn",
[
(functools.partial(datetime.datetime.now, tz=datetime.timezone.max), datetime.datetime.now),
(datetime.datetime.now, datetime.datetime.now),
(datetime.datetime.now, functools.partial(datetime.datetime.now, tz=datetime.timezone.max)),
(
functools.partial(datetime.datetime.now, tz=datetime.timezone.max),
functools.partial(datetime.datetime.now, tz=datetime.timezone.max),
),
(functools.partial(datetime.datetime.now, tz=datetime.timezone.max), lambda: None),
(datetime.datetime.now, lambda: None),
],
)
def test_age_timezone_aware(now_fn, prevdate_fn):
from rhodecode.lib.utils2 import age
now = now_fn()
prevdate = prevdate_fn()
try:
age(prevdate, now)
except Exception as e:
pytest.fail(f"Function raised an exception: {e}")
@pytest.mark.parametrize(
"age_args, expected, kw",
[
@ -326,19 +352,19 @@ def test_metatag_extraction(sample, expected_tags):
(("lang", "[lang =&gt; .NET]"), '<div class="metatag" tag="lang">.NET</div>'),
(
("license", "[license =&gt; BSD 3-clause]"),
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/BSD 3-clause">BSD 3-clause</a></div>',
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/BSD 3-clause">BSD 3-clause</a></div>', # noqa: W605
),
(
("license", "[license =&gt; GPLv3]"),
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/GPLv3">GPLv3</a></div>',
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/GPLv3">GPLv3</a></div>', # noqa: W605
),
(
("license", "[license =&gt; MIT]"),
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/MIT">MIT</a></div>',
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/MIT">MIT</a></div>', # noqa: W605
),
(
("license", "[license =&gt; AGPLv3]"),
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/AGPLv3">AGPLv3</a></div>',
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/AGPLv3">AGPLv3</a></div>', # noqa: W605
),
(
("ref", "[requires =&gt; RepoName]"),