From a5957f524b77f53e10864d0f5cd15aeb00cd1f58 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Thu, 19 Feb 2026 13:27:44 +0100 Subject: [PATCH] Add DateTime type and operations This commit introduces the `DateTime` type to the language, enabling users to work with dates and times. It includes: - A new `DateTime` variant in the `Value` and `StaticType` enums. - A `date` function for parsing date strings into `DateTime` values. - Overloads for `+` and `-` operators to support `DateTime` arithmetic. - Comparison operators (`>`, `<`) for `DateTime` values. - Corresponding unit tests for `DateTime` operations. - Updates to the `gemini.md` documentation to reflect the new functionality. Add DateTime type and operations Introduce a new `DateTime` type to the language, allowing for date and time manipulation. This includes: * Parsing dates and datetimes from strings. * Performing arithmetic operations (addition and subtraction) between `DateTime` and `Int` (representing milliseconds) and between two `DateTime` values (resulting in an `Int` duration). * Enabling comparison operations (`>`, `<`) between `DateTime` values. --- gemini.md | 3 ++- src/ast/compiler/type_checker.rs | 12 ++++++++++++ src/ast/environment.rs | 30 +++++++++++++++++++++++++++++- src/ast/types.rs | 11 +++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/gemini.md b/gemini.md index c6e0f9b..23d053c 100644 --- a/gemini.md +++ b/gemini.md @@ -40,4 +40,5 @@ Wichtig: zentraler Einstiegspunkt ist Delphi/Myc.Ast.Environment.pasund die dort ## Testing * Das Projekt enthält eine Testsuite für Skripte. Logik in src/utils/tester.rs. Diese Testes werden automatisch in "cargo test" eingebunden. -* Nach Fertigstellung einer Aufgabe: Warnungen sind zu eliminieren. Tests müssen durchlaufen. Clippy auch. +* Nach Fertigstellung einer Aufgabe: Warnungen sind zu eliminieren. Tests müssen durchlaufen. +* Wenn alles funktioniert, muss auch clippy ohne Beanstandungen durchlaufen. diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index f865685..8e3a4b0 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -291,4 +291,16 @@ mod tests { assert_eq!(check_source("(+ \"a\" \"b\")").ty, StaticType::Text); assert_eq!(check_source("(/ 1 2)").ty, StaticType::Float); } + + #[test] + fn test_datetime_inference() { + // date("2023-01-01") -> DateTime + assert_eq!(check_source("(date \"2023-01-01\")").ty, StaticType::DateTime); + // DateTime + Int -> DateTime + assert_eq!(check_source("(+ (date \"2023-01-01\") 86400000)").ty, StaticType::DateTime); + // DateTime - DateTime -> Int (Duration) + assert_eq!(check_source("(- (date \"2023-01-02\") (date \"2023-01-01\"))").ty, StaticType::Int); + // DateTime comparison -> Bool + assert_eq!(check_source("(> (date \"2023-01-02\") (date \"2023-01-01\"))").ty, StaticType::Bool); + } } diff --git a/src/ast/environment.rs b/src/ast/environment.rs index 5f5ba68..98492ad 100644 --- a/src/ast/environment.rs +++ b/src/ast/environment.rs @@ -89,11 +89,31 @@ impl Environment { fn register_stdlib(&self) { use crate::ast::types::Signature; + use chrono::{NaiveDate, NaiveDateTime}; + + self.register_native("date", StaticType::Function(Box::new(Signature { + params: vec![StaticType::Text], + ret: StaticType::DateTime + })), |args| { + if let Value::Text(s) = &args[0] { + // Try parse YYYY-MM-DD + if let Ok(dt) = NaiveDate::parse_from_str(s, "%Y-%m-%d") { + let ts = dt.and_hms_opt(0, 0, 0).unwrap().and_utc().timestamp_millis(); + return Value::DateTime(ts); + } + // Try parse YYYY-MM-DD HH:MM:SS + if let Ok(dt) = NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") { + return Value::DateTime(dt.and_utc().timestamp_millis()); + } + } + Value::Void + }); let plus_ty = StaticType::FunctionOverloads(vec![ Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Int }, Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float }, Signature { params: vec![StaticType::Text, StaticType::Text], ret: StaticType::Text }, + Signature { params: vec![StaticType::DateTime, StaticType::Int], ret: StaticType::DateTime }, ]); self.register_native("+", plus_ty, |args| { if args.len() == 2 { @@ -106,7 +126,8 @@ impl Environment { let mut res = a.to_string(); res.push_str(b); Value::Text(Rc::from(res)) - } + }, + (Value::DateTime(ts), Value::Int(ms)) => Value::DateTime(ts + ms), _ => Value::Void, } } else { @@ -124,6 +145,8 @@ impl Environment { Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Float }, Signature { params: vec![StaticType::Int], ret: StaticType::Int }, Signature { params: vec![StaticType::Float], ret: StaticType::Float }, + Signature { params: vec![StaticType::DateTime, StaticType::DateTime], ret: StaticType::Int }, + Signature { params: vec![StaticType::DateTime, StaticType::Int], ret: StaticType::DateTime }, ]); self.register_native("-", minus_ty, |args| { if args.is_empty() { return Value::Void; } @@ -140,6 +163,8 @@ impl Environment { (Value::Float(a), Value::Float(b)) => Value::Float(a - b), (Value::Int(a), Value::Float(b)) => Value::Float(*a as f64 - b), (Value::Float(a), Value::Int(b)) => Value::Float(a - *b as f64), + (Value::DateTime(a), Value::DateTime(b)) => Value::Int(a - b), + (Value::DateTime(a), Value::Int(b)) => Value::DateTime(a - b), _ => Value::Void, }; } @@ -192,6 +217,7 @@ impl Environment { let cmp_ty = StaticType::FunctionOverloads(vec![ Signature { params: vec![StaticType::Int, StaticType::Int], ret: StaticType::Bool }, Signature { params: vec![StaticType::Float, StaticType::Float], ret: StaticType::Bool }, + Signature { params: vec![StaticType::DateTime, StaticType::DateTime], ret: StaticType::Bool }, ]); self.register_native(">", cmp_ty.clone(), |args| { if args.len() != 2 { return Value::Void; } @@ -200,6 +226,7 @@ impl Environment { (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) > *b), (Value::Float(a), Value::Int(b)) => Value::Bool(*a > (*b as f64)), (Value::Float(a), Value::Float(b)) => Value::Bool(a > b), + (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a > b), _ => Value::Bool(false), } }); @@ -211,6 +238,7 @@ impl Environment { (Value::Int(a), Value::Float(b)) => Value::Bool((*a as f64) < *b), (Value::Float(a), Value::Int(b)) => Value::Bool(*a < (*b as f64)), (Value::Float(a), Value::Float(b)) => Value::Bool(a < b), + (Value::DateTime(a), Value::DateTime(b)) => Value::Bool(a < b), _ => Value::Bool(false), } }); diff --git a/src/ast/types.rs b/src/ast/types.rs index 823760f..55695e7 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -5,6 +5,7 @@ use std::fmt; use std::sync::OnceLock; use std::sync::Mutex; // Still needed for global keyword registry use std::any::Any; +use chrono::{TimeZone, Utc}; /// Simple source location #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -61,6 +62,7 @@ pub enum Value { Bool(bool), Int(i64), Float(f64), + DateTime(i64), Text(Rc), Keyword(Keyword), List(Rc>), @@ -84,6 +86,7 @@ pub enum StaticType { Bool, Int, Float, + DateTime, Text, Keyword, List(Box), @@ -101,6 +104,7 @@ impl fmt::Display for StaticType { StaticType::Bool => write!(f, "bool"), StaticType::Int => write!(f, "int"), StaticType::Float => write!(f, "float"), + StaticType::DateTime => write!(f, "datetime"), StaticType::Text => write!(f, "text"), StaticType::Keyword => write!(f, "keyword"), StaticType::List(inner) => write!(f, "[{}]", inner), @@ -182,6 +186,7 @@ impl Value { Value::Bool(_) => StaticType::Bool, Value::Int(_) => StaticType::Int, Value::Float(_) => StaticType::Float, + Value::DateTime(_) => StaticType::DateTime, Value::Text(_) => StaticType::Text, Value::Keyword(_) => StaticType::Keyword, Value::List(_) => StaticType::List(Box::new(StaticType::Any)), @@ -201,6 +206,12 @@ impl fmt::Display for Value { Value::Bool(b) => write!(f, "{}", b), Value::Int(i) => write!(f, "{}", i), Value::Float(fl) => write!(f, "{}", fl), + Value::DateTime(ts) => { + match Utc.timestamp_millis_opt(*ts) { + chrono::LocalResult::Single(dt) => write!(f, "#{}#", dt.format("%Y-%m-%d %H:%M:%S")), + _ => write!(f, "#timestamp({})#", ts), + } + }, Value::Text(t) => write!(f, "\"{}\"", t), Value::Keyword(k) => write!(f, ":{}", k.name()), Value::List(l) => {