Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Temporal Rules

Payment initiation should only be possible during business hours. A settlement batch may run only on weekdays. An agent’s payment mandate expires when the conference it was booked for ends. None of this is a property of the detail object – it depends on when the request is evaluated.

authz-details-rs represents time the same way as hbac-rs and abac-rs: an acls-rs Timestamp, milliseconds since the Unix epoch, passed to evaluate_at / check_access_at (evaluate / check_access use the current time). Two mechanisms build on it:

MechanismModelsCounterpart elsewhere
TemporalAuthzDetailsRuleA rule that is valid from / until a point in timeTemporalHbacRule, TemporalAbacRule
TimeWindow conditionA recurring daily window in UTC, optionally on some weekdays(authz-details only)

Validity windows

Wrap a rule in TemporalAuthzDetailsRule to give it a start (inclusive) and an end (exclusive), either of which may be open. The constructors match TemporalHbacRule: new(rule, from, until), valid_until, valid_from and valid_for_duration.

#![allow(unused)]
fn main() {
use authz_details_rs::prelude::*;

// 2026-10-31 00:00 UTC, when the conference ends
const MANDATE_ENDS: Timestamp = 1_793_404_800_000;

let rule = AuthzDetailsRule::builder("trip-payments")
    .type_id("payment_initiation")
    .field("$.instructedAmount.amount").le(600)
    .build()
    .unwrap();

let mut policy = AuthzDetailsPolicy::new();
policy
    .add_temporal_rule(TemporalAuthzDetailsRule::new(rule, None, Some(MANDATE_ENDS)).unwrap())
    .unwrap();

let payment = [AuthorizationDetail::new(
    "payment_initiation",
    serde_json::json!({"instructedAmount": {"currency": "EUR", "amount": "450.00"}}),
)];

let during_trip = 1_791_795_600_000; // 2026-10-12 09:00 UTC
let after_trip = 1_793_610_000_000; // 2026-11-02 09:00 UTC
assert!(policy.check_access_at(&payment, during_trip));
// After expiry no rule applies any more
assert_eq!(policy.evaluate_at(&payment, after_trip).verdict, Verdict::NoRule);
}

An expired rule simply stops applying: if it was the only rule for the type, the verdict becomes NoRule, which composed policies treat according to their mode (And denies).

Recurring time windows

TimeWindow is a daily window in UTC with both ends inclusive; a window whose end precedes its start crosses midnight. Limit it to weekdays with on(days). Rule builders add windows directly:

Builder methodCondition
during("09:00", "17:00")Inside the window, every day
during_on("09:00", "17:00", Weekday::WORKDAYS)Inside the window, on those days
on_days([Weekday::Saturday, Weekday::Sunday])On those days, all day
within(window) / outside(window)Inside / outside any TimeWindow
#![allow(unused)]
fn main() {
use authz_details_rs::prelude::*;

let office_hours = AuthzDetailsRule::builder("office-hours-payments")
    .type_id("payment_initiation")
    .during_on("09:00", "17:00", Weekday::WORKDAYS)
    .build()
    .unwrap();

let no_night_batches = AuthzDetailsRule::builder("no-night-batches")
    .type_id("settlement_batch")
    .outside(TimeWindow::parse("22:00", "06:00").unwrap())
    .build()
    .unwrap();

let detail = AuthorizationDetail::new("payment_initiation", serde_json::json!({}));

let monday_10am = 1_705_312_800_000; // 2024-01-15 10:00 UTC
let monday_8pm = 1_705_348_800_000; // 2024-01-15 20:00 UTC
let saturday_10am = 1_705_744_800_000; // 2024-01-20 10:00 UTC
assert!(office_hours.evaluate(&detail, monday_10am).unwrap());
assert!(!office_hours.evaluate(&detail, monday_8pm).unwrap());
assert!(!office_hours.evaluate(&detail, saturday_10am).unwrap());

let batch = AuthorizationDetail::new("settlement_batch", serde_json::json!({}));
assert!(no_night_batches.evaluate(&batch, monday_10am).unwrap());
assert!(!no_night_batches.evaluate(&batch, monday_10am + 13 * 3_600_000).unwrap()); // 23:00
}

Windows can be inspected and tested on their own; Weekday::at and TimeOfDay::at decompose a timestamp in UTC:

#![allow(unused)]
fn main() {
use authz_details_rs::prelude::*;

let monday_1030 = 1_705_314_600_000; // 2024-01-15 10:30 UTC
assert_eq!(Weekday::at(monday_1030), Weekday::Monday);
assert_eq!(TimeOfDay::at(monday_1030).to_string(), "10:30:00");

let night = TimeWindow::parse("22:00", "02:00").unwrap();
assert!(!night.contains(monday_1030));
}

Windows are evaluated in UTC only. Express local business hours in UTC (and remember daylight-saving shifts), or use validity windows for calendar-bound grants.

Best practices

  • Pin the evaluation time in tests and audits with evaluate_at, so decisions are deterministic and replayable.
  • Prefer validity windows (TemporalAuthzDetailsRule) for grants that start or expire on a date; they survive DST changes and match how HBAC and ABAC rules expire.
  • Keep time conditions in their own rules when they are an independent concern (“no payments at night”), so they can be disabled separately.

Next steps

Combine temporal rules with field conditions in a policy – see Rules and Policies – and compose the policy with RBAC in the acls-rs Bridge.