Skip to content

Commit

Permalink
Allow using GMT in the HTML last updated format (#12907)
Browse files Browse the repository at this point in the history
  • Loading branch information
AA-Turner committed Sep 22, 2024
1 parent cc85533 commit 45b7ebd
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 4 deletions.
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

0 comments on commit 45b7ebd

Please sign in to comment.