Repository: mkdocs/mkdocs

Issue:
Please support SOURCE_DATE_EPOCH specification for build time stamps
Copied from http://bugs.debian.org/824266:

> Hi,
> 
> mkdocs integrates build time stamps into the documentation it is
> generating. This makes at least unburden-home-dir no more reproducibly
> building because it now uses mkdocs to generate HTML documentation at
> build time.
> 
> See https://tests.reproducible-builds.org/rb-pkg/unstable/amd64/unburden-home-dir.html and
> https://tests.reproducible-builds.org/dbd/unstable/amd64/unburden-home-dir_0.4.diffoscope.html
> 
> Other cases of this issue are the source package djangorestframework (see
> https://tests.reproducible-builds.org/rb-pkg/testing/i386/djangorestframework.html and
> https://tests.reproducible-builds.org/dbd/testing/i386/djangorestframework_3.3.2-2.diffoscope.html)
> and of course the source package python-mkdocs itself, see
> https://tests.reproducible-builds.org/rb-pkg/unstable/amd64/python-mkdocs.html and
> https://tests.reproducible-builds.org/dbd/unstable/amd64/python-mkdocs_0.15.3-3.diffoscope.html
> 
> At https://reproducible-builds.org/specs/source-date-epoch/ you can find
> a bound to be widely adopted specification for how to solve such kind of
> issues generally: If the environment variable SOURCE_DATE_EPOCH exists
> use the Unix time stamp (seconds since 1st of January 1970, 00:00:00
> UTC) it contains to preseed all build time stamps with that time stamp
> instead of the current time.
> 
> See https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal#Examples
> for some examples on how to implement support for SOURCE_DATE_EPOCH,
> including an example for Python.



Discussion:
Yup, this change seems reasonable. Thanks for the report.

> See https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal#Examples
> for some examples on how to implement support for SOURCE_DATE_EPOCH,
> including an example for Python.

FYI, the Python example is wrong. If `SOURCE_DATE_EPOCH` is supposed to be a Unix timestamp (number of seconds since epoch), then `time.gmtime()` is not the Python equivalent. However, `calendar.timegm(datetime.datetime.utcnow().utctimetuple())` or `calendar.timegm(time.gmtime())` is. It is annoyingly complicated to get a Unix timestamp in Python. Although Python >= 3.3 makes it easier with `datetime.datetime.utcnow().timestamp()`. Not sure how the `datetime` module went so long without the ability to return a timestamp.


Resolving patch:
diff --git a/mkdocs/commands/build.py b/mkdocs/commands/build.py
--- a/mkdocs/commands/build.py
+++ b/mkdocs/commands/build.py
@@ -2,6 +2,7 @@
 
 from __future__ import unicode_literals
 from datetime import datetime
+from calendar import timegm
 import io
 import logging
 import os
@@ -56,6 +57,10 @@ def get_global_context(nav, config):
 
     extra_css = utils.create_media_urls(nav, config['extra_css'])
 
+    # Support SOURCE_DATE_EPOCH environment variable for "reproducible" builds.
+    # See https://reproducible-builds.org/specs/source-date-epoch/
+    timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timegm(datetime.utcnow().utctimetuple())))
+
     return {
         'site_name': site_name,
         'site_author': config['site_author'],
@@ -84,7 +89,7 @@ def get_global_context(nav, config):
         'google_analytics': config['google_analytics'],
 
         'mkdocs_version': mkdocs.__version__,
-        'build_date_utc': datetime.utcnow(),
+        'build_date_utc': datetime.utcfromtimestamp(timestamp),
 
         'config': config
     }


Test patch: