Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow using GMT in the HTML last updated format #12907

Merged
merged 1 commit into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ Features added
* #12743: Add :option:`sphinx-build --exception-on-warning`,
to raise an exception when warnings are emitted during the build.
Patch by Adam Turner and Jeremy Maitin-Shepard.
* #12907: Add :confval:`html_last_updated_time_zone` to allow using
GMT (universal time) instead of local time for the date-time
supplied to :confval:`html_last_updated_fmt`.
Patch by Adam Turner.

Bugs fixed
----------
Expand Down
1 change: 1 addition & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
html_use_opensearch = 'https://www.sphinx-doc.org/en/master'
html_baseurl = 'https://www.sphinx-doc.org/en/master/'
html_favicon = '_static/favicon.svg'
html_last_updated_time_zone = 'GMT'

htmlhelp_basename = 'Sphinxdoc'

Expand Down
8 changes: 8 additions & 0 deletions doc/usage/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1717,6 +1717,14 @@ and also make use of these options.
The empty string is equivalent to :code-py:`'%b %d, %Y'`
(or a locale-dependent equivalent).

.. confval:: html_last_updated_time_zone
:type: :code-py:`'local' | 'GMT'`
:default: :code-py:`'local'`

Choose GMT (+00:00) or the system's local time zone
for the time supplied to :confval:`html_last_updated_fmt`.
This is most useful when the format used includes the time.

.. confval:: html_permalinks
:type: :code-py:`bool`
:default: :code-py:`True`
Expand Down
7 changes: 6 additions & 1 deletion sphinx/builders/html/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,10 @@ def prepare_writing(self, docnames: set[str]) -> None:
last_updated: str | None
if (lu_fmt := self.config.html_last_updated_fmt) is not None:
lu_fmt = lu_fmt or _('%b %d, %Y')
last_updated = format_date(lu_fmt, language=self.config.language)
local_time = self.config.html_last_updated_time_zone == 'local'
last_updated = format_date(
lu_fmt, language=self.config.language, local_time=local_time
)
else:
last_updated = None

Expand Down Expand Up @@ -1323,6 +1326,8 @@ def setup(app: Sphinx) -> ExtensionMetadata:
app.add_config_value('html_static_path', [], 'html')
app.add_config_value('html_extra_path', [], 'html')
app.add_config_value('html_last_updated_fmt', None, 'html', str)
app.add_config_value('html_last_updated_time_zone', 'local', 'html',
ENUM('GMT', 'local'))
app.add_config_value('html_sidebars', {}, 'html')
app.add_config_value('html_additional_pages', {}, 'html')
app.add_config_value('html_domain_indices', True, 'html', types={set, list})
Expand Down
22 changes: 19 additions & 3 deletions sphinx/util/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import os
import re
import sys
from datetime import datetime, timezone
from os import path
from typing import TYPE_CHECKING, NamedTuple
Expand Down Expand Up @@ -59,6 +60,11 @@ def __call__( # NoQA: E704

Formatter: TypeAlias = DateFormatter | TimeFormatter | DatetimeFormatter

if sys.version_info[:2] >= (3, 11):
from datetime import UTC
else:
UTC = timezone.utc

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -223,16 +229,26 @@ def babel_format_date(date: datetime, format: str, locale: str,


def format_date(
format: str, *, date: datetime | None = None, language: str,
format: str,
*,
date: datetime | None = None,
language: str,
local_time: bool = False,
) -> str:
if date is None:
# If time is not specified, try to use $SOURCE_DATE_EPOCH variable
# See https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal
source_date_epoch = os.getenv('SOURCE_DATE_EPOCH')
if source_date_epoch is not None:
date = datetime.fromtimestamp(float(source_date_epoch), tz=timezone.utc)
date = datetime.fromtimestamp(float(source_date_epoch), tz=UTC)
else:
date = datetime.now(tz=timezone.utc).astimezone()
date = datetime.now(tz=UTC)

if local_time:
# > If called with tz=None, the system local time zone
# > is assumed for the target time zone.
# https://docs.python.org/dev/library/datetime.html#datetime.datetime.astimezone
date = date.astimezone(tz=None)

result = []
tokens = date_format_re.split(format)
Expand Down
19 changes: 19 additions & 0 deletions tests/test_util/test_util_i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import datetime
import os
import time

import babel
import pytest
Expand Down Expand Up @@ -93,6 +94,24 @@ def test_format_date():
assert i18n.format_date(format, date=datet, language='en') == '+0000'


def test_format_date_timezone():
dt = datetime.datetime(2016, 8, 7, 5, 11, 17, 0, tzinfo=datetime.timezone.utc)
if time.localtime(dt.timestamp()).tm_gmtoff == 0:
raise pytest.skip('Local time zone is GMT') # NoQA: EM101

fmt = '%Y-%m-%d %H:%M:%S'

iso_gmt = dt.isoformat(' ').split('+')[0]
fd_gmt = i18n.format_date(fmt, date=dt, language='en', local_time=False)
assert fd_gmt == '2016-08-07 05:11:17'
assert fd_gmt == iso_gmt

iso_local = dt.astimezone().isoformat(' ').split('+')[0]
fd_local = i18n.format_date(fmt, date=dt, language='en', local_time=True)
assert fd_local == iso_local
assert fd_local != fd_gmt


@pytest.mark.sphinx('html', testroot='root')
def test_get_filename_for_language(app):
get_filename = i18n.get_image_filename_for_language
Expand Down