source-date-epoch.md 18.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
---
title: SOURCE_DATE_EPOCH
layout: docs
permalink: /docs/source-date-epoch/
---

[S_D_E (for short) is a standard](https://reproducible-builds.org/specs/source-date-epoch/) that defines an environment variable `SOURCE_DATE_EPOCH` that distributions can set centrally, and have build tools consume this in order to produce reproducible output.

Before implementing this, you should scan through [our checklist](https://wiki.debian.org/ReproducibleBuilds/StandardEnvironmentVariables#Checklist) to see if you can avoid implementing it.

If you find that it's ideal for your use-case, please feel free to jump straight to our **[published specification](https://reproducible-builds.org/specs/source-date-epoch/)**.

## Proposal

Please read our [SOURCE_DATE_EPOCH specification](https://reproducible-builds.org/specs/source-date-epoch/) for details.

See [Standard Environment Variables](https://wiki.debian.org/ReproducibleBuilds/StandardEnvironmentVariables) for more detailed discussion of the rationales behind this mechanism.

Below we also have [[#More_detailed_discussion|more detailed discussion]] about this specific variable, as well as documentation on [[#history-and-alternatives|history and alternative proposals]].

## Setting the variable

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

 1. For packages using debhelper, versions >= 9.20151004 (Bug:791823) exports this variable during builds, so you probably don't need to change anything. One exception is if your `debian/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 (Bug:794241) exports this variable during builds, so no changes are needed.

 3. With dpkg >= 1.18.8 (Bug:824572) you can either `include /usr/share/dpkg/pkg-info.mk` or `include /usr/share/dpkg/default.mk`.

 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.

## Reading the variable

We are persuading upstream tools to support this directly. 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.

55
 Pending:: [gettext](https://bugs.debian.org/792687), [qt4-x11](https://bugs.debian.org/794681)
56
 Complete::
57
  * [cmake](https://gitlab.kitware.com/cmake/cmake/merge_requests/432) (>= 3.8.0)
58
  * [debhelper](https://bugs.debian.org/791823) (>= 9.20151004)
59 60 61 62 63 64 65 66 67
  * [docbook-utils](https://bugs.debian.org/800797) (Debian >= 0.6.14-3.1, upstream TODO)
  * [doxygen](https://bugs.debian.org/792201) (>= [1.8.12](https://github.com/doxygen/doxygen/commit/9a2c7bbfb0c53b4532db7280e6804c7ce76d70a3), Debian pending)
  * [epydoc](https://bugs.debian.org/790899) (>= 3.0.1+dfsg-8, upstream [pending](https://sourceforge.net/p/epydoc/bugs/368/))
  * [gcc](https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=e3e8c48c4a494d9da741c1c8ea6c4c0b7c4ff934) (>= 7, Debian >= 5.3.1-17, Debian >= 6.1.1-1)
  * [ghostscript](https://bugs.debian.org/794004) (Debian >= 9.16~dfsg-1, upstream [unfortunately rejected](http://bugs.ghostscript.com/show_bug.cgi?id=696765) due to additional constraints they have)
  * [groff](https://bugs.debian.org/762854) (Debian >= 1.22.3-2, upstream [pending](https://lists.gnu.org/archive/html/groff/2015-11/msg00038.html))
  * [help2man](https://bugs.debian.org/787444) (>= 1.47.1)
  * [libxslt](https://bugs.debian.org/791815) (>= [1.1.29](https://bugzilla.gnome.org/show_bug.cgi?id=758148), Debian >= 1.1.28-3)
  * [man2html](https://bugs.debian.org/796130) (Debian >= 1.6g-8, [needs forwarding](https://sources.debian.net/src/man2html/1.6g-8/debian/patches/035-source-date-epoch.patch/))
68
  * [mkdocs](https://bugs.debian.org/824266) (>= [0.16](https://github.com/mkdocs/mkdocs/pull/939/commits/8b006bd7fda55e47e29412896c511c7244398f82), Debian pending)
69 70 71 72 73 74 75
  * [ocamldoc](https://bugs.debian.org/794586) (>= [4.03.0](https://github.com/ocaml/ocaml/commit/0319173b7d02008e4ce6b81dceaf7c32cf5f8a6f), Debian >= 4.02.3-1)
  * [pydoctor](https://bugs.debian.org/807166) (>= 0.5+git20151204)
  * [rpm upstream](https://github.com/rpm-software-management/rpm/pull/144) (>4.13 other relevant patches linked in there)
  * [sphinx](https://github.com/sphinx-doc/sphinx/pull/1954) (>= 1.4, Debian >= 1.3.1-3)
  * [texi2html](https://bugs.debian.org/783475) (Debian >= 1.82+dfsg1-4, [needs forwarding](https://sources.debian.net/src/texi2html/1.82%2Bdfsg1-5/debian/patches/05_reproducible-build/))
  * [texlive-bin](https://bugs.debian.org/792202) (>= 2016.20160512.41045)
  * [txt2man](https://bugs.debian.org/790801) (>= [1.5.7](https://github.com/mvertes/txt2man/pull/1), Debian >= 1.5.6-4)
76
  * [rcc](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=894476) (Qt5 >= [5.13.0](https://code.qt.io/cgit/qt/qtbase.git/commit/?id=1ffcca4cc208c48ddb06b6a23abf1756f9724351), Debian TODO)
77

78
Or search in all Debian sources: <https://codesearch.debian.net/search?perpkg=1&q=SOURCE_DATE_EPOCH>
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118

### Python

```
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 also support BSD date as well:

```
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 $date = strftime("%Y-%m-%d", gmtime($ENV{SOURCE_DATE_EPOCH} || time));
```

### Makefile

```
DATE_FMT = %Y-%m-%d
ifdef SOURCE_DATE_EPOCH
119
    BUILD_DATE ?= $(shell 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)")
120 121 122 123 124 125 126 127 128 129 130 131 132
else
    BUILD_DATE ?= $(shell date "+$(DATE_FMT)")
endif
```

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

### CMake

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

133 134
works with cmake >= 2.8.11 , but is only reproducible with cmake >= 3.8.0.
If you do not have a modern cmake, but need reproducibility, you can use the less preferred variant:
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156

```
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 ()
```

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

### C

```
#include <errno.h>
#include <limits.h>
157
#include <stdlib.h>
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192

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);
```

193 194 195 196 197 198 199 200 201 202 203 204
If you want less verbose code and are happy with the assumtion, that the variable will contain a correct, positive integer in the `time_t` range, you can use

```
#include <stdlib.h>

time_t now;
char *source_date_epoch;
if ((source_date_epoch = getenv("SOURCE_DATE_EPOCH")) == NULL ||
    (now = (time_t)strtoll(source_date_epoch, NULL, 10)) <= 0)
        time(&now);
```

205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
### 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)
}
```

### git repository

to set SOURCE_DATE_EPOCH to the last modification of a git repository, in shell:

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

### PHP

```
263
\date('Y', (int)\getenv('SOURCE_DATE_EPOCH') ?: \time())
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
```

### Emacs-Lisp

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

### Javascript / node.js

```
var timestamp = new Date(process.env.SOURCE_DATE_EPOCH ? (process.env.SOURCE_DATE_EPOCH * 1000) : new Date().getTime());

// Alternatively, to ensure a fixed timezone:

var now = new Date();
if (process.env.SOURCE_DATE_EPOCH) {
  now = new Date((process.env.SOURCE_DATE_EPOCH * 1000) + (now.getTimezoneOffset() * 60000));
}
```


### 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.

### 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 := $(CURDIR)/debian/strip-nondeterminism:$(PATH)
```

`debian/control`:

```
Build-Depends: faketime
```

But please be aware that this does not work out of the box with pbuilder on Debian 7 Wheezy, see [#778462](https://bugs.debian.org/778462) against faketime and [#700591](https://bugs.debian.org/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 of `date(1)` need to have a fixed `TZ` environment set.

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

## More detailed discussion

Sometimes developers of build tools do not want to support `SOURCE_DATE_EPOCH`, or they will tweak the suggestion to something related but different. We really do think the best approach is to use `SOURCE_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 [[ReproducibleBuilds/StandardEnvironmentVariables#more-detailed-discussion|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 that `SOURCE_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 - we need `SOURCE_DATE_EPOCH` for long-running build processes that take varying amounts of time. If the tool was run near the end of the process, then merely setting the system clock would not make timestamps here reproducible. It is not up to the tool to judge whether the user is lying with their system clock, and likewise, it is not up to the tool to judge whether `SOURCE_DATE_EPOCH` is a "lie" or not.

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 setting `SOURCE_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 was `SOURCE_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 than `SOURCE_DATE_EPOCH` being set or not, the `texlive` project came up with the variable `FORCE_SOURCE_DATE`; when that environment variable is set to `1` cases that wouldn't normally obey `SOURCE_DATE_EPOCH` will do.  We **strongly 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 avoids `SOURCE_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.

## History and alternative proposals

[1](https://lists.alioth.debian.org/pipermail/reproducible-builds/Week-of-Mon-20150608/001823.html) and the surrounding messages describe the initial motivation behind this, including an evaluation of how different programming languages handle date formats.

At present, 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 <unix timestamp> <time zone offset>, where <unix timestamp> is the number of seconds since the UNIX epoch.  <time zone offset> 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:

```bash
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.