diff --git a/espanso-render/src/extension/date.rs b/espanso-render/src/extension/date.rs
new file mode 100644
index 0000000..d696144
--- /dev/null
+++ b/espanso-render/src/extension/date.rs
@@ -0,0 +1,120 @@
+/*
+ * This file is part of espanso.
+ *
+ * Copyright (C) 2019-2021 Federico Terzi
+ *
+ * espanso is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * espanso is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with espanso. If not, see .
+ */
+
+use chrono::{DateTime, Duration, Local};
+
+use crate::{Extension, ExtensionOutput, ExtensionResult, Number, Params, Value};
+
+pub struct DateExtension {
+ fixed_date: Option>,
+}
+
+#[allow(clippy::new_without_default)]
+impl DateExtension {
+ pub fn new() -> Self {
+ Self { fixed_date: None }
+ }
+}
+
+impl Extension for DateExtension {
+ fn name(&self) -> &str {
+ "date"
+ }
+
+ fn calculate(
+ &self,
+ _: &crate::Context,
+ _: &crate::Scope,
+ params: &Params,
+ ) -> crate::ExtensionResult {
+ let mut now = self.get_date();
+
+ // Compute the given offset
+ let offset = params.get("offset");
+ if let Some(Value::Number(Number::Integer(offset))) = offset {
+ let offset = Duration::seconds(*offset);
+ now = now + offset;
+ }
+
+ let format = params.get("format");
+
+ let date = if let Some(Value::String(format)) = format {
+ now.format(format).to_string()
+ } else {
+ now.to_rfc2822()
+ };
+
+ ExtensionResult::Success(ExtensionOutput::Single(date))
+ }
+}
+
+impl DateExtension {
+ fn get_date(&self) -> DateTime {
+ if let Some(fixed_date) = self.fixed_date {
+ fixed_date
+ } else {
+ Local::now()
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use chrono::offset::TimeZone;
+ use std::iter::FromIterator;
+
+ #[test]
+ fn date_formatted_correctly() {
+ let mut extension = DateExtension::new();
+ extension.fixed_date = Some(Local.ymd(2014, 7, 8).and_hms(9, 10, 11));
+
+ let param = Params::from_iter(
+ vec![("format".to_string(), Value::String("%H:%M:%S".to_string()))].into_iter(),
+ );
+ assert_eq!(
+ extension
+ .calculate(&Default::default(), &Default::default(), ¶m)
+ .into_success()
+ .unwrap(),
+ ExtensionOutput::Single("09:10:11".to_string())
+ );
+ }
+
+ #[test]
+ fn offset_works_correctly() {
+ let mut extension = DateExtension::new();
+ extension.fixed_date = Some(Local.ymd(2014, 7, 8).and_hms(9, 10, 11));
+
+ let param = Params::from_iter(
+ vec![
+ ("format".to_string(), Value::String("%H:%M:%S".to_string())),
+ ("offset".to_string(), Value::Number(Number::Integer(3600))),
+ ]
+ .into_iter(),
+ );
+ assert_eq!(
+ extension
+ .calculate(&Default::default(), &Default::default(), ¶m)
+ .into_success()
+ .unwrap(),
+ ExtensionOutput::Single("10:10:11".to_string())
+ );
+ }
+}