build_info_build/build_script_options/
timestamp.rs

1use chrono::{DateTime, TimeZone, Utc};
2
3impl crate::BuildScriptOptions {
4	/// Set the build timestamp by hand.
5	///
6	/// This is mostly important for reproducible builds using only cargo. If possible, consider setting the environment
7	/// variable [`SOURCE_DATE_EPOCH`](https://reproducible-builds.org/specs/source-date-epoch/) instead, which does not
8	/// require any setup.
9	pub fn build_timestamp(mut self, timestamp: DateTime<Utc>) -> Self {
10		self.timestamp = Some(timestamp);
11		self
12	}
13
14	/// Set the build timestamp by hand as nanosecond-precise UNIX timestamp.
15	///
16	/// This is mostly important for reproducible builds using only cargo. If possible, consider setting the environment
17	/// variable [`SOURCE_DATE_EPOCH`](https://reproducible-builds.org/specs/source-date-epoch/) instead, which does not
18	/// require any setup.
19	pub fn build_timestamp_as_nanos(mut self, nanos: i64) -> Self {
20		self.timestamp = Some(Utc.timestamp_nanos(nanos));
21		self
22	}
23}
24
25pub(crate) fn get_timestamp() -> DateTime<Utc> {
26	get_timestamp_internal(std::env::var("SOURCE_DATE_EPOCH").ok())
27}
28
29fn get_timestamp_internal(epoch: Option<String>) -> DateTime<Utc> {
30	// https://reproducible-builds.org/specs/source-date-epoch/
31	if let Some(epoch) = epoch {
32		let epoch: i64 = epoch.parse().expect("Could not parse SOURCE_DATE_EPOCH");
33		match Utc.timestamp_opt(epoch, 0) {
34			chrono::LocalResult::None => panic!("Invalid SOURCE_DATE_EPOCH: {epoch}"),
35			chrono::LocalResult::Single(timestamp) => timestamp,
36			chrono::LocalResult::Ambiguous(min, max) => {
37				panic!("Ambiguous epoch: {epoch} could refer to {min} or {max}. This should never occur for UTC!")
38			}
39		}
40	} else {
41		Utc::now()
42	}
43}
44
45#[cfg(test)]
46mod test {
47	use pretty_assertions::assert_eq;
48
49	use super::*;
50
51	#[test]
52	fn get_current_timestamp() {
53		let past = Utc.timestamp_opt(1591113000, 0).single().unwrap();
54		let now = get_timestamp_internal(None);
55		let future = Utc.timestamp_opt(32503680000, 0).single().unwrap();
56		assert!(past < now);
57		assert!(now < future);
58	}
59
60	#[test]
61	fn get_fixed_timestamp() {
62		let epoch = 1591113000;
63		assert_eq!(
64			get_timestamp_internal(Some(epoch.to_string())),
65			Utc.timestamp_opt(epoch, 0).single().unwrap()
66		);
67	}
68}