Skip to content

fix: accept a Duration in from_now/1 - #2844

Merged
zachdaniel merged 1 commit into
ash-project:mainfrom
matt-beanland:fix/from-now-duration
Aug 14, 2026
Merged

fix: accept a Duration in from_now/1#2844
zachdaniel merged 1 commit into
ash-project:mainfrom
matt-beanland:fix/from-now-duration

Conversation

@matt-beanland

Copy link
Copy Markdown
Contributor

Contributor checklist

  • I accept the AI Policy, or AI was not used in the creation of this PR.
  • Bug fixes include regression tests
  • Chores
  • Documentation changes
  • Features include unit/acceptance tests
  • Refactoring
  • Update dependencies

Fixes #2843

from_now's Function'args/0 gains the Duration form, matching ago

ago, date_add and datetime_add all declare their Duration form. This brings from_now into line.

Added a test that goes through the expression path.

Documented missing Duration forms in the expressions guide.

`from_now`'s moduledoc documents `from_now(Duration.new!(day: 7))` and
`evaluate/1` implements it, but `args/0` declared only the interval form, so
arg validation rejected the call before `evaluate/1` was reached:

    No such function from_now/1. Did you mean one of: from_now/2

`ago`, `date_add` and `datetime_add` all declare their Duration form. This
brings `from_now` into line.

The existing test called `evaluate/1` directly, which is reached only once
`args/0` declares the form, so it passed while the function was uncallable in
an expression. Added a test that goes through the expression path instead.

Also documents the Duration forms in the expressions guide, which listed only
the interval forms for all four functions.
use Ash.Query.Function, name: :from_now, eager_evaluate?: false

def args, do: [[:integer, :duration_name]]
def args, do: [[:integer, :duration_name], [:duration]]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We either need to:

  1. update ash_sql code to handle this
  2. modify def new to destructure the duration into its parts

I'm partial to #2

@matt-beanland

Copy link
Copy Markdown
Contributor Author

Thanks Zach,

Ecto 3.14 has a native :duration type, and Postgres accepts a %Duration{} as a parameter directly — I checked against PG19:

Duration.new!(day: 7)                      ~U[2026-08-21 01:27:15Z]
Duration.new!(month: 1, day: 2, hour: 3)   ~U[2026-09-16 04:27:15Z]
Duration.new!(minute: -30)                 ~U[2026-08-14 00:57:15Z]
Duration.new!(microsecond: {500, 6})       ~U[2026-08-14 01:27:15Z]

Multi-unit, negative and sub-second all arrive correctly. So option 1 is one extra clause per function rendering ? + ? with the duration as a parameter — no interval-name string building at all.

That multi-unit row is also what put me off (2): a Duration doesn't destructure into a single [amount, interval] pair, and ago(^%Duration{month: 1, day: 2, hour: 3}) already evaluates correctly today, so new/1 would have to compose nested calls or reject it, narrowing what existing non-SQL data layers do today.

Worth noting this isn't really about from_nowago, datetime_add and date_add
all declare [:duration] already, so they pass core validation and fail in ash_sql on
released ash today. from_now is just the one whose breakage was hidden behind
args/0.

Given how small it is I went ahead this way with ash_sql. I've contributed ash-project/ash_sql#245 and will do an ash_postgres test PR for merge on ash_sql release. This will deliver part of the ash_postgres Duration support ash-project/ash_postgres#553 which I'll continue with, it needs the operators and read/write path, which writes OK but needs to handle %Postgrex.Interval{} on read.

So if you are happy with the ash_sql PR, this one can be merged unchanged?

@zachdaniel
zachdaniel merged commit dcf2551 into ash-project:main Aug 14, 2026
51 checks passed
@zachdaniel

Copy link
Copy Markdown
Contributor

🚀 Thank you for your contribution! 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

from_now rejects a Duration

2 participants