This can use the index:
SELECT * FROM some_table WHERE expires_at < date_trunc('day', (now() AT TIME ZONE 'America/New_York')) AT TIME ZONE 'America/New_York' -- ORDER BY expires_at --!!? LIMIT 5;
fiddle - proving equivalence
You may want to add ORDER BY expires_at or ORDER BY expires_at DESC to get deterministic results (and still use the index).
Wait ... what?
The manual:
The function timezone(zone, timestamp) is equivalent to the SQL-conforming construct timestamp AT TIME ZONE zone.
So this is your query in a more commonly used form:
SELECT * FROM some_table WHERE (expires_at AT TIME ZONE 'America/New_York')::date < (CURRENT_TIMESTAMP AT TIME ZONE 'America/New_York')::date LIMIT 5
(The cast and LIMIT are still Postgres-specific, the rest is now standard SQL.)
See:
To make the index applicable, you need a "sargable" expression, i.e. Postgres must be able to place the indexed term on the left side of an applicable operator, and a stable value to the right. See:
It may help to express your objective in plain English:
Get rows where expires_at adjusted to the time zone 'America/New_York' falls before 00:00 hours of the current day at that time zone.
This can be broken down into 4 steps:
Take the current timestamp with time zone:
now()
Get the according local timestamp without time zone for New York:
now() AT TIME ZONE 'America/New_York'
Truncate it to the start of the day (still timestamp without time zone):
date_trunc('day', (now() AT TIME ZONE 'America/New_York'))
Get the according timestamp with time zone:
date_trunc('day', (now() AT TIME ZONE 'America/New_York')) AT TIME ZONE 'America/New_York'
test=> SELECT now() AS step1 test-> , now() AT TIME ZONE 'America/New_York' AS step2 test-> , date_trunc('day', (now() AT TIME ZONE 'America/New_York')) AS step3 test-> , date_trunc('day', (now() AT TIME ZONE 'America/New_York')) AT TIME ZONE 'America/New_York' AS step4; step1 | step2 | step3 | step4 ------------------------------+---------------------------+---------------------+------------------------ 2022-05-21 19:52:34.23824+02 | 2022-05-21 13:52:34.23824 | 2022-05-21 00:00:00 | 2022-05-21 06:00:00+02 (1 row)
Keep in mind that timestamptz is displayed according to the current time zone setting of your session ('Europe/Vienna' in my example), which has no bearing on the value whatsoever.
There are two distinct implementations of AT TIME ZONE with text input (plus a third one for the broken timetz, which shouldn't be used): one transposing timestamp to timestamptz, and one for the reverse operation of transposing timestamptz to timestamp. My query uses both.
Likewise there are two (three) Postgres functions:
test=> SELECT proname AS func_name test-> , pg_get_function_arguments(oid) AS arguments test-> , pg_get_function_result (oid) AS result test-> FROM pg_proc test-> WHERE proname = 'timezone' test-> AND proargtypes[0] = 'text'::regtype; func_name | arguments | result -----------+-----------------------------------+----------------------------- timezone | text, timestamp without time zone | timestamp with time zone timezone | text, timestamp with time zone | timestamp without time zone timezone | text, time with time zone | time with time zone (3 rows)
Basics: