7
7
import pickle
8
8
from collections import defaultdict
9
9
from copy import copy
10
- from datetime import datetime
10
+ from datetime import datetime , timezone
11
11
from os import path
12
12
from typing import TYPE_CHECKING , Any , Callable , Generator , Iterator
13
13
55
55
56
56
# This is increased every time an environment attribute is added
57
57
# or changed to properly invalidate pickle files.
58
- ENV_VERSION = 57
58
+ ENV_VERSION = 58
59
59
60
60
# config status
61
61
CONFIG_OK = 1
@@ -166,9 +166,9 @@ def __init__(self, app: Sphinx):
166
166
# All "docnames" here are /-separated and relative and exclude
167
167
# the source suffix.
168
168
169
- # docname -> mtime at the time of reading
169
+ # docname -> time of reading (in integer microseconds)
170
170
# contains all read docnames
171
- self .all_docs : dict [str , float ] = {}
171
+ self .all_docs : dict [str , int ] = {}
172
172
# docname -> set of dependent file
173
173
# names, relative to documentation root
174
174
self .dependencies : dict [str , set [str ]] = defaultdict (set )
@@ -481,12 +481,14 @@ def get_outdated_files(self, config_changed: bool) -> tuple[set[str], set[str],
481
481
continue
482
482
# check the mtime of the document
483
483
mtime = self .all_docs [docname ]
484
- newmtime = path . getmtime (self .doc2path (docname ))
484
+ newmtime = _last_modified_time (self .doc2path (docname ))
485
485
if newmtime > mtime :
486
+ # convert integer microseconds to floating-point seconds,
487
+ # and then to timezone-aware datetime objects.
488
+ mtime_dt = datetime .fromtimestamp (mtime / 1_000_000 , tz = timezone .utc )
489
+ newmtime_dt = datetime .fromtimestamp (mtime / 1_000_000 , tz = timezone .utc )
486
490
logger .debug ('[build target] outdated %r: %s -> %s' ,
487
- docname ,
488
- datetime .utcfromtimestamp (mtime ),
489
- datetime .utcfromtimestamp (newmtime ))
491
+ docname , mtime_dt , newmtime_dt )
490
492
changed .add (docname )
491
493
continue
492
494
# finally, check the mtime of dependencies
@@ -497,7 +499,7 @@ def get_outdated_files(self, config_changed: bool) -> tuple[set[str], set[str],
497
499
if not path .isfile (deppath ):
498
500
changed .add (docname )
499
501
break
500
- depmtime = path . getmtime (deppath )
502
+ depmtime = _last_modified_time (deppath )
501
503
if depmtime > mtime :
502
504
changed .add (docname )
503
505
break
@@ -728,3 +730,18 @@ def check_consistency(self) -> None:
728
730
for domain in self .domains .values ():
729
731
domain .check_consistency ()
730
732
self .events .emit ('env-check-consistency' , self )
733
+
734
+
735
+ def _last_modified_time (filename : str | os .PathLike [str ]) -> int :
736
+ """Return the last modified time of ``filename``.
737
+
738
+ The time is returned as integer microseconds.
739
+ The lowest common denominator of modern file-systems seems to be
740
+ microsecond-level precision.
741
+
742
+ We prefer to err on the side of re-rendering a file,
743
+ so we round up to the nearest microsecond.
744
+ """
745
+
746
+ # upside-down floor division to get the ceiling
747
+ return - (os .stat (filename ).st_mtime_ns // - 1_000 )
0 commit comments