Back to Blog

July 21, 2026

Odoo's ORM Now Speaks Relative Time: Dynamic Date Expressions and the any! Operator Land in Domain Filters

Odoo 19 introduces a compact expression language for writing date-relative domain filters directly in the ORM, eliminating the need for computed fields or Python date arithmetic. A new any! operator also lets developers traverse relational fields without access control interference.

Odoo 19 ORM domain filter enhancements showing dynamic date expression syntax and the new any! operator

If you’ve ever written a scheduled action or a custom filter in Odoo that needed to say “all records created in the last seven days” or “invoices due before next Monday,” you know the dance. You either write a computed field that recalculates the target date, or you drop into Python to build the domain with datetime.timedelta andfields.Date.today(). It works, but it’s ceremony for something that should be a one-liner.

Odoo 19 eliminates that ceremony entirely. The ORM now accepts relative time expressions directly inside domain filter values — a compact, space-separated syntax that lets you express dates and times relative to “now” without writing a single line of Python.

The Syntax: Compact and Surprisingly Flexible

The expression language is built around a simple grammar. You start with an optional anchor — either today (midnight of the current day) or now (the exact current timestamp). Then you chain terms using + (add),- (subtract), or = (set) followed by an integer and a unit.

The supported units cover every granularity you’d realistically need: d for days, w for weeks,m for months, y for years,H for hours, M for minutes, andSfor seconds. There’s even weekday support — you can reference monday throughsunday as targets.

Here’s what it looks like in practice:

  • '-3d +1H'— three days ago plus one hour from the current moment
  • 'today'— midnight of the current day
  • '=5d'— the 5th day of the current month at midnight
  • '=3H'— today at exactly 3:00 AM
  • '=1m'— January of the current year, same day, at midnight
  • '=monday -1w'— Monday of the previous week

The = operator is the interesting one. While+ and - do arithmetic relative to the current moment, = sets a specific component of the date. Writing =5ddoesn’t add five days — it pins the day-of-month to the 5th. When you set a component, everything below it (hours, minutes, seconds) resets to zero. That’s a subtle but important detail for anyone writing filters that need midnight precision.

Where This Matters Most

The immediate beneficiaries are automated actions, server actions, and any XML-defined domain filter. Previously, if you wanted a filter like “show leads created in the last 48 hours,” you needed either a computed field on the model or a Python-evaluated domain. Now you write it inline:

[('create_date', '>=', '-2d')]

That’s it. No helper method, no computed field, nocontext_today()gymnastics. The ORM evaluates the expression at query time in the user’s timezone, which means the filter adapts correctly whether the user is in Tokyo or Toronto.

For reporting and dashboard filters, this is particularly useful. Instead of hardcoding date ranges that drift out of relevance, you can define rolling windows that always reflect the current moment. A sales manager who wants to see “orders from the first of this month to now” can have that as a saved filter without any custom development.

The any! Operator: Relational Traversal Without ACL Friction

Alongside dynamic dates, Odoo 19 introduces a new domain operator:any!. It works exactly like the existinganyoperator — which traverses relational fields to match records where at least one related record satisfies a condition — but with one critical difference: it bypasses access control checks during the traversal.

This isn’t about circumventing security. It’s about solving a practical problem that developers hit constantly: you want to filter records based on a related model’s data, but the current user doesn’t have read access to that related model. The classic example is filtering sales orders by internal cost data that only managers can see. With any, the domain fails silently or returns incomplete results. Withany!, the filter works as intended because the access check happens on the primary model, not the traversed one.

The corresponding not any! variant is also available for exclusion filters.

Operator Documentation Gets a Cleanup Too

While adding these features, the development team also reorganized how domain operators are documented. Previously, each negative variant (not like, not ilike,not in, not any) had its own standalone entry with a separate description. Now they’re folded into their positive counterparts — like (and not like),ilike (and not ilike), and so on. It’s a small change, but it cuts the reference documentation nearly in half and makes it easier to scan when you’re looking up the right operator for a domain.

What Developers Should Know

Dynamic date expressions work anywhere the ORM evaluates a domain. That includes search() calls, action domains, window action filters, and automated action trigger conditions. They evaluate at query time, not at definition time, so a domain defined once stays current indefinitely.

The timezone handling is worth noting: expressions are evaluated in the context user’s timezone. If your automated action runs as the OdooBot user (which typically has no timezone set), the behavior defaults to UTC. For most server-side operations that’s fine, but if you’re building user-facing filters, the timezone-awareness is a genuine advantage over manual date arithmetic that often forgets to account for it.

These changes shipped as part of the Odoo 19.0 ORM changelog, alongside support for GROUPING SETS in pivot views and the deprecation of odoo.osv and the legacyrecord._cr / record._context /record._uid accessors. The ORM is getting both more expressive and more disciplined at the same time.

Ready to experience Odoo AI?

Join hundreds of teams using DearERP to customize Odoo in minutes, not weeks. Plans start at $29/month.