Skip to content

Timestamptz(Usec) is cast to timestamp in when performing calculation, leading to erroneous queries #651

@Munksgaard

Description

@Munksgaard

Code of Conduct

  • I agree to follow this project's Code of Conduct

AI Policy

  • I agree to follow this project's AI Policy, or I agree that AI was not used while creating this issue.

Versions

* ash 3.9.0 (Hex package) (mix)
  locked at 3.9.0 (ash) e3e80a18
* ash_postgres 2.6.25 (Hex package) (mix)
  locked at 2.6.25 (ash_postgres) dadf95df
* ash_sql 0.3.12 (Hex package) (mix)
  locked at 0.3.12 (ash_sql) 2d1e8bff

Operating system

NixOS

Current Behavior

I have the following attributes and calculations:

  attributes do
    integer_primary_key :id

    attribute :bar, AshPostgres.Timestamptz, allow_nil?: false
  end

  calculations do
    calculate :past_bar?, :boolean, expr(bar <= now())
  end

I then have this test:

  alias AshTimestamptzError.Foos.Foo

  test "recently created foo should have true `past_bar?`" do
    foo =
      Ash.create!(Foo, %{bar: DateTime.utc_now()})

    assert Ash.get!(Foo, foo.id, load: :past_bar?).past_bar?
  end

When running this on a machine that has the clock set to UTC+1, this test fails, because the query to load past_bar? looks like this:

14:15:37.018 [debug] QUERY OK source="foos" db=4.9ms queue=0.8ms
SELECT f0."id", f0."bar", (f0."bar"::timestamp <= $1::timestamp::timestamp)::boolean FROM "foos" AS f0 WHERE (f0."id"::bigint = $2::bigint) LIMIT $3 [~U[2025-11-14 13:15:37.007177Z], 5, 2]

Note that bar and the input timestamp ($1) are being cast to timestamp, throwing away time zone information. Instead, they should've been cast to timestamptz, and indeed that's what I thought would happen, since I am using AshPostgres.Timestamptz as the type.

Reproduction

I have a created a replication repo here: Munksgaard/ash_timestamptz_error@d51a0ca.

However, do note that you probably need to be using a UTC+ timezone, e.g. it won't work on a computer/postgres instance set to run with US timezone.

Expected Behavior

The database query should detect that bar is a AshPostgres.Timestamptz and cast it (and the input timestamp) to timestamptz.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions