SOURCE_DATE_EPOCH — reproducible-builds.org (original) (raw)

SOURCE_DATE_EPOCH is a standardised environment variable that distributions can set centrally and have build tools consume this in order to produce reproducible output. In practice, SOURCE_DATE_EPOCH specifies the last modification of something, usually the source code, measured in the number seconds since the Unix epoch, ie. January 1st 1970, 00:00:00 UTC.

Before added support to a tool for reading this variable, you should scan through Debian’s checklistto see if you can avoid implementing it: a number of tools already will do this for you. However, if you find that it’s ideal for your use-case, please feel free to jump straight to our published specification.

Reading the variable

Python >= 3.x

import os
import time
import datetime

build_date = datetime.datetime.fromtimestamp(
    int(os.environ.get('SOURCE_DATE_EPOCH', time.time())),
    tz=datetime.timezone.utc,
)

… or with fewer imports and rendering to a string:

import os
import time

date_str = time.strftime(
    "%Y-%m-%d",
    time.gmtime(int(os.environ.get('SOURCE_DATE_EPOCH', time.time())))
)

for zip files:

import os
import time
import zipfile
filetime = max(315532800, int(os.environ.get('SOURCE_DATE_EPOCH', time.time())))
with zipfile.ZipFile("test.zip", 'w') as ziparchive:
    file_zipinfo = zipfile.ZipInfo("hello.txt", date_time=time.gmtime(filetime))
    ziparchive.writestr(file_zipinfo, "Hello world\n")

Python >= 2.x

If you still require Python 2.x support, you will need to use the non-recommendeddatetime.utcfromtimestampmethod (more info):

import os
import time
import datetime

build_date = datetime.datetime.utcfromtimestamp(
    int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
)

Bash / POSIX shell

For GNU systems:

BUILD_DATE="$(date --utc --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y-%m-%d)"

If you need to support BSD date as well you should fallback to trying ther-r seconds timestamp variant:

DATE_FMT="+%Y-%m-%d"
SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(date +%s)}"
BUILD_DATE=$(date -u -d "@$SOURCE_DATE_EPOCH" "$DATE_FMT" 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" "$DATE_FMT" 2>/dev/null || date -u "$DATE_FMT")

Perl

use POSIX qw(strftime);
my <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi><mi>a</mi><mi>t</mi><mi>e</mi><mo>=</mo><mi>s</mi><mi>t</mi><mi>r</mi><mi>f</mi><mi>t</mi><mi>i</mi><mi>m</mi><mi>e</mi><mo stretchy="false">(</mo><mi mathvariant="normal">&quot;</mi></mrow><annotation encoding="application/x-tex">date = strftime(&quot;%Y-%m-%d&quot;, gmtime(</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal">t</span><span class="mord mathnormal">im</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord">&quot;</span></span></span></span>ENV{SOURCE_DATE_EPOCH} || time));

Makefile

DATE_FMT = +%Y-%m-%d
ifdef SOURCE_DATE_EPOCH
    BUILD_DATE ?= <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>s</mi><mi>h</mi><mi>e</mi><mi>l</mi><mi>l</mi><mi>d</mi><mi>a</mi><mi>t</mi><mi>e</mi><mo>−</mo><mi>u</mi><mo>−</mo><mi>d</mi><mi mathvariant="normal">&quot;</mi><mi mathvariant="normal">@</mi></mrow><annotation encoding="application/x-tex">(shell date -u -d &quot;@</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal">s</span><span class="mord mathnormal">h</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.01968em;">ll</span><span class="mord mathnormal">d</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">u</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span><span class="mord">&quot;@</span></span></span></span>(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u -r "$(SOURCE_DATE_EPOCH)" "$(DATE_FMT)" 2>/dev/null || date -u "$(DATE_FMT)")
else
    BUILD_DATE ?= <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>s</mi><mi>h</mi><mi>e</mi><mi>l</mi><mi>l</mi><mi>d</mi><mi>a</mi><mi>t</mi><mi>e</mi><mi mathvariant="normal">&quot;</mi></mrow><annotation encoding="application/x-tex">(shell date &quot;</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal">s</span><span class="mord mathnormal">h</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.01968em;">ll</span><span class="mord mathnormal">d</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mord">&quot;</span></span></span></span>(DATE_FMT)")
endif

The above will work with either GNU or BSD date, and fallback to ignoreSOURCE_DATE_EPOCH if both fails.

GNU Autotools / configure.ac

DATE_FMT="%Y-%m-%d"
BUILD_DATE=m4_esyscmd([date -u -d "@$SOURCE_DATE_EPOCH" <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi><mi>A</mi><mi>T</mi><msub><mi>E</mi><mi>F</mi></msub><mi>M</mi><mi>T</mi><mn>2</mn><mo>&gt;</mo><mi mathvariant="normal">/</mi><mi>d</mi><mi>e</mi><mi>v</mi><mi mathvariant="normal">/</mi><mi>n</mi><mi>u</mi><mi>l</mi><mi>l</mi><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mi>d</mi><mi>a</mi><mi>t</mi><mi>e</mi><mo>−</mo><mi>u</mi><mo>−</mo><mi>r</mi></mrow><annotation encoding="application/x-tex">DATE_FMT 2&gt;/dev/null || date -u -r </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal">A</span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0576em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">F</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal" style="margin-right:0.13889em;">MT</span><span class="mord">2</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&gt;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">/</span><span class="mord mathnormal">d</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.03588em;">v</span><span class="mord">/</span><span class="mord mathnormal">n</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.01968em;">ll</span><span class="mord">∣∣</span><span class="mord mathnormal">d</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">u</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span></span></span></span>SOURCE_DATE_EPOCH <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi><mi>A</mi><mi>T</mi><msub><mi>E</mi><mi>F</mi></msub><mi>M</mi><mi>T</mi><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mi>d</mi><mi>a</mi><mi>t</mi><mi>e</mi><mo>−</mo><mi>u</mi></mrow><annotation encoding="application/x-tex">DATE_FMT || date -u </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal">A</span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0576em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">F</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal" style="margin-right:0.13889em;">MT</span><span class="mord">∣∣</span><span class="mord mathnormal">d</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">u</span></span></span></span>DATE_FMT])
AC_SUBST([BUILD_DATE])

or:

DATE_FMT="%Y-%m-%d"
BUILD_DATE=`date $DATE_FMT`
AS_IF([test "x$SOURCE_DATE_EPOCH" != "x"], [
    BUILD_DATE=`date -u -d "@$SOURCE_DATE_EPOCH" <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi><mi>A</mi><mi>T</mi><msub><mi>E</mi><mi>F</mi></msub><mi>M</mi><mi>T</mi><mn>2</mn><mo>&gt;</mo><mi mathvariant="normal">/</mi><mi>d</mi><mi>e</mi><mi>v</mi><mi mathvariant="normal">/</mi><mi>n</mi><mi>u</mi><mi>l</mi><mi>l</mi><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mi>d</mi><mi>a</mi><mi>t</mi><mi>e</mi><mo>−</mo><mi>u</mi><mo>−</mo><mi>r</mi></mrow><annotation encoding="application/x-tex">DATE_FMT 2&gt;/dev/null || date -u -r </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal">A</span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0576em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">F</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal" style="margin-right:0.13889em;">MT</span><span class="mord">2</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&gt;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">/</span><span class="mord mathnormal">d</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.03588em;">v</span><span class="mord">/</span><span class="mord mathnormal">n</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.01968em;">ll</span><span class="mord">∣∣</span><span class="mord mathnormal">d</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">u</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span></span></span></span>SOURCE_DATE_EPOCH <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi><mi>A</mi><mi>T</mi><msub><mi>E</mi><mi>F</mi></msub><mi>M</mi><mi>T</mi><mi mathvariant="normal">∣</mi><mi mathvariant="normal">∣</mi><mi>d</mi><mi>a</mi><mi>t</mi><mi>e</mi><mo>−</mo><mi>u</mi></mrow><annotation encoding="application/x-tex">DATE_FMT || date -u </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal">A</span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0576em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">F</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord mathnormal" style="margin-right:0.13889em;">MT</span><span class="mord">∣∣</span><span class="mord mathnormal">d</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">u</span></span></span></span>DATE_FMT`
])
AC_SUBST([BUILD_DATE])

CMake

STRING(TIMESTAMP BUILD_DATE "%Y-%m-%d" UTC)

… will compile with CMake versions 2.8.11 and higher (released May 2013), but it only respects SOURCE_DATE_EPOCH since version 3.8.0 (April 2017). Note that the final argument UTC is required or the timestamp may vary between timezones.

If you would like to support legacy/archival versions of CMake, you can use this less-preferred variant:

if (DEFINED ENV{SOURCE_DATE_EPOCH})
  execute_process(
    COMMAND "date" "-u" "-d" "@$ENV{SOURCE_DATE_EPOCH}" "+%Y-%m-%d"
    OUTPUT_VARIABLE BUILD_DATE
    OUTPUT_STRIP_TRAILING_WHITESPACE)
else ()
  execute_process(
    COMMAND "date" "+%Y-%m-%d"
    OUTPUT_VARIABLE BUILD_DATE
    OUTPUT_STRIP_TRAILING_WHITESPACE)
endif ()

Note that the above will work only with GNU date; see the POSIX shell example on how to support BSD date.

Meson

By deliberate design, Meson does not provide access to environment variables in build fileswhich makes accessing SOURCE_DATE_EPOCH troublesome.

date_exe = find_program('date')
cmd = run_command('sh', '-c', 'echo $SOURCE_DATE_EPOCH')
source_date_epoch = cmd.stdout().strip()
if source_date_epoch == ''
    source_date_epoch = run_command(date_exe, '+%s').stdout().strip()
endif

formatted_date = run_command(date_exe, '-u', '-d', '@' + source_date_epoch, '+%Y-%m-%d').stdout().strip()

The above will work only with GNU date. See the POSIX shell example on how to support BSD date variants.

Alternative version

You could also use Python (or similar) to generate the formatted date:

python = import('python').find_installation('python3')

datetime = '''
import os
import time

print(time.strftime(
    "%Y-%m-%d",
    time.gmtime(int(os.environ.get('SOURCE_DATE_EPOCH', time.time())))
))
'''

formatted_date = run_command(python, '-c', datetime, check: true).stdout().strip()

Dockerfile

The SOURCE_DATE_EPOCH argument value is automaticallly propagated from theSOURCE_DATE_EPOCH environment value of the client host, since Docker Buildx v0.10.

The SOURCE_DATE_EPOCH argument value can be captured as an environment value for RUN instructions, by putting an ARG instruction between FROMand RUN:

FROM [...]
ARG SOURCE_DATE_EPOCH
RUN [...]

Capturing the SOURCE_DATE_EPOCH argument value is optional. Alternatively, the SOURCE_DATE_EPOCH environment value can be declared inside a RUN instruction too.

FROM [...]
ADD src /src
RUN [...]
  SOURCE_DATE_EPOCH="$(find /src -type f -exec stat -c '%Y' {} + | sort -nr | head -n1)"; \
  export SOURCE_DATE_EPOCH; \
# for logging validation/edification
  date --date "@$SOURCE_DATE_EPOCH" --rfc-2822; \
  [...]

Scope

Regardless to whether the SOURCE_DATE_EPOCH argument value is captured into Dockerfile with ARG SOURCE_DATE_EPOCH, the argument value is also used for: - the created timestamp in the OCI Image Config

To apply the SOURCE_DATE_EPOCH argument value to the timestamps of the files inside the image, specify rewrite-timestamp=true as an image exporter option:

docker buildx create --use --name buildkit
docker buildx build --output type=image,name=docker.io/username/image,push=true,rewrite-timestamp=true .

The rewrite-timestamp option is not set to true by default due to the overhead of rewriting image layers.

apt-get

RUN apt-get does not automatically consume SOURCE_DATE_EPOCH to install packages from the past snapshot.

https://github.com/reproducible-containers/repro-sources-list.sh can be used for reconfiguring /etc/apt/sources.listto use https://snapshot.debian.org/archive/debian/<SOURCE_DATE_EPOCH>/.

Further information

See: - https://github.com/moby/buildkit/blob/master/docs/build-repro.md - https://github.com/docker-library/official-images/issues/16044

C

#include <errno.h>
#include <limits.h>
#include <stdlib.h>

struct tm *build_time;
time_t now;
char *source_date_epoch;
unsigned long long epoch;
char *endptr;

source_date_epoch = getenv("SOURCE_DATE_EPOCH");
if (source_date_epoch) {
    errno = 0;
    epoch = strtoull(source_date_epoch, &endptr, 10);
    if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0))
            || (errno != 0 && epoch == 0)) {
        fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: strtoull: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    if (endptr == source_date_epoch) {
        fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: No digits were found: %s\n", endptr);
        exit(EXIT_FAILURE);
    }
    if (*endptr != '\0') {
        fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: Trailing garbage: %s\n", endptr);
        exit(EXIT_FAILURE);
    }
    if (epoch > ULONG_MAX) {
        fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: value must be smaller than or equal to %lu but was found to be: %llu \n", ULONG_MAX, epoch);
        exit(EXIT_FAILURE);
    }
    now = epoch;
} else {
    now = time(NULL);
}
build_time = gmtime(&now);

If you want less verbose code and are happy with the assumptions stated below, you can use

#include <stdlib.h>

time_t now;
char *source_date_epoch;
/* This assumes that the SOURCE_DATE_EPOCH environment variable will contain
   a correct, positive integer in the time_t range */
if ((source_date_epoch = getenv("SOURCE_DATE_EPOCH")) == NULL ||
    (now = (time_t)strtoll(source_date_epoch, NULL, 10)) <= 0)
        time(&now);

C++

#include <sstream>
#include <iostream>
#include <cstdlib>
#include <ctime>

  time_t now;
  char *source_date_epoch = std::getenv("SOURCE_DATE_EPOCH");
  if (source_date_epoch) {
    std::istringstream iss(source_date_epoch);
    iss >> now;
    if (iss.fail() || !iss.eof()) {
      std::cerr << "Error: Cannot parse SOURCE_DATE_EPOCH as integer\n";
      exit(27);
    }
  } else {
    now = std::time(NULL);
  }

Go

import (
        "fmt"
        "os"
        "strconv"
        "time"
)

[...]

source_date_epoch := os.Getenv("SOURCE_DATE_EPOCH")
var build_date string
if source_date_epoch == "" {
        build_date = time.Now().UTC().Format(http.TimeFormat)
} else {
        sde, err := strconv.ParseInt(source_date_epoch, 10, 64)
        if err != nil {
                panic(fmt.Sprintf("Invalid SOURCE_DATE_EPOCH: %s", err))
        }
        build_date = time.Unix(sde, 0).UTC().Format(http.TimeFormat)
}

PHP

\date('Y', (int)\getenv('SOURCE_DATE_EPOCH') ?: \time())

Emacs-Lisp

(current-time-string
  (when (getenv "SOURCE_DATE_EPOCH")
    (seconds-to-time
      (string-to-number
        (getenv "SOURCE_DATE_EPOCH"))))))

OCaml

let build_date =
  try
    float_of_string (Sys.getenv "SOURCE_DATE_EPOCH")
  with
    Not_found -> Unix.time ()

Java / gradle

Legacy Groovy implementation:

def buildDate = System.getenv("SOURCE_DATE_EPOCH") == null ?
  new java.util.Date() :
  new java.util.Date(1000 * Long.parseLong(System.getenv("SOURCE_DATE_EPOCH")))

JavaScript / Node.js

const epoch = Number(process.env["SOURCE_DATE_EPOCH"]);
const timestamp = Number.isInteger(epoch) ? new Date(epoch * 1000) : new Date();

CoffeeScript

epoch = Number(process.env["SOURCE_DATE_EPOCH"])
timestamp = if Number.isInteger(epoch) then new Date(epoch * 1000) else new Date()

Ruby

if ENV['SOURCE_DATE_EPOCH'].nil?
  now = Time.now
else
  now = Time.at(ENV['SOURCE_DATE_EPOCH'].to_i).gmtime
end

Note that Ruby’s Datetime.strftime is locale-independent by default.

Scala

To get milliseconds since Epoch:

sys.env.get("SOURCE_DATE_EPOCH").map(_.toLong * 1000)

To get a java.util.Date:

sys.env.get("SOURCE_DATE_EPOCH")
  .map(sde => new java.util.Date(sde.toLong * 1000))

Rust

Using the chrono crate:

use chrono::{TimeZone, Utc};
use std::env;

let now = match env::var("SOURCE_DATE_EPOCH") {
    Ok(val) => { Utc.timestamp_opt(val.parse::<i64>().unwrap(), 0).unwrap() }
    Err(_) => Utc::now(),
};

or

use chrono::{DateTime, NaiveDateTime, Utc};
use std::env;

let now = match env::var("SOURCE_DATE_EPOCH") {
    Ok(val) => {
        let naive = NaiveDateTime::from_timestamp(val.parse::<i64>().unwrap(), 0);
        let datetime: DateTime<Utc> = DateTime::from_utc(naive, Utc);
        datetime
    }
    Err(_) => Utc::now(),
};

Maven

Add the following property in the pom.xml file:

<properties>
    <project.build.outputTimestamp>
        ${env.SOURCE_DATE_EPOCH}
    </project.build.outputTimestamp>
</properties>

JDK

Builds of OpenJDK version 19 or later support the following new option in the jar and jmodcommands:

--date=TIMESTAMP
   The timestamp in ISO-8601 extended offset date-time with optional
   time-zone format, to use for the timestamp of the entries,
   e.g. "2022-02-12T12:30:00-05:00".

Groovy

import java.time.Instant
import java.time.temporal.ChronoUnit

def buildInstant = Instant.now().truncatedTo(ChronoUnit.SECONDS)
def sourceDateEpoch = System.getenv("SOURCE_DATE_EPOCH")
if (sourceDateEpoch != null) {
   def epochSeconds = Long.parseLong(sourceDateEpoch)
   buildInstant = Instant.ofEpochSecond(epochSeconds)
}
// Creates the timestamp in UTC using the ISO 8601 extended format.
def extendedTimestamp = buildInstant.toString()

R

if (Sys.getenv("SOURCE_DATE_EPOCH") == "") {
  current_date <- Sys.Date()
} else {
  current_date <- as.Date(as.numeric(Sys.getenv("SOURCE_DATE_EPOCH")) / 86400, "1970-01-01")
}

Last-resort using faketime

'’As a last resort to be avoided where possible’’ (e.g. if the upstream tool is too hard to patch, or too time-consuming for you right now to patch, or if they are being uncooperative or unresponsive), package maintainers may try something like the following:

debian/strip-nondeterminism/a2x:

#!/bin/sh
# Depends: faketime
# Eventually the upstream tool should support SOURCE_DATE_EPOCH internally.
test -n "$SOURCE_DATE_EPOCH" || { echo >&2 "$0: SOURCE_DATE_EPOCH not set"; exit 255; }
exec env NO_FAKE_STAT=1 faketime -f "$(TZ=UTC date -d "@$SOURCE_DATE_EPOCH" +'%Y-%m-%d %H:%M:%S')" /usr/bin/a2x "$@"

debian/rules:

export PATH := <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">(</mo><mi>C</mi><mi>U</mi><mi>R</mi><mi>D</mi><mi>I</mi><mi>R</mi><mo stretchy="false">)</mo><mi mathvariant="normal">/</mi><mi>d</mi><mi>e</mi><mi>b</mi><mi>i</mi><mi>a</mi><mi>n</mi><mi mathvariant="normal">/</mi><mi>s</mi><mi>t</mi><mi>r</mi><mi>i</mi><mi>p</mi><mo>−</mo><mi>n</mi><mi>o</mi><mi>n</mi><mi>d</mi><mi>e</mi><mi>t</mi><mi>e</mi><mi>r</mi><mi>m</mi><mi>i</mi><mi>n</mi><mi>i</mi><mi>s</mi><mi>m</mi><mo>:</mo></mrow><annotation encoding="application/x-tex">(CURDIR)/debian/strip-nondeterminism:</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mord mathnormal" style="margin-right:0.10903em;">U</span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mclose">)</span><span class="mord">/</span><span class="mord mathnormal">d</span><span class="mord mathnormal">e</span><span class="mord mathnormal">bian</span><span class="mord">/</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">i</span><span class="mord mathnormal">p</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">n</span><span class="mord mathnormal">o</span><span class="mord mathnormal">n</span><span class="mord mathnormal">d</span><span class="mord mathnormal">e</span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.02778em;">er</span><span class="mord mathnormal">mini</span><span class="mord mathnormal">s</span><span class="mord mathnormal">m</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>(PATH)

debian/control:

But please be aware that this does not work out of the box with pbuilder on Debian 7 Wheezy, see #778462 against faketime and #700591 against pbuilder (fixed in Jessie, but not Wheezy). Adding an according hook to/etc/pbuilder/hook.d which mounts /run/shm inside the chroot should suffice as local workaround, though.

TODO: document some other nicer options. Generally, all invocations ofdate(1) need to have a fixed TZ environment set.

NOTE: faketime BREAKS builds on some archs, for example hurd. See #778462 for details.

Setting the variable

Debian

In Debian, this is automatically set to the same time as the latest entry indebian/changelog, i.e. the same as the output of dpkg-parsechangelog -SDate.

  1. For packages using debhelper versions >= 9.20151004, this variable is automatically exported during builds, so you probably don’t need to change anything. One exception is if yourdebian/rules needs this variable in non-debhelper parts, in which case you can try (3) or (4).
  2. For packages using CDBS, versions >= 0.4.131 this variable is also exported automatically during builds, so no changes are needed.
  3. With dpkg >= 1.18.8 you can either include /usr/share/dpkg/pkg-info.mk or include /usr/share/dpkg/default.mk. This was added in Debian bug#824572.
  4. If none of the above options are good (you should have a ‘‘very good reason’’) then package maintainers may set and export this variable manually in debian/rules:
export SOURCE_DATE_EPOCH ?= $(shell dpkg-parsechangelog -STimestamp)

If you need/want to support dpkg versions earlier than 1.18.8:

export SOURCE_DATE_EPOCH ?= $(shell dpkg-parsechangelog -SDate | date -f- +%s)

If you need/want to support dpkg versions earlier than 1.17.0:

export SOURCE_DATE_EPOCH ?= $(shell dpkg-parsechangelog | grep -Po '^Date: \K.*' | date -f- +%s)

This snippet is believed to work on dpkg versions as far back as 2003.

Git

To set SOURCE_DATE_EPOCH to the last modification of a Git repository you can use git log. For example:

export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)

Tool support

We are persuading upstream tools to support SOURCE_DATE_EPOCHdirectly. You may help by writing patches for these tools; please add links to the bug reports here so we know, and to act as an example resource for future patch writers.

Complete:

Or you can search in all Debian sources.

More detailed discussion

Sometimes developers of build tools do not want to supportSOURCE_DATE_EPOCH, or they will tweak the suggestion to something related but different. We really do think the best approach is to useSOURCE_DATE_EPOCH exactly as-is described above in our proposal, without any variation. Here we explain our reasoning versus the arguments we have encountered.

(See Standard Environment Variables for general arguments.)

“Lying about the time” / “violates language spec”

This argument arises when the tool processes some input which contains a static instruction to the effect of “get_current_time()”. The input has a specification that defines what this means. The tool executes this instruction, then embeds the result in the output. It is argued thatSOURCE_DATE_EPOCH would break these semantics and violate the specification.

In most cases, this argument places too much weight on the absoluteness of time. Regardless of what any specification says, the user can set their own system clock and achieve an effect similar to SOURCE_DATE_EPOCH. Note: Setting the system clock is not enough for ‘‘reliable’’ reproducible builds

For all intents and purposes, if the user has set SOURCE_DATE_EPOCH then they are taking a position that “this is the current time; please use this instead of whatever clock you normally use”. Yes, the project developer wrote “get_current_time()” but I as the user, by settingSOURCE_DATE_EPOCH, am choosing to override this with my own idea of what time it is. Please execute the build as if the current time wasSOURCE_DATE_EPOCH. FOSS software should generally prefer to respect end-users’ wishes rather than developers’ wishes. (And in practise, we haven’t seen ‘‘any’’ instance where a project developer really really prefers “time of build” over “modtime of source”.)

In conclusion, the tool may choose to ignore SOURCE_DATE_EPOCH for other reasons, but to judge that this is a ‘‘lie’’ is to disrespect the user’s wishes. Furthermore, choosing to support this is unlikely to ‘‘actually’’ violate any specifications, since they generally don’t define “current”. This does not take into account, if the specification needs to interoperate consistently with other programs in a strong cryptographic ledger protocol where time values ‘‘must’’ be consistent across multiple entities. However this scenario is unlikely to apply, in the context of build tools where SOURCE_DATE_EPOCH would be set.)

Many tools allow the user to override the “current” date - e.g. -D__TIME__=xxx, \\year=yyy, etc. In these cases, it makes even less sense to ignore SOURCE_DATE_EPOCH for data integrity reasons - you ‘‘already’’ have a mechanism where the user can “lie” or “break the spec”;SOURCE_DATE_EPOCH would just be adding an extra mechanism that makes it easier to do this globally across all tools.

If for some reason you’re still conflicted on suddenly changing the meaning of your “now()” function and desire another switch other thanSOURCE_DATE_EPOCH being set or not, the texlive project came up with the variable FORCE_SOURCE_DATE; when that environment variable is set to 1cases that wouldn’t normally obey SOURCE_DATE_EPOCH will do. Westrongly discourage the usage of such variable; SOURCE_DATE_EPOCH is meant to be already a flag forcing a particular timestamp to be used.

OTOH, one alternative we can agree with, that also avoidsSOURCE_DATE_EPOCH, would be to translate the static instruction “get_current_time()” from the input format to ‘‘an equivalent instruction’’ in the output format, if the output format supports that.

Interaction of SOURCE_DATE_EPOCH with automatic rebuilds

When build dependencies change, it is sometimes necessary to rebuild a package without otherwise changing the source. This is especially the case when a security bug is embedded from the build dependency into the build output, as a rebuild is required to fix the security issue. There are, however, many are other situations. Whilst rebuilds of this kind are a manual process in many distributions, some do this automatically.

Debian only performs these rebuilds via a manual developer request called Binary Non-Maintainer Upload (binNMU). For this, SOURCE_DATE_EPOCH is incremented by 1 to avoid creating a situation where two build results contain files with the same name and the same modification timestamp but a different content. Not increasing the date breaks rsync without the--checksum option or indeed any transfer/sync/backup process that relies on the mtime of files to change to detect underlying content changes. While it is not generally safe to rely on modification times (and thus also not to backup systems without --checksum), we should not unnecessarily break things.

OpenSUSE performs such rebuilds automatically and also uses something called “build tree pruning”. Build tree pruning is to discard the output when it is the same and thus not trigger such rebuilds on packages that build depend on the discarded output. Normally this works, but if a build e.g. embedsSOURCE_DATE_EPOCH in its output, then the output changes every time such a rebuild happens, which can be very often. This is to be avoided as updating packages without necessity is too expensive. (The correct solution would, of course, be to never embed SOURCE_DATE_EPOCH as so far, no use-case was found were something else is not better, like instead embed a major version number. However convincing everyone and changing every instance of this is an impractical task.)

The following achieves both detecting rebuilds that did not change, despite a changed build dependency and having an incremented mtime on files in the output:

History and alternative proposals

1and the surrounding messages describe the initial motivation behind this, including an evaluation of how different programming languages handle date formats.

We do not have a proposal that includes anything resembling a “time zone”. Developing such a standard requires consideration of various issues:

Intuitive and naive ways of handling human-readable dates, such as the POSIX date functions, are highly flawed and freely mix implicit not-well-defined calendars with absolute time. For example, they don’t specify they mean the Gregorian calendar, and/or don’t specify what to do with dates before when the Gregorian calendar was introduced, or use named time zones that require an up-to-date timezone database (e.g. with historical DST definitions) to parse properly.

Since this is meant to be a universal standard that all tools and distributions can support, we need to keep things simple and precise, so that different groups of people cannot accidentally interpret it in different ways. So it is probably unwise to try to standardise anything that resembles a named time zone, since that is very very complex.

One likely candidate would be something similar to the git internal timestamp format, see man git-commit:

It is , where is the number of seconds since the UNIX epoch. is a positive or negative offset from UTC. For example CET (which is 2 hours ahead UTC) is +0200.

We already have SOURCE_DATE_EPOCH so the time zone offset could be placed in SOURCE_DATE_TZOFFSET or something like that. But all of this needs further discussion.

Other non-standard variables that we haven’t yet agreed upon, use at your own risk:

export SOURCE_DATE_TZOFFSET = $(shell dpkg-parsechangelog -SDate | tail -c6)
export SOURCE_DATE_RFC2822 = $(shell dpkg-parsechangelog -SDate)
export SOURCE_DATE_ISO8601 = $(shell python -c 'import time,email.utils,sys;t=email.utils.parsedate_tz(sys.argv[1]);\
print(time.strftime("%Y-%m-%dT%H:%M:%S",t[:-1])+"{0:+03d}{1:02d}".format(t[-1]/3600,t[-1]/60%60));' "$(SOURCE_DATE_RFC2822)")

The ISO8601 code snippet is complex, in order to preserve the same timezone offset as in the RFC2822 form. If one is OK with stripping out this offset, i.e. forcing to UTC, then one can just use date -u instead. However, this then contains the same information as the unix timestamp, but the latter is generally easier to work with in nearly all programming languages.


Documentation index