diff --git a/AGENTS.md b/AGENTS.md index 7fb516e..b086b77 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -149,6 +149,22 @@ test/predicator/ ## Recent Additions (2025) +### Durations and Relative Dates (v3.4.0) + +- Natural-language durations and relative time expressions +- Relative dates: `3d ago`, `2w from now`, `next 1mo`, `last 1y` +- Date/DateTime arithmetic: `#2024-01-10# + 5d`, `#2024-01-15T10:30:00Z# - 2h` +- Grammar updates: `duration` and `relative_date` productions +- Full pipeline support (lexer, parser, compiler, evaluator, string visitor) with tests +- Examples: + + ```elixir + Predicator.evaluate("created_at > 3d ago", %{"created_at" => ~U[2024-01-20 00:00:00Z]}) + Predicator.evaluate("due_at < 2w from now", %{"due_at" => Date.add(Date.utc_today(), 10)}) + Predicator.evaluate("#2024-01-10# + 5d = #2024-01-15#", %{}) + Predicator.evaluate("#2024-01-15T10:30:00Z# - 2h < #2024-01-15T10:30:00Z#", %{}) + ``` + ### Object Literals (v3.1.0 - JavaScript-Style Objects) - **Syntax Support**: Complete JavaScript-style object literal syntax (`{}`, `{name: "John"}`, `{user: {role: "admin"}}`) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d3742..fa5cf10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.4.0] - 2025-09-09 + +### Added + +#### Durations and relative date/time arithmetic + +- New duration literals and relative date expressions (e.g., `3d ago`, `2w from now`, `next 1mo`, `last 1y`) +- Date and DateTime arithmetic using durations (e.g., `#2024-01-10# + 5d`, `#2024-01-15T10:30:00Z# - 2h`) +- Grammar additions: `duration` and `relative_date` productions +- Full pipeline support (lexer, parser, compiler, evaluator, string visitor) with tests + +#### Examples: + +```elixir +Predicator.evaluate("created_at > 3d ago", %{"created_at" => ~U[2024-01-20 00:00:00Z]}) +Predicator.evaluate("due_at < 2w from now", %{"due_at" => Date.add(Date.utc_today(), 10)}) +Predicator.evaluate("#2024-01-10# + 5d = #2024-01-15#", %{}) +Predicator.evaluate("#2024-01-15T10:30:00Z# - 2h < #2024-01-15T10:30:00Z#", %{}) +``` + +### Documentation + +- Updated EBNF grammar in docs +- Added AGENTS.md with model-agnostic agent guidance; `CLAUDE.md` now references the same content + ## [3.3.0] - 2025-08-31 ### Added diff --git a/README.md b/README.md index 570a8a7..5396233 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Predicator allows you to safely evaluate user-defined expressions without the se - 🔄 **Reversible**: Convert AST back to string expressions with formatting options - 🧮 **Arithmetic**: Full arithmetic operations (`+`, `-`, `*`, `/`, `%`) with proper precedence - 📅 **Date Support**: Native date and datetime literals with ISO 8601 format +- ⏳ **Durations & Relative Dates**: Natural-language durations and relative time expressions with date arithmetic - 📋 **Lists**: List literals with membership operations (`in`, `contains`) - 🧠 **Smart Logic**: Logical operators with proper precedence (`AND`, `OR`, `NOT`) - 🔧 **Functions**: Built-in functions for string, numeric, and date operations @@ -32,7 +33,7 @@ Add `predicator` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:predicator, "~> 3.3"} + {:predicator, "~> 3.4"} ] end ``` @@ -58,6 +59,19 @@ true iex> Predicator.evaluate!("created_at < #2024-01-15T10:30:00Z#", %{"created_at" => ~U[2024-01-10 09:00:00Z]}) true +# Durations, relative dates, and date arithmetic +iex> Predicator.evaluate!("created_at > 3d ago", %{"created_at" => ~U[2024-01-20 00:00:00Z]}) +true + +iex> Predicator.evaluate!("due_at < 2w from now", %{"due_at" => Date.add(Date.utc_today(), 10)}) +true + +iex> Predicator.evaluate!("#2024-01-10# + 5d = #2024-01-15#", %{}) +true + +iex> Predicator.evaluate!("#2024-01-15T10:30:00Z# - 2h < #2024-01-15T10:30:00Z#", %{}) +true + # List literals and membership iex> Predicator.evaluate!("role in ['admin', 'manager']", %{"role" => "admin"}) true @@ -334,6 +348,9 @@ iex> Predicator.evaluate("'coding' in user.hobbies", list_context) - **Booleans**: `true`, `false` (or plain identifiers like `active`, `expired`) - **Dates**: `#2024-01-15#` (ISO 8601 date format) - **DateTimes**: `#2024-01-15T10:30:00Z#` (ISO 8601 datetime format with timezone) +- **Durations**: Natural units for time spans (e.g., `3d`, `2h`, `15m`) + - In relative expressions: `3d ago`, `2w from now`, `next 1mo`, `last 1y` + - In arithmetic: `#2024-01-10# + 5d`, `#2024-01-15T10:30:00Z# - 2h` - **Lists**: `[1, 2, 3]`, `['admin', 'manager']` (homogeneous collections) - **Objects**: `{}`, `{name: "John", age: 30}`, `{user: {role: "admin"}}` (JavaScript-style object literals) - **Identifiers**: `score`, `user_name`, `is_active`, `user.profile.name`, `user['key']`, `items[0]` (variable references with dot notation and bracket notation for nested data) @@ -554,4 +571,3 @@ mix dialyzer # Type checking ## Documentation Full documentation is available at [HexDocs](https://hexdocs.pm/predicator). - diff --git a/mix.exs b/mix.exs index d80b69b..0c14589 100644 --- a/mix.exs +++ b/mix.exs @@ -2,7 +2,7 @@ defmodule Predicator.MixProject do use Mix.Project @app :predicator - @version "3.3.0" + @version "3.4.0" @description "A secure, non-evaling condition (boolean predicate) engine for end users" @source_url "https://github.com/riddler/predicator-ex" @deps [