Skip to content

Commit 2f025a4

Browse files
mitya57AA-Turner
andauthored
linkcheck: Fix conversion from UTC time to the UNIX epoch (#11649)
Co-authored-by: Adam Turner <[email protected]>
1 parent 1567281 commit 2f025a4

File tree

4 files changed

+46
-15
lines changed

4 files changed

+46
-15
lines changed

CHANGES

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Bugs fixed
66

77
* #11618: Fix a regression in the MoveModuleTargets transform,
88
introduced in #10478 (#9662).
9+
* #11649: linkcheck: Fix conversions from UTC to UNIX time
10+
for timezones west of London.
911

1012
Release 7.2.3 (released Aug 23, 2023)
1113
=====================================
@@ -24,7 +26,7 @@ Bugs fixed
2426
when ``autodoc_preserve_defaults`` is ``True``.
2527
* Restore support string methods on path objects.
2628
This is deprecated and will be removed in Sphinx 8.
27-
Use :py:func`os.fspath` to convert :py:class:`~pathlib.Path` objects to strings,
29+
Use :py:func:`os.fspath` to convert :py:class:`~pathlib.Path` objects to strings,
2830
or :py:class:`~pathlib.Path`'s methods to work with path objects.
2931

3032
Release 7.2.2 (released Aug 17, 2023)

sphinx/builders/linkcheck.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import re
88
import socket
99
import time
10-
from email.utils import parsedate_tz
1110
from html.parser import HTMLParser
1211
from os import path
1312
from queue import PriorityQueue, Queue
@@ -29,6 +28,7 @@
2928
red,
3029
turquoise,
3130
)
31+
from sphinx.util.http_date import rfc1123_to_epoch
3232
from sphinx.util.nodes import get_node_line
3333

3434
if TYPE_CHECKING:
@@ -488,11 +488,8 @@ def limit_rate(self, response_url: str, retry_after: str) -> float | None:
488488
except ValueError:
489489
try:
490490
# An HTTP-date: time of next attempt.
491-
parsed = parsedate_tz(retry_after)
492-
assert parsed is not None
493-
# the 10th element is the GMT offset in seconds
494-
next_check = time.mktime(parsed[:9]) - (parsed[9] or 0)
495-
except (AssertionError, TypeError, ValueError):
491+
next_check = rfc1123_to_epoch(retry_after)
492+
except (ValueError, TypeError):
496493
# TypeError: Invalid date format.
497494
# ValueError: Invalid date, e.g. Oct 52th.
498495
pass

sphinx/util/http_date.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
"""
55

66
import time
7-
from email.utils import formatdate, parsedate
7+
import warnings
8+
from email.utils import formatdate, parsedate_tz
9+
10+
from sphinx.deprecation import RemovedInSphinx90Warning
11+
12+
_GMT_OFFSET = float(time.localtime().tm_gmtoff)
813

914

1015
def epoch_to_rfc1123(epoch: float) -> str:
@@ -14,7 +19,21 @@ def epoch_to_rfc1123(epoch: float) -> str:
1419

1520
def rfc1123_to_epoch(rfc1123: str) -> float:
1621
"""Return epoch offset from HTTP-date string."""
17-
t = parsedate(rfc1123)
18-
if t:
19-
return time.mktime(t)
20-
raise ValueError
22+
t = parsedate_tz(rfc1123)
23+
if t is None:
24+
raise ValueError
25+
if not rfc1123.endswith(" GMT"):
26+
warnings.warn(
27+
"HTTP-date string does not meet RFC 7231 requirements "
28+
f"(must end with 'GMT'): {rfc1123!r}",
29+
RemovedInSphinx90Warning, stacklevel=3,
30+
)
31+
epoch_secs = time.mktime(time.struct_time(t[:9])) + _GMT_OFFSET
32+
if (gmt_offset := t[9]) != 0:
33+
warnings.warn(
34+
"HTTP-date string does not meet RFC 7231 requirements "
35+
f"(must be GMT time): {rfc1123!r}",
36+
RemovedInSphinx90Warning, stacklevel=3,
37+
)
38+
return epoch_secs - (gmt_offset or 0)
39+
return epoch_secs

tests/test_build_linkcheck.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import http.server
66
import json
77
import re
8+
import sys
89
import textwrap
910
import time
1011
import wsgiref.handlers
@@ -16,6 +17,7 @@
1617
import pytest
1718
from urllib3.poolmanager import PoolManager
1819

20+
import sphinx.util.http_date
1921
from sphinx.builders.linkcheck import (
2022
CheckRequest,
2123
Hyperlink,
@@ -772,11 +774,22 @@ def test_too_many_requests_retry_after_int_delay(app, capsys, status):
772774
)
773775

774776

777+
@pytest.mark.parametrize('tz', [None, 'GMT', 'GMT+3', 'GMT-3'])
775778
@pytest.mark.sphinx('linkcheck', testroot='linkcheck-localserver', freshenv=True)
776-
def test_too_many_requests_retry_after_HTTP_date(app, capsys):
779+
def test_too_many_requests_retry_after_HTTP_date(tz, app, monkeypatch, capsys):
777780
retry_after = wsgiref.handlers.format_date_time(time.time())
778-
with http_server(make_retry_after_handler([(429, retry_after), (200, None)])):
779-
app.build()
781+
782+
with monkeypatch.context() as m:
783+
if tz is not None:
784+
m.setenv('TZ', tz)
785+
if sys.platform != "win32":
786+
time.tzset()
787+
m.setattr(sphinx.util.http_date, '_GMT_OFFSET',
788+
float(time.localtime().tm_gmtoff))
789+
790+
with http_server(make_retry_after_handler([(429, retry_after), (200, None)])):
791+
app.build()
792+
780793
content = (app.outdir / 'output.json').read_text(encoding='utf8')
781794
assert json.loads(content) == {
782795
"filename": "index.rst",

0 commit comments

Comments
 (0)