summaryrefslogtreecommitdiff
path: root/crates/typst-cli/src/world.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-cli/src/world.rs')
-rw-r--r--crates/typst-cli/src/world.rs48
1 files changed, 36 insertions, 12 deletions
diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs
index 4e0bcd54..e0215130 100644
--- a/crates/typst-cli/src/world.rs
+++ b/crates/typst-cli/src/world.rs
@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use std::{fmt, fs, io, mem};
-use chrono::{DateTime, Datelike, Local};
+use chrono::{DateTime, Datelike, FixedOffset, Local, Utc};
use comemo::Prehashed;
use ecow::{eco_format, EcoString};
use once_cell::sync::Lazy;
@@ -42,8 +42,9 @@ pub struct SystemWorld {
/// Maps file ids to source files and buffers.
slots: Mutex<HashMap<FileId, FileSlot>>,
/// The current datetime if requested. This is stored here to ensure it is
- /// always the same within one compilation. Reset between compilations.
- now: OnceLock<DateTime<Local>>,
+ /// always the same within one compilation.
+ /// Reset between compilations if not [`Now::Fixed`].
+ now: Now,
/// The export cache, used for caching output files in `typst watch`
/// sessions.
export_cache: ExportCache,
@@ -104,6 +105,11 @@ impl SystemWorld {
let mut searcher = FontSearcher::new();
searcher.search(&command.font_paths);
+ let now = match command.source_date_epoch {
+ Some(time) => Now::Fixed(time),
+ None => Now::System(OnceLock::new()),
+ };
+
Ok(Self {
workdir: std::env::current_dir().ok(),
root,
@@ -112,7 +118,7 @@ impl SystemWorld {
book: Prehashed::new(searcher.book),
fonts: searcher.fonts,
slots: Mutex::new(HashMap::new()),
- now: OnceLock::new(),
+ now,
export_cache: ExportCache::new(),
})
}
@@ -146,7 +152,9 @@ impl SystemWorld {
for slot in self.slots.get_mut().values_mut() {
slot.reset();
}
- self.now.take();
+ if let Now::System(time_lock) = &mut self.now {
+ time_lock.take();
+ }
}
/// Lookup a source file by id.
@@ -187,17 +195,24 @@ impl World for SystemWorld {
}
fn today(&self, offset: Option<i64>) -> Option<Datetime> {
- let now = self.now.get_or_init(chrono::Local::now);
+ let now = match &self.now {
+ Now::Fixed(time) => time,
+ Now::System(time) => time.get_or_init(Utc::now),
+ };
- let naive = match offset {
- None => now.naive_local(),
- Some(o) => now.naive_utc() + chrono::Duration::try_hours(o)?,
+ // The time with the specified UTC offset, or within the local time zone.
+ let with_offset = match offset {
+ None => now.with_timezone(&Local).fixed_offset(),
+ Some(hours) => {
+ let seconds = i32::try_from(hours).ok()?.checked_mul(3600)?;
+ now.with_timezone(&FixedOffset::east_opt(seconds)?)
+ }
};
Datetime::from_ymd(
- naive.year(),
- naive.month().try_into().ok()?,
- naive.day().try_into().ok()?,
+ with_offset.year(),
+ with_offset.month().try_into().ok()?,
+ with_offset.day().try_into().ok()?,
)
}
}
@@ -384,6 +399,15 @@ fn decode_utf8(buf: &[u8]) -> FileResult<&str> {
Ok(std::str::from_utf8(buf.strip_prefix(b"\xef\xbb\xbf").unwrap_or(buf))?)
}
+/// The current date and time.
+enum Now {
+ /// The date and time if the environment `SOURCE_DATE_EPOCH` is set.
+ /// Used for reproducible builds.
+ Fixed(DateTime<Utc>),
+ /// The current date and time if the time is not externally fixed.
+ System(OnceLock<DateTime<Utc>>),
+}
+
/// An error that occurs during world construction.
#[derive(Debug)]
pub enum WorldCreationError {