mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Duration type, simple date-duration-calculations and comparisons (#1843)
This commit is contained in:
parent
35c785ea11
commit
f616302496
@ -3,7 +3,7 @@ use std::str::FromStr;
|
||||
|
||||
use time::{Month, PrimitiveDateTime};
|
||||
|
||||
use typst::eval::{Bytes, Datetime, Module, Plugin, Reflect, Regex};
|
||||
use typst::eval::{Bytes, Datetime, Duration, Module, Plugin, Reflect, Regex};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
@ -334,6 +334,54 @@ pub fn datetime_today(
|
||||
.ok_or("unable to get the current date")?)
|
||||
}
|
||||
|
||||
/// Creates a new duration.
|
||||
///
|
||||
/// You can specify the [duration]($type/duration) using weeks, days, hours,
|
||||
/// minutes and seconds. You can also get a duration by subtracting two
|
||||
/// [datetimes]($type/datetime).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```example
|
||||
/// #duration(
|
||||
/// days: 3,
|
||||
/// hours: 12,
|
||||
/// ).hours()
|
||||
/// ```
|
||||
///
|
||||
/// Display: Duration
|
||||
/// Category: construct
|
||||
#[func]
|
||||
pub fn duration(
|
||||
/// The number of seconds.
|
||||
#[named]
|
||||
#[default(0)]
|
||||
seconds: i64,
|
||||
/// The number of minutes.
|
||||
#[named]
|
||||
#[default(0)]
|
||||
minutes: i64,
|
||||
/// The number of hours.
|
||||
#[named]
|
||||
#[default(0)]
|
||||
hours: i64,
|
||||
/// The number of days.
|
||||
#[named]
|
||||
#[default(0)]
|
||||
days: i64,
|
||||
/// The number of weeks.
|
||||
#[named]
|
||||
#[default(0)]
|
||||
weeks: i64,
|
||||
) -> Duration {
|
||||
Duration::from(
|
||||
time::Duration::seconds(seconds)
|
||||
+ time::Duration::minutes(minutes)
|
||||
+ time::Duration::hours(hours)
|
||||
+ time::Duration::days(days)
|
||||
+ time::Duration::weeks(weeks),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a CMYK color.
|
||||
///
|
||||
/// This is useful if you want to target a specific printer. The conversion
|
||||
|
@ -25,6 +25,7 @@ pub(super) fn define(global: &mut Scope) {
|
||||
global.define("cmyk", cmyk_func());
|
||||
global.define("color", color_module());
|
||||
global.define("datetime", datetime_func());
|
||||
global.define("duration", duration_func());
|
||||
global.define("symbol", symbol_func());
|
||||
global.define("str", str_func());
|
||||
global.define("bytes", bytes_func());
|
||||
|
@ -1,17 +1,20 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::hash::Hash;
|
||||
use std::ops::{Add, Sub};
|
||||
|
||||
use ecow::{eco_format, EcoString, EcoVec};
|
||||
use time::error::{Format, InvalidFormatDescription};
|
||||
use time::{format_description, PrimitiveDateTime};
|
||||
|
||||
use crate::eval::cast;
|
||||
use crate::diag::bail;
|
||||
use crate::eval::{Duration, StrResult};
|
||||
use crate::util::pretty_array_like;
|
||||
|
||||
/// A datetime object that represents either a date, a time or a combination of
|
||||
/// both.
|
||||
#[derive(Clone, Copy, PartialEq, Hash)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Datetime {
|
||||
/// Representation as a date.
|
||||
Date(time::Date),
|
||||
@ -22,6 +25,15 @@ pub enum Datetime {
|
||||
}
|
||||
|
||||
impl Datetime {
|
||||
/// Which kind of variant this datetime stores.
|
||||
pub fn kind(&self) -> &'static str {
|
||||
match self {
|
||||
Datetime::Datetime(_) => "datetime",
|
||||
Datetime::Date(_) => "date",
|
||||
Datetime::Time(_) => "time",
|
||||
}
|
||||
}
|
||||
|
||||
/// Display the date and/or time in a certain format.
|
||||
pub fn display(&self, pattern: Option<EcoString>) -> Result<EcoString, EcoString> {
|
||||
let pattern = pattern.as_ref().map(EcoString::as_str).unwrap_or(match self {
|
||||
@ -106,6 +118,15 @@ impl Datetime {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the ordinal (day of the year), if existing.
|
||||
pub fn ordinal(&self) -> Option<u16> {
|
||||
match self {
|
||||
Datetime::Datetime(datetime) => Some(datetime.ordinal()),
|
||||
Datetime::Date(date) => Some(date.ordinal()),
|
||||
Datetime::Time(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a datetime from year, month, and day.
|
||||
pub fn from_ymd(year: i32, month: u8, day: u8) -> Option<Self> {
|
||||
Some(Datetime::Date(
|
||||
@ -136,6 +157,56 @@ impl Datetime {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Datetime {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match (self, other) {
|
||||
(Datetime::Datetime(a), Datetime::Datetime(b)) => a.partial_cmp(b),
|
||||
(Datetime::Date(a), Datetime::Date(b)) => a.partial_cmp(b),
|
||||
(Datetime::Time(a), Datetime::Time(b)) => a.partial_cmp(b),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Duration> for Datetime {
|
||||
type Output = Datetime;
|
||||
|
||||
fn add(self, rhs: Duration) -> Self::Output {
|
||||
let rhs: time::Duration = rhs.into();
|
||||
match self {
|
||||
Datetime::Datetime(datetime) => Self::Datetime(datetime + rhs),
|
||||
Datetime::Date(date) => Self::Date(date + rhs),
|
||||
Datetime::Time(time) => Self::Time(time + rhs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Duration> for Datetime {
|
||||
type Output = Datetime;
|
||||
|
||||
fn sub(self, rhs: Duration) -> Self::Output {
|
||||
let rhs: time::Duration = rhs.into();
|
||||
match self {
|
||||
Datetime::Datetime(datetime) => Self::Datetime(datetime - rhs),
|
||||
Datetime::Date(date) => Self::Date(date - rhs),
|
||||
Datetime::Time(time) => Self::Time(time - rhs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Datetime {
|
||||
type Output = StrResult<Duration>;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Datetime::Datetime(a), Datetime::Datetime(b)) => Ok((a - b).into()),
|
||||
(Datetime::Date(a), Datetime::Date(b)) => Ok((a - b).into()),
|
||||
(Datetime::Time(a), Datetime::Time(b)) => Ok((a - b).into()),
|
||||
(a, b) => bail!("cannot subtract {} from {}", b.kind(), a.kind()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Datetime {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let year = self.year().map(|y| eco_format!("year: {y}"));
|
||||
@ -153,10 +224,6 @@ impl Debug for Datetime {
|
||||
}
|
||||
}
|
||||
|
||||
cast! {
|
||||
type Datetime: "datetime",
|
||||
}
|
||||
|
||||
/// Format the `Format` error of the time crate in an appropriate way.
|
||||
fn format_time_format_error(error: Format) -> EcoString {
|
||||
match error {
|
||||
|
140
crates/typst/src/eval/duration.rs
Normal file
140
crates/typst/src/eval/duration.rs
Normal file
@ -0,0 +1,140 @@
|
||||
use crate::util::pretty_array_like;
|
||||
use ecow::eco_format;
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::ops::{Add, Div, Mul, Neg, Sub};
|
||||
use time::ext::NumericalDuration;
|
||||
|
||||
/// Represents a positive or negative span of time.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Duration(time::Duration);
|
||||
|
||||
impl Duration {
|
||||
/// Whether the duration is empty / zero.
|
||||
pub fn is_zero(&self) -> bool {
|
||||
self.0.is_zero()
|
||||
}
|
||||
|
||||
/// The duration expressed in seconds.
|
||||
pub fn seconds(&self) -> f64 {
|
||||
self.0.as_seconds_f64()
|
||||
}
|
||||
|
||||
/// The duration expressed in minutes.
|
||||
pub fn minutes(&self) -> f64 {
|
||||
self.seconds() / 60.0
|
||||
}
|
||||
|
||||
/// The duration expressed in hours.
|
||||
pub fn hours(&self) -> f64 {
|
||||
self.seconds() / 3_600.0
|
||||
}
|
||||
|
||||
/// The duration expressed in days.
|
||||
pub fn days(&self) -> f64 {
|
||||
self.seconds() / 86_400.0
|
||||
}
|
||||
|
||||
/// The duration expressed in weeks.
|
||||
pub fn weeks(&self) -> f64 {
|
||||
self.seconds() / 604_800.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<time::Duration> for Duration {
|
||||
fn from(value: time::Duration) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Duration {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let mut tmp = self.0;
|
||||
let mut vec = Vec::with_capacity(5);
|
||||
|
||||
let weeks = tmp.whole_seconds() / 604_800.0 as i64;
|
||||
if weeks != 0 {
|
||||
vec.push(eco_format!("weeks: {weeks}"));
|
||||
}
|
||||
tmp -= weeks.weeks();
|
||||
|
||||
let days = tmp.whole_days();
|
||||
if days != 0 {
|
||||
vec.push(eco_format!("days: {days}"));
|
||||
}
|
||||
tmp -= days.days();
|
||||
|
||||
let hours = tmp.whole_hours();
|
||||
if hours != 0 {
|
||||
vec.push(eco_format!("hours: {hours}"));
|
||||
}
|
||||
tmp -= hours.hours();
|
||||
|
||||
let minutes = tmp.whole_minutes();
|
||||
if minutes != 0 {
|
||||
vec.push(eco_format!("minutes: {minutes}"));
|
||||
}
|
||||
tmp -= minutes.minutes();
|
||||
|
||||
let seconds = tmp.whole_seconds();
|
||||
if seconds != 0 {
|
||||
vec.push(eco_format!("seconds: {seconds}"));
|
||||
}
|
||||
|
||||
write!(f, "duration{}", &pretty_array_like(&vec, false))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Duration> for time::Duration {
|
||||
fn from(value: Duration) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Duration(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Duration(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
fn neg(self) -> Self::Output {
|
||||
Duration(-self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
fn mul(self, rhs: f64) -> Self::Output {
|
||||
Duration(self.0 * rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Duration {
|
||||
type Output = Duration;
|
||||
|
||||
fn div(self, rhs: f64) -> Self::Output {
|
||||
Duration(self.0 / rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for Duration {
|
||||
type Output = f64;
|
||||
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
self.0 / rhs.0
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ use ecow::{eco_format, EcoString};
|
||||
|
||||
use super::{Args, Bytes, IntoValue, Plugin, Str, Value, Vm};
|
||||
use crate::diag::{At, Hint, SourceResult};
|
||||
use crate::eval::{bail, Datetime};
|
||||
use crate::eval::bail;
|
||||
use crate::geom::{Align, Axes, Color, Dir, Em, GenAlign};
|
||||
use crate::model::{Location, Selector};
|
||||
use crate::syntax::Span;
|
||||
@ -107,6 +107,28 @@ pub fn call(
|
||||
_ => return missing(),
|
||||
},
|
||||
|
||||
Value::Datetime(datetime) => match method {
|
||||
"display" => datetime.display(args.eat()?).at(args.span)?.into_value(),
|
||||
"year" => datetime.year().into_value(),
|
||||
"month" => datetime.month().into_value(),
|
||||
"weekday" => datetime.weekday().into_value(),
|
||||
"day" => datetime.day().into_value(),
|
||||
"hour" => datetime.hour().into_value(),
|
||||
"minute" => datetime.minute().into_value(),
|
||||
"second" => datetime.second().into_value(),
|
||||
"ordinal" => datetime.ordinal().into_value(),
|
||||
_ => return missing(),
|
||||
},
|
||||
|
||||
Value::Duration(duration) => match method {
|
||||
"seconds" => duration.seconds().into_value(),
|
||||
"minutes" => duration.minutes().into_value(),
|
||||
"hours" => duration.hours().into_value(),
|
||||
"days" => duration.days().into_value(),
|
||||
"weeks" => duration.weeks().into_value(),
|
||||
_ => return missing(),
|
||||
},
|
||||
|
||||
Value::Content(content) => match method {
|
||||
"func" => content.func().into_value(),
|
||||
"has" => content.has(&args.expect::<EcoString>("field")?).into_value(),
|
||||
@ -257,20 +279,6 @@ pub fn call(
|
||||
}
|
||||
_ => return missing(),
|
||||
}
|
||||
} else if let Some(&datetime) = dynamic.downcast::<Datetime>() {
|
||||
match method {
|
||||
"display" => {
|
||||
datetime.display(args.eat()?).at(args.span)?.into_value()
|
||||
}
|
||||
"year" => datetime.year().into_value(),
|
||||
"month" => datetime.month().into_value(),
|
||||
"weekday" => datetime.weekday().into_value(),
|
||||
"day" => datetime.day().into_value(),
|
||||
"hour" => datetime.hour().into_value(),
|
||||
"minute" => datetime.minute().into_value(),
|
||||
"second" => datetime.second().into_value(),
|
||||
_ => return missing(),
|
||||
}
|
||||
} else if let Some(direction) = dynamic.downcast::<Dir>() {
|
||||
match method {
|
||||
"axis" => direction.axis().description().into_value(),
|
||||
@ -426,6 +434,24 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
|
||||
("trim", true),
|
||||
],
|
||||
"bytes" => &[("len", false), ("at", true), ("slice", true)],
|
||||
"datetime" => &[
|
||||
("display", true),
|
||||
("year", false),
|
||||
("month", false),
|
||||
("weekday", false),
|
||||
("day", false),
|
||||
("hour", false),
|
||||
("minute", false),
|
||||
("second", false),
|
||||
("ordinal", false),
|
||||
],
|
||||
"duration" => &[
|
||||
("seconds", false),
|
||||
("minutes", false),
|
||||
("hours", false),
|
||||
("days", false),
|
||||
("weeks", false),
|
||||
],
|
||||
"content" => &[
|
||||
("func", false),
|
||||
("has", true),
|
||||
|
@ -16,6 +16,7 @@ mod args;
|
||||
mod auto;
|
||||
mod bytes;
|
||||
mod datetime;
|
||||
mod duration;
|
||||
mod fields;
|
||||
mod func;
|
||||
mod int;
|
||||
@ -48,6 +49,7 @@ pub use self::cast::{
|
||||
};
|
||||
pub use self::datetime::Datetime;
|
||||
pub use self::dict::{dict, Dict};
|
||||
pub use self::duration::Duration;
|
||||
pub use self::fields::fields_on;
|
||||
pub use self::func::{Func, FuncInfo, NativeFunc, ParamInfo};
|
||||
pub use self::library::{set_lang_items, LangItems, Library};
|
||||
|
@ -62,6 +62,7 @@ pub fn neg(value: Value) -> StrResult<Value> {
|
||||
Ratio(v) => Ratio(-v),
|
||||
Relative(v) => Relative(-v),
|
||||
Fraction(v) => Fraction(-v),
|
||||
Duration(v) => Duration(-v),
|
||||
v => mismatch!("cannot apply '-' to {}", v),
|
||||
})
|
||||
}
|
||||
@ -115,6 +116,10 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
})
|
||||
}
|
||||
|
||||
(Duration(a), Duration(b)) => Duration(a + b),
|
||||
(Datetime(a), Duration(b)) => Datetime(a + b),
|
||||
(Duration(a), Datetime(b)) => Datetime(b + a),
|
||||
|
||||
(Dyn(a), Dyn(b)) => {
|
||||
// 1D alignments can be summed into 2D alignments.
|
||||
if let (Some(&a), Some(&b)) =
|
||||
@ -161,6 +166,10 @@ pub fn sub(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
|
||||
(Fraction(a), Fraction(b)) => Fraction(a - b),
|
||||
|
||||
(Duration(a), Duration(b)) => Duration(a - b),
|
||||
(Datetime(a), Duration(b)) => Datetime(a - b),
|
||||
(Datetime(a), Datetime(b)) => Duration((a - b)?),
|
||||
|
||||
(a, b) => mismatch!("cannot subtract {1} from {0}", a, b),
|
||||
})
|
||||
}
|
||||
@ -214,6 +223,11 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
(Content(a), b @ Int(_)) => Content(a.repeat(b.cast()?)),
|
||||
(a @ Int(_), Content(b)) => Content(b.repeat(a.cast()?)),
|
||||
|
||||
(Int(a), Duration(b)) => Duration(b * (a as f64)),
|
||||
(Float(a), Duration(b)) => Duration(b * a),
|
||||
(Duration(a), Int(b)) => Duration(a * (b as f64)),
|
||||
(Duration(a), Float(b)) => Duration(a * b),
|
||||
|
||||
(a, b) => mismatch!("cannot multiply {} with {}", a, b),
|
||||
})
|
||||
}
|
||||
@ -254,6 +268,10 @@ pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
(Fraction(a), Float(b)) => Fraction(a / b),
|
||||
(Fraction(a), Fraction(b)) => Float(a / b),
|
||||
|
||||
(Duration(a), Int(b)) => Duration(a / (b as f64)),
|
||||
(Duration(a), Float(b)) => Duration(a / b),
|
||||
(Duration(a), Duration(b)) => Float(a / b),
|
||||
|
||||
(a, b) => mismatch!("cannot divide {} by {}", a, b),
|
||||
})
|
||||
}
|
||||
@ -268,6 +286,7 @@ fn is_zero(v: &Value) -> bool {
|
||||
Ratio(v) => v.is_zero(),
|
||||
Relative(v) => v.is_zero(),
|
||||
Fraction(v) => v.is_zero(),
|
||||
Duration(v) => v.is_zero(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -357,6 +376,8 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool {
|
||||
(Func(a), Func(b)) => a == b,
|
||||
(Args(a), Args(b)) => a == b,
|
||||
(Module(a), Module(b)) => a == b,
|
||||
(Datetime(a), Datetime(b)) => a == b,
|
||||
(Duration(a), Duration(b)) => a == b,
|
||||
(Dyn(a), Dyn(b)) => a == b,
|
||||
|
||||
// Some technically different things should compare equal.
|
||||
@ -392,6 +413,9 @@ pub fn compare(lhs: &Value, rhs: &Value) -> StrResult<Ordering> {
|
||||
(Relative(a), Length(b)) if a.rel.is_zero() => try_cmp_values(&a.abs, b)?,
|
||||
(Relative(a), Ratio(b)) if a.abs.is_zero() => a.rel.cmp(b),
|
||||
|
||||
(Duration(a), Duration(b)) => a.cmp(b),
|
||||
(Datetime(a), Datetime(b)) => try_cmp_datetimes(a, b)?,
|
||||
|
||||
_ => mismatch!("cannot compare {} and {}", lhs, rhs),
|
||||
})
|
||||
}
|
||||
@ -402,6 +426,12 @@ fn try_cmp_values<T: PartialOrd + Debug>(a: &T, b: &T) -> StrResult<Ordering> {
|
||||
.ok_or_else(|| eco_format!("cannot compare {:?} with {:?}", a, b))
|
||||
}
|
||||
|
||||
/// Try to compare two datetimes.
|
||||
fn try_cmp_datetimes(a: &super::Datetime, b: &super::Datetime) -> StrResult<Ordering> {
|
||||
a.partial_cmp(b)
|
||||
.ok_or_else(|| eco_format!("cannot compare {} and {}", a.kind(), b.kind()))
|
||||
}
|
||||
|
||||
/// Test whether one value is "in" another one.
|
||||
pub fn in_(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
if let Some(b) = contains(&lhs, &rhs) {
|
||||
|
@ -10,6 +10,7 @@ use serde::de::{Error, MapAccess, SeqAccess, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use siphasher::sip128::{Hasher128, SipHasher13};
|
||||
use time::macros::format_description;
|
||||
use typst::eval::Duration;
|
||||
|
||||
use super::{
|
||||
cast, fields, format_str, ops, Args, Array, Bytes, CastInfo, Content, Dict,
|
||||
@ -55,6 +56,10 @@ pub enum Value {
|
||||
Bytes(Bytes),
|
||||
/// A label: `<intro>`.
|
||||
Label(Label),
|
||||
/// A datetime
|
||||
Datetime(Datetime),
|
||||
/// A duration
|
||||
Duration(Duration),
|
||||
/// A content value: `[*Hi* there]`.
|
||||
Content(Content),
|
||||
// Content styles.
|
||||
@ -116,6 +121,8 @@ impl Value {
|
||||
Self::Str(_) => Str::TYPE_NAME,
|
||||
Self::Bytes(_) => Bytes::TYPE_NAME,
|
||||
Self::Label(_) => Label::TYPE_NAME,
|
||||
Self::Datetime(_) => Datetime::TYPE_NAME,
|
||||
Self::Duration(_) => Duration::TYPE_NAME,
|
||||
Self::Content(_) => Content::TYPE_NAME,
|
||||
Self::Styles(_) => Styles::TYPE_NAME,
|
||||
Self::Array(_) => Array::TYPE_NAME,
|
||||
@ -200,6 +207,8 @@ impl Debug for Value {
|
||||
Self::Str(v) => Debug::fmt(v, f),
|
||||
Self::Bytes(v) => Debug::fmt(v, f),
|
||||
Self::Label(v) => Debug::fmt(v, f),
|
||||
Self::Datetime(v) => Debug::fmt(v, f),
|
||||
Self::Duration(v) => Debug::fmt(v, f),
|
||||
Self::Content(v) => Debug::fmt(v, f),
|
||||
Self::Styles(v) => Debug::fmt(v, f),
|
||||
Self::Array(v) => Debug::fmt(v, f),
|
||||
@ -245,6 +254,8 @@ impl Hash for Value {
|
||||
Self::Label(v) => v.hash(state),
|
||||
Self::Content(v) => v.hash(state),
|
||||
Self::Styles(v) => v.hash(state),
|
||||
Self::Datetime(v) => v.hash(state),
|
||||
Self::Duration(v) => v.hash(state),
|
||||
Self::Array(v) => v.hash(state),
|
||||
Self::Dict(v) => v.hash(state),
|
||||
Self::Func(v) => v.hash(state),
|
||||
@ -435,7 +446,7 @@ impl<'de> Visitor<'de> for ValueVisitor {
|
||||
let dict = Dict::deserialize(MapAccessDeserializer::new(map))?;
|
||||
Ok(match parse_toml_date(&dict) {
|
||||
None => dict.into_value(),
|
||||
Some(dt) => Value::dynamic(dt),
|
||||
Some(datetime) => datetime.into_value(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -610,6 +621,8 @@ primitive! {
|
||||
}
|
||||
primitive! { Bytes: "bytes", Bytes }
|
||||
primitive! { Label: "label", Label }
|
||||
primitive! { Datetime: "datetime", Datetime }
|
||||
primitive! { Duration: "duration", Duration }
|
||||
primitive! { Content: "content",
|
||||
Content,
|
||||
None => Content::empty(),
|
||||
|
@ -431,6 +431,71 @@ Returns the second of the datetime, if it exists. Otherwise, it returns
|
||||
|
||||
- returns: integer or none
|
||||
|
||||
### ordinal()
|
||||
Returns the ordinal (day of the year) of the datetime, if it exists.
|
||||
Otherwise, it returns `{none}`.
|
||||
|
||||
- returns: integer or none
|
||||
|
||||
# Duration
|
||||
Represents a span of time. Can be created by either specifying a custom
|
||||
duration using the [`duration`]($func/datetime) function or by subtracting two
|
||||
[datetimes]($type/datetime).
|
||||
|
||||
## Example
|
||||
```example
|
||||
#let duration = duration(
|
||||
days: 4,
|
||||
hours: 2,
|
||||
minutes: 10,
|
||||
)
|
||||
|
||||
#duration.hours()
|
||||
```
|
||||
|
||||
## Methods
|
||||
### seconds()
|
||||
Returns the duration in seconds as a floating-point value.
|
||||
|
||||
This function returns the total duration represented in seconds as a
|
||||
floating-point number. It does not provide the second component of the duration,
|
||||
but rather gives the overall duration in terms of seconds.
|
||||
|
||||
- returns: float
|
||||
|
||||
### minutes()
|
||||
Returns the duration in minutes as a floating-point value.
|
||||
|
||||
This function returns the total duration represented in minutes as a
|
||||
floating-point number. It does not provide the minute component of the duration,
|
||||
but rather gives the overall duration in terms of minutes.
|
||||
|
||||
- returns: float
|
||||
|
||||
### hours()
|
||||
Returns the duration in hours as a floating-point value.
|
||||
|
||||
This function returns the total duration represented in hours as a
|
||||
floating-point number. It does not provide the hour component of the duration,
|
||||
but rather gives the overall duration in terms of hours.
|
||||
- returns: float
|
||||
|
||||
### days()
|
||||
Returns the duration in days as a floating-point value.
|
||||
|
||||
This function returns the total duration represented in days as a
|
||||
floating-point number. It does not provide the day component of the duration,
|
||||
but rather gives the overall duration in terms of days.
|
||||
- returns: float
|
||||
|
||||
### weeks()
|
||||
Returns the duration in weeks as a floating-point value.
|
||||
|
||||
This function returns the total duration represented in weeks as a
|
||||
floating-point number. It does not provide the week component of the duration,
|
||||
but rather gives the overall duration in terms of weeks.
|
||||
- returns: float
|
||||
|
||||
# Symbol
|
||||
A Unicode symbol.
|
||||
|
||||
|
104
tests/typ/compiler/duration.typ
Normal file
104
tests/typ/compiler/duration.typ
Normal file
@ -0,0 +1,104 @@
|
||||
// Test durations.
|
||||
// Ref: false
|
||||
|
||||
---
|
||||
// Test negating durations.
|
||||
#test(-duration(hours: 2), duration(hours: -2))
|
||||
|
||||
---
|
||||
// Test adding and subtracting durations.
|
||||
#test(duration(weeks: 1, hours: 1), duration(weeks: 1) + duration(hours: 1))
|
||||
#test(duration(weeks: 1, hours: -1), duration(weeks: 1) - duration(hours: 1))
|
||||
#test(duration(days: 6, hours: 23), duration(weeks: 1) - duration(hours: 1))
|
||||
|
||||
---
|
||||
// Test adding and subtracting durations and dates.
|
||||
#let d = datetime(day: 1, month: 1, year: 2000)
|
||||
#let d2 = datetime(day: 1, month: 2, year: 2000)
|
||||
#test(d + duration(weeks: 2), datetime(day: 15, month: 1, year: 2000))
|
||||
#test(d + duration(days: 3), datetime(day: 4, month: 1, year: 2000))
|
||||
#test(d + duration(weeks: 1, days: 3), datetime(day: 11, month: 1, year: 2000))
|
||||
#test(d2 + duration(days: -1), datetime(day: 31, month: 1, year: 2000))
|
||||
#test(d2 + duration(days: -3), datetime(day: 29, month: 1, year: 2000))
|
||||
#test(d2 + duration(weeks: -1), datetime(day: 25, month: 1, year: 2000))
|
||||
#test(d + duration(days: -1), datetime(day: 31, month: 12, year: 1999))
|
||||
#test(d + duration(weeks: 1, days: -7), datetime(day: 1, month: 1, year: 2000))
|
||||
#test(d2 - duration(days: 1), datetime(day: 31, month: 1, year: 2000))
|
||||
#test(d2 - duration(days: 3), datetime(day: 29, month: 1, year: 2000))
|
||||
#test(d2 - duration(weeks: 1), datetime(day: 25, month: 1, year: 2000))
|
||||
#test(d - duration(days: 1), datetime(day: 31, month: 12, year: 1999))
|
||||
#test(datetime(day: 31, month: 1, year: 2000) + duration(days: 1), d2)
|
||||
#test(
|
||||
datetime(day: 31, month: 12, year: 2000) + duration(days: 1),
|
||||
datetime(day: 1, month: 1, year: 2001),
|
||||
)
|
||||
|
||||
---
|
||||
// Test adding and subtracting durations and times.
|
||||
#let a = datetime(hour: 12, minute: 0, second: 0)
|
||||
#test(a + duration(hours: 1, minutes: -60), datetime(hour: 12, minute: 0, second: 0))
|
||||
#test(a + duration(hours: 2), datetime(hour: 14, minute: 0, second: 0))
|
||||
#test(a + duration(minutes: 10), datetime(hour: 12, minute: 10, second: 0))
|
||||
#test(a + duration(seconds: 30), datetime(hour: 12, minute: 0, second: 30))
|
||||
#test(a + duration(hours: -2), datetime(hour: 10, minute: 0, second: 0))
|
||||
#test(a - duration(hours: 2), datetime(hour: 10, minute: 0, second: 0))
|
||||
#test(a + duration(minutes: -10), datetime(hour: 11, minute: 50, second: 0))
|
||||
#test(a - duration(minutes: 10), datetime(hour: 11, minute: 50, second: 0))
|
||||
#test(a + duration(seconds: -30), datetime(hour: 11, minute: 59, second: 30))
|
||||
#test(a - duration(seconds: 30), datetime(hour: 11, minute: 59, second: 30))
|
||||
#test(
|
||||
a + duration(hours: 1, minutes: 13, seconds: 13),
|
||||
datetime(hour: 13, minute: 13, second: 13),
|
||||
)
|
||||
|
||||
---
|
||||
// Test adding and subtracting durations and datetimes.
|
||||
#test(
|
||||
datetime(day: 1, month: 1, year: 2000, hour: 12, minute: 0, second: 0)
|
||||
+ duration(weeks: 1, days: 3, hours: -13, minutes: 10, seconds: -10 ),
|
||||
datetime(day: 10, month: 1, year: 2000, hour: 23, minute: 9, second: 50),
|
||||
)
|
||||
#test(
|
||||
datetime(day: 1, month: 1, year: 2000, hour: 12, minute: 0, second: 0)
|
||||
+ duration(weeks: 1, days: 3, minutes: 10)
|
||||
- duration(hours: 13, seconds: 10),
|
||||
datetime(day: 10, month: 1, year: 2000, hour: 23, minute: 9, second: 50),
|
||||
)
|
||||
|
||||
---
|
||||
// Test subtracting dates.
|
||||
#let a = datetime(hour: 12, minute: 0, second: 0)
|
||||
#let b = datetime(day: 1, month: 1, year: 2000)
|
||||
#test(datetime(hour: 14, minute: 0, second: 0) - a, duration(hours: 2))
|
||||
#test(datetime(hour: 14, minute: 0, second: 0) - a, duration(minutes: 120))
|
||||
#test(datetime(hour: 13, minute: 0, second: 0) - a, duration(seconds: 3600))
|
||||
#test(datetime(day: 1, month: 2, year: 2000) - b, duration(days: 31))
|
||||
#test(datetime(day: 15, month: 1, year: 2000) - b, duration(weeks: 2))
|
||||
|
||||
---
|
||||
// Test multiplying and dividing durations with numbers.
|
||||
#test(duration(minutes: 10) * 6, duration(hours: 1))
|
||||
#test(duration(minutes: 10) * 2, duration(minutes: 20))
|
||||
#test(duration(minutes: 10) * 2.5, duration(minutes: 25))
|
||||
#test(duration(minutes: 10) / 2, duration(minutes: 5))
|
||||
#test(duration(minutes: 10) / 2.5, duration(minutes: 4))
|
||||
|
||||
---
|
||||
// Test dividing durations with durations
|
||||
#test(duration(minutes: 20) / duration(hours: 1), 1 / 3)
|
||||
#test(duration(minutes: 20) / duration(minutes: 10), 2)
|
||||
#test(duration(minutes: 20) / duration(minutes: 8), 2.5)
|
||||
|
||||
---
|
||||
// Test comparing durations
|
||||
#test(duration(minutes: 20) > duration(minutes: 10), true)
|
||||
#test(duration(minutes: 20) >= duration(minutes: 10), true)
|
||||
#test(duration(minutes: 10) < duration(minutes: 20), true)
|
||||
#test(duration(minutes: 10) <= duration(minutes: 20), true)
|
||||
#test(duration(minutes: 10) == duration(minutes: 10), true)
|
||||
#test(duration(minutes: 10) != duration(minutes: 20), true)
|
||||
#test(duration(minutes: 10) <= duration(minutes: 10), true)
|
||||
#test(duration(minutes: 10) >= duration(minutes: 10), true)
|
||||
#test(duration(minutes: 20) < duration(minutes: 10), false)
|
||||
#test(duration(minutes: 20) <= duration(minutes: 10), false)
|
||||
#test(duration(minutes: 20) == duration(minutes: 10), false)
|
@ -198,3 +198,11 @@
|
||||
#test(2deg.deg(), 2.0)
|
||||
#test(2.94deg.deg(), 2.94)
|
||||
#test(0rad.deg(), 0.0)
|
||||
|
||||
---
|
||||
// Test date methods.
|
||||
#test(datetime(day: 1, month: 1, year: 2000).ordinal(), 1);
|
||||
#test(datetime(day: 1, month: 3, year: 2000).ordinal(), 31 + 29 + 1);
|
||||
#test(datetime(day: 31, month: 12, year: 2000).ordinal(), 366);
|
||||
#test(datetime(day: 1, month: 3, year: 2001).ordinal(), 31 + 28 + 1);
|
||||
#test(datetime(day: 31, month: 12, year: 2001).ordinal(), 365);
|
||||
|
Loading…
x
Reference in New Issue
Block a user