mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
linkcheck: Fix conversion from UTC time to the UNIX epoch (#11649)
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
This commit is contained in:
parent
1567281287
commit
2f025a4b22
4
CHANGES
4
CHANGES
@ -6,6 +6,8 @@ Bugs fixed
|
|||||||
|
|
||||||
* #11618: Fix a regression in the MoveModuleTargets transform,
|
* #11618: Fix a regression in the MoveModuleTargets transform,
|
||||||
introduced in #10478 (#9662).
|
introduced in #10478 (#9662).
|
||||||
|
* #11649: linkcheck: Fix conversions from UTC to UNIX time
|
||||||
|
for timezones west of London.
|
||||||
|
|
||||||
Release 7.2.3 (released Aug 23, 2023)
|
Release 7.2.3 (released Aug 23, 2023)
|
||||||
=====================================
|
=====================================
|
||||||
@ -24,7 +26,7 @@ Bugs fixed
|
|||||||
when ``autodoc_preserve_defaults`` is ``True``.
|
when ``autodoc_preserve_defaults`` is ``True``.
|
||||||
* Restore support string methods on path objects.
|
* Restore support string methods on path objects.
|
||||||
This is deprecated and will be removed in Sphinx 8.
|
This is deprecated and will be removed in Sphinx 8.
|
||||||
Use :py:func`os.fspath` to convert :py:class:`~pathlib.Path` objects to strings,
|
Use :py:func:`os.fspath` to convert :py:class:`~pathlib.Path` objects to strings,
|
||||||
or :py:class:`~pathlib.Path`'s methods to work with path objects.
|
or :py:class:`~pathlib.Path`'s methods to work with path objects.
|
||||||
|
|
||||||
Release 7.2.2 (released Aug 17, 2023)
|
Release 7.2.2 (released Aug 17, 2023)
|
||||||
|
@ -7,7 +7,6 @@ import json
|
|||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
from email.utils import parsedate_tz
|
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
from os import path
|
from os import path
|
||||||
from queue import PriorityQueue, Queue
|
from queue import PriorityQueue, Queue
|
||||||
@ -29,6 +28,7 @@ from sphinx.util.console import ( # type: ignore[attr-defined]
|
|||||||
red,
|
red,
|
||||||
turquoise,
|
turquoise,
|
||||||
)
|
)
|
||||||
|
from sphinx.util.http_date import rfc1123_to_epoch
|
||||||
from sphinx.util.nodes import get_node_line
|
from sphinx.util.nodes import get_node_line
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -488,11 +488,8 @@ class HyperlinkAvailabilityCheckWorker(Thread):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
try:
|
try:
|
||||||
# An HTTP-date: time of next attempt.
|
# An HTTP-date: time of next attempt.
|
||||||
parsed = parsedate_tz(retry_after)
|
next_check = rfc1123_to_epoch(retry_after)
|
||||||
assert parsed is not None
|
except (ValueError, TypeError):
|
||||||
# the 10th element is the GMT offset in seconds
|
|
||||||
next_check = time.mktime(parsed[:9]) - (parsed[9] or 0)
|
|
||||||
except (AssertionError, TypeError, ValueError):
|
|
||||||
# TypeError: Invalid date format.
|
# TypeError: Invalid date format.
|
||||||
# ValueError: Invalid date, e.g. Oct 52th.
|
# ValueError: Invalid date, e.g. Oct 52th.
|
||||||
pass
|
pass
|
||||||
|
@ -4,7 +4,12 @@ Reference: https://www.rfc-editor.org/rfc/rfc7231#section-7.1.1.1
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from email.utils import formatdate, parsedate
|
import warnings
|
||||||
|
from email.utils import formatdate, parsedate_tz
|
||||||
|
|
||||||
|
from sphinx.deprecation import RemovedInSphinx90Warning
|
||||||
|
|
||||||
|
_GMT_OFFSET = float(time.localtime().tm_gmtoff)
|
||||||
|
|
||||||
|
|
||||||
def epoch_to_rfc1123(epoch: float) -> str:
|
def epoch_to_rfc1123(epoch: float) -> str:
|
||||||
@ -14,7 +19,21 @@ def epoch_to_rfc1123(epoch: float) -> str:
|
|||||||
|
|
||||||
def rfc1123_to_epoch(rfc1123: str) -> float:
|
def rfc1123_to_epoch(rfc1123: str) -> float:
|
||||||
"""Return epoch offset from HTTP-date string."""
|
"""Return epoch offset from HTTP-date string."""
|
||||||
t = parsedate(rfc1123)
|
t = parsedate_tz(rfc1123)
|
||||||
if t:
|
if t is None:
|
||||||
return time.mktime(t)
|
raise ValueError
|
||||||
raise ValueError
|
if not rfc1123.endswith(" GMT"):
|
||||||
|
warnings.warn(
|
||||||
|
"HTTP-date string does not meet RFC 7231 requirements "
|
||||||
|
f"(must end with 'GMT'): {rfc1123!r}",
|
||||||
|
RemovedInSphinx90Warning, stacklevel=3,
|
||||||
|
)
|
||||||
|
epoch_secs = time.mktime(time.struct_time(t[:9])) + _GMT_OFFSET
|
||||||
|
if (gmt_offset := t[9]) != 0:
|
||||||
|
warnings.warn(
|
||||||
|
"HTTP-date string does not meet RFC 7231 requirements "
|
||||||
|
f"(must be GMT time): {rfc1123!r}",
|
||||||
|
RemovedInSphinx90Warning, stacklevel=3,
|
||||||
|
)
|
||||||
|
return epoch_secs - (gmt_offset or 0)
|
||||||
|
return epoch_secs
|
||||||
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
|||||||
import http.server
|
import http.server
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
import time
|
import time
|
||||||
import wsgiref.handlers
|
import wsgiref.handlers
|
||||||
@ -16,6 +17,7 @@ from unittest import mock
|
|||||||
import pytest
|
import pytest
|
||||||
from urllib3.poolmanager import PoolManager
|
from urllib3.poolmanager import PoolManager
|
||||||
|
|
||||||
|
import sphinx.util.http_date
|
||||||
from sphinx.builders.linkcheck import (
|
from sphinx.builders.linkcheck import (
|
||||||
CheckRequest,
|
CheckRequest,
|
||||||
Hyperlink,
|
Hyperlink,
|
||||||
@ -772,11 +774,22 @@ def test_too_many_requests_retry_after_int_delay(app, capsys, status):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('tz', [None, 'GMT', 'GMT+3', 'GMT-3'])
|
||||||
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True)
|
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True)
|
||||||
def test_too_many_requests_retry_after_HTTP_date(app, capsys):
|
def test_too_many_requests_retry_after_HTTP_date(tz, app, monkeypatch, capsys):
|
||||||
retry_after = wsgiref.handlers.format_date_time(time.time())
|
retry_after = wsgiref.handlers.format_date_time(time.time())
|
||||||
with http_server(make_retry_after_handler([(429, retry_after), (200, None)])):
|
|
||||||
app.build()
|
with monkeypatch.context() as m:
|
||||||
|
if tz is not None:
|
||||||
|
m.setenv('TZ', tz)
|
||||||
|
if sys.platform != "win32":
|
||||||
|
time.tzset()
|
||||||
|
m.setattr(sphinx.util.http_date, '_GMT_OFFSET',
|
||||||
|
float(time.localtime().tm_gmtoff))
|
||||||
|
|
||||||
|
with http_server(make_retry_after_handler([(429, retry_after), (200, None)])):
|
||||||
|
app.build()
|
||||||
|
|
||||||
content = (app.outdir / 'output.json').read_text(encoding='utf8')
|
content = (app.outdir / 'output.json').read_text(encoding='utf8')
|
||||||
assert json.loads(content) == {
|
assert json.loads(content) == {
|
||||||
"filename": "index.rst",
|
"filename": "index.rst",
|
||||||
|
Loading…
Reference in New Issue
Block a user