Dates and times
Dates are RFC 3339 strings. A date-time is converted to UTC before its calendar date is taken.
There is no date type: a date is a string in RFC 3339 form. A date is "2026-10-03". A
date-time has seconds and an offset: "2026-10-03T09:00:00Z" or "2026-10-03T22:00:00-05:00".
The date operators convert a date-time to UTC first, then take its calendar date. daysBetween
counts whole calendar days; yearsBetween counts completed years, the way ages are counted.
Syntax
{ op: daysBetween, args: [{ var: data.orderDate }, { var: data.deliveryDate }] } # whole days, first to second
{ op: yearsBetween, args: [{ var: data.dateOfBirth }, { var: ctx.today }] } # completed years| Accepted | Refused (an evaluation error) |
|---|---|
2026-10-03 | 2026-02-30 (no such date) |
2026-10-03T09:00:00Z | 2026-10-03T09:00Z (no seconds) |
2026-10-03T09:00:00.5-04:00 | 2026-10-03 09:00:00 (space, no offset) |
Example
Delivery must be at least one day after the order. At 22:00 in Chicago (UTC−5) it is already 4 October in UTC, so a delivery on the 4th is the same day.
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.dates, version: 1.0.0, title: "Dates" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: order.delivery.next-day
kind: validation
target: { entity: Order, field: /deliveryDate }
operations: [create]
assert: { op: gte, args: [{ op: daysBetween, args: [{ var: data.orderDate }, { var: data.deliveryDate }] }, 1] }
severity: error
finding: { code: LRN-DATE-001, message: order.deliveryTooSoon }
messages:
en:
order.deliveryTooSoon: "Delivery is at least one day after the order."
tests:
- name: 22:00 in Chicago is already the next day in UTC
entity: Order
operation: create
given:
data: { orderDate: "2026-10-03T22:00:00-05:00", deliveryDate: "2026-10-04" }
expect:
decision: deny
findings:
- { rule: order.delivery.next-day, fields: [/deliveryDate] }
- name: ordered on the 3rd in UTC, delivered on the 4th
entity: Order
operation: create
given:
data: { orderDate: "2026-10-03T10:00:00Z", deliveryDate: "2026-10-04" }
expect: { decision: allow, findings: [] }{
"entity": "Order",
"operation": "create",
"data": {
"orderDate": "2026-10-03T22:00:00-05:00",
"deliveryDate": "2026-10-04"
}
}Result, from the engine
Decisiondeny1 finding, server channel
LRN-DATE-001errorblockingDelivery is at least one day after the order./deliveryDate
Common mistakes
- Leaving dates unquoted in YAML.
2026-10-03without quotes is a date object to some parsers. Write"2026-10-03". - Reading today's date inside the rule. The engine has no clock: pass it as
ctx.today. - Sending a date that does not exist. The rule cannot be evaluated, so it fails closed with
RULE-EVALUATION-ERRORand the request is denied.
Exercise
Add a rule for the Customer entity: a customer must be at least 18 on ctx.today. Test a customer
born on "2010-05-01" on "2026-10-03".
Hint
Declare the Customer entity, then add a validation rule with yearsBetween from data.dateOfBirth to ctx.today, at least 18.
Show answer
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.dates, version: 1.0.0, title: "Dates" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
Customer:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Customer" }
rules:
- id: order.delivery.next-day
kind: validation
target: { entity: Order, field: /deliveryDate }
operations: [create]
assert: { op: gte, args: [{ op: daysBetween, args: [{ var: data.orderDate }, { var: data.deliveryDate }] }, 1] }
severity: error
finding: { code: LRN-DATE-001, message: order.deliveryTooSoon }
- id: customer.adult
kind: validation
target: { entity: Customer, field: /dateOfBirth }
operations: [create]
assert: { op: gte, args: [{ op: yearsBetween, args: [{ var: data.dateOfBirth }, { var: ctx.today }] }, 18] }
severity: error
finding: { code: LRN-DATE-002, message: customer.tooYoung }
messages:
en:
order.deliveryTooSoon: "Delivery is at least one day after the order."
customer.tooYoung: "You must be 18 or older."
tests:
- name: born in 2010, too young on 2026-10-03
entity: Customer
operation: create
given:
data: { dateOfBirth: "2010-05-01" }
ctx: { today: "2026-10-03" }
expect:
decision: deny
findings:
- { rule: customer.adult, fields: [/dateOfBirth] }
- name: eighteen on the day itself
entity: Customer
operation: create
given:
data: { dateOfBirth: "2008-10-03" }
ctx: { today: "2026-10-03" }
expect: { decision: allow, findings: [] }
- name: 22:00 in Chicago is already the next day in UTC
entity: Order
operation: create
given:
data: { orderDate: "2026-10-03T22:00:00-05:00", deliveryDate: "2026-10-04" }
expect:
decision: deny
findings:
- { rule: order.delivery.next-day, fields: [/deliveryDate] }
- name: ordered on the 3rd in UTC, delivered on the 4th
entity: Order
operation: create
given:
data: { orderDate: "2026-10-03T10:00:00Z", deliveryDate: "2026-10-04" }
expect: { decision: allow, findings: [] }{
"entity": "Customer",
"operation": "create",
"data": {
"dateOfBirth": "2010-05-01"
},
"ctx": {
"today": "2026-10-03"
}
}Result, from the engine
Decisiondeny1 finding, server channel
LRN-DATE-002errorblockingYou must be 18 or older./dateOfBirth