diff --git a/flask_babel/__init__.py b/flask_babel/__init__.py index 9f94d22..e2301a1 100644 --- a/flask_babel/__init__.py +++ b/flask_babel/__init__.py @@ -10,7 +10,7 @@ import os from dataclasses import dataclass from types import SimpleNamespace -from datetime import datetime +from datetime import datetime, timezone as stdlib_timezone from contextlib import contextmanager from typing import List, Callable, Optional, Union @@ -453,7 +453,16 @@ def format_timedelta(datetime_or_timedelta, granularity: str = 'second', named `timedeltaformat`. """ if isinstance(datetime_or_timedelta, datetime): - datetime_or_timedelta = datetime.utcnow() - datetime_or_timedelta + is_aware = ( + datetime_or_timedelta.tzinfo is not None + and datetime_or_timedelta.tzinfo.utcoffset(datetime_or_timedelta) + is not None + ) + if is_aware: + now = datetime.now(stdlib_timezone.utc) + else: + now = datetime.utcnow() + datetime_or_timedelta = now - datetime_or_timedelta return dates.format_timedelta( datetime_or_timedelta, granularity, diff --git a/tests/test_date_formatting.py b/tests/test_date_formatting.py index 545a59d..cb81d3c 100644 --- a/tests/test_date_formatting.py +++ b/tests/test_date_formatting.py @@ -1,30 +1,57 @@ -from datetime import datetime, timedelta +import pytz +from datetime import datetime, timedelta, timezone import flask import flask_babel as babel from flask_babel import get_babel +import time_machine def test_basics(): + london = pytz.timezone("Europe/London") app = flask.Flask(__name__) babel.Babel(app) d = datetime(2010, 4, 12, 13, 46) + d_tzinfo_non_dst = london.localize(datetime(2010, 1, 16, 23, 23)) # non-DST + d_tzinfo_dst = london.localize(datetime(2010, 8, 12, 5, 13)) # DST delta = timedelta(days=6) with app.test_request_context(): assert babel.format_datetime(d) == 'Apr 12, 2010, 1:46:00\u202fPM' + assert babel.format_datetime(d_tzinfo_non_dst) == \ + "Jan 16, 2010, 11:23:00\u202fPM" + assert babel.format_datetime(d_tzinfo_dst) == \ + "Aug 12, 2010, 4:13:00\u202fAM" assert babel.format_date(d) == 'Apr 12, 2010' assert babel.format_time(d) == '1:46:00\u202fPM' assert babel.format_timedelta(delta) == '1 week' assert babel.format_timedelta(delta, threshold=1) == '6 days' + with time_machine.travel(datetime(2010, 4, 12, 14, tzinfo=timezone.utc)): + assert babel.format_timedelta(d) == "14 minutes" + with time_machine.travel(datetime(2010, 1, 16, 23, 30, tzinfo=timezone.utc)): + assert babel.format_timedelta(d_tzinfo_non_dst) == "7 minutes" + with time_machine.travel(datetime(2010, 8, 12, 4, 30, tzinfo=timezone.utc)): + assert babel.format_timedelta(d_tzinfo_dst) == "17 minutes" + with app.test_request_context(): get_babel(app).default_timezone = 'Europe/Vienna' assert babel.format_datetime(d) == 'Apr 12, 2010, 3:46:00\u202fPM' + assert babel.format_datetime(d_tzinfo_non_dst) == \ + "Jan 17, 2010, 12:23:00\u202fAM" + assert babel.format_datetime(d_tzinfo_dst) == \ + "Aug 12, 2010, 6:13:00\u202fAM" assert babel.format_date(d) == 'Apr 12, 2010' assert babel.format_time(d) == '3:46:00\u202fPM' + with time_machine.travel(datetime(2010, 4, 12, 14, tzinfo=timezone.utc)): + assert babel.format_timedelta(d) == "14 minutes" + with time_machine.travel(datetime(2010, 1, 16, 23, 30, tzinfo=timezone.utc)): + assert babel.format_timedelta(d_tzinfo_non_dst) == "7 minutes" + with time_machine.travel(datetime(2010, 8, 12, 4, 30, tzinfo=timezone.utc)): + assert babel.format_timedelta(d_tzinfo_dst) == "17 minutes" + with app.test_request_context(): get_babel(app).default_locale = 'de_DE' assert babel.format_datetime(d, 'long') == \