PostgreSQL NOW() Function: Transaction Time, Time Zones and CURRENT_TIMESTAMP

Verdict first: the PostgreSQL now function returns the moment your transaction started, as timestamp with time zone. It’s the same value as CURRENT_TIMESTAMP. When you need the real clock inside a long transaction, call clock_timestamp() instead.

Quick answer: SELECT now(); is all it takes. Here’s the type and the server version from my run:

SELECT now(),
       pg_typeof(now()) AS type,
       current_setting('server_version') AS version;

Output:

              now              |           type           | version 
-------------------------------+--------------------------+---------
 2026-10-28 03:25:08.459581-04 | timestamp with time zone | 18.6
(1 row)

My runs used PostgreSQL 18.6 on Windows with the session TimeZone at America/New_York, so you’ll see -04 offsets (EDT).

NOW() vs CURRENT_TIMESTAMP vs statement_timestamp() vs clock_timestamp()

You’ll read that now() is “the current time.” Not once a transaction spans two statements. Same transaction: four functions, a two-second pg_sleep, the same four again:

\pset footer off
BEGIN;
SELECT now()::time(3)                 AS now,
       current_timestamp::time(3)     AS current_ts,
       statement_timestamp()::time(3) AS statement_ts,
       clock_timestamp()::time(3)     AS clock_ts;
SELECT pg_sleep(2) \gset
SELECT now()::time(3)                 AS now,
       current_timestamp::time(3)     AS current_ts,
       statement_timestamp()::time(3) AS statement_ts,
       clock_timestamp()::time(3)     AS clock_ts;
COMMIT;

Output:

BEGIN
     now      |  current_ts  | statement_ts |   clock_ts   
--------------+--------------+--------------+--------------
 03:25:08.561 | 03:25:08.561 | 03:25:08.561 | 03:25:08.563

     now      |  current_ts  | statement_ts |   clock_ts   
--------------+--------------+--------------+--------------
 03:25:08.561 | 03:25:08.561 | 03:25:10.576 | 03:25:10.576

COMMIT
PostgreSQL now(), current_timestamp, statement_timestamp() and clock_timestamp() before and after pg_sleep(2) in one transaction
After pg_sleep(2), now() and current_timestamp haven’t moved; statement_ts and clock_ts jumped two seconds.

now() and CURRENT_TIMESTAMP never budged. statement_timestamp() moved with the new statement, and clock_timestamp() reads the clock at each call.

Why does NOW() not change inside a loop or a big SELECT?

Because a statement never leaves its transaction. Across rows, now() repeats while clock_timestamp() ticks:

SELECT g AS row_no,
       now()::time(6)             AS now,
       clock_timestamp()::time(6) AS clock_ts
FROM generate_series(1, 2) AS g;

Output:

 row_no |       now       |    clock_ts     
--------+-----------------+-----------------
      1 | 03:25:10.676842 | 03:25:10.679198
      2 | 03:25:10.676842 | 03:25:10.679205
(2 rows)

The same holds in a PL/pgSQL FOR loop: every iteration sees one now(), so timed steps all look instant.

Where the answer flips: pick clock_timestamp() when you measure elapsed time or log steps of a long job. Keep now() when every row of one change must share a timestamp.

NOW()::date, NOW()::time and CURRENT_DATE

Cast for the date or time of day; add an interval for a future timestamp:

SELECT now()::date      AS today,
       current_date     AS current_date,
       now()::time(0)   AS time_now,
       now() + interval '30 days' AS due_in_30_days;

Output:

   today    | current_date | time_now |        due_in_30_days         
------------+--------------+----------+-------------------------------
 2026-10-28 | 2026-10-28   | 03:25:11 | 2026-11-27 03:25:10.777921-05
(1 row)

Note the -05 on the last column. Thirty days from 10/28/2026 crosses the 11/01/2026 DST change, so PostgreSQL kept the wall-clock time and shifted the offset. To cut to a day or month, use PostgreSQL date_trunc.

NOW() minus INTERVAL: rows from the last 7 days

The sample table holds six US orders, with created_at set relative to now():

CREATE TABLE orders (
  order_id   int PRIMARY KEY,
  customer   varchar(20) NOT NULL,
  state      char(2) NOT NULL,
  amount     numeric(10,2) NOT NULL,
  created_at timestamptz NOT NULL   -- 2 hours to 40 days ago
);
SELECT order_id, customer, state, amount
FROM orders
WHERE created_at >= now() - interval '7 days'
ORDER BY created_at DESC;

Output:

 order_id |  customer   | state | amount 
----------+-------------+-------+--------
      101 | Megan Bowen | TX    | 129.99
      102 | Alex Wilber | WA    |  54.50
      103 | Adele Vance | NY    | 310.00
(3 rows)

Order 104, seven days and three hours old, falls out. For whole calendar days, compare with current_date - 7 (midnight). More patterns: PostgreSQL dates between two dates.

NOW() as a column DEFAULT, and the ‘now’ string trap

DEFAULT now() runs at insert time. The string 'now' is converted once, at CREATE TABLE. I waited three seconds before inserting:

CREATE TABLE signups (
  id         int GENERATED ALWAYS AS IDENTITY,
  email      varchar(30) NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now(),
  frozen_at  timestamptz DEFAULT 'now'
);
SELECT pg_sleep(3) \gset
INSERT INTO signups (email) VALUES ('megan.bowen@example.com');
SELECT email, created_at - frozen_at AS gap
FROM signups;

Output:

CREATE TABLE
INSERT 0 1
          email          |       gap       
-------------------------+-----------------
 megan.bowen@example.com | 00:00:03.014014
(1 row)
PostgreSQL DEFAULT now() column versus DEFAULT 'now' literal frozen at table creation
The ‘now’ default is 3 seconds older than the insert: it kept the CREATE TABLE time.

On a real table that gap grows to months. To fix a column, use ALTER TABLE with ALTER COLUMN frozen_at SET DEFAULT now().

NOW() time zone: SET TIME ZONE and AT TIME ZONE

now() is an absolute instant; the session TimeZone (America/New_York on my demo database) only decides how it prints:

\pset footer off
SELECT now()::timestamp(0) AS new_york,
       (now() AT TIME ZONE 'UTC')::timestamp(0) AS utc;
SET TIME ZONE 'America/Chicago';
SELECT now()::timestamptz(0) AS now_tz,
       now()::timestamp(0)   AS now_cast;

Output:

      new_york       |         utc         
---------------------+---------------------
 2026-10-28 03:25:14 | 2026-10-28 07:25:14

SET
         now_tz         |      now_cast       
------------------------+---------------------
 2026-10-28 02:25:14-05 | 2026-10-28 02:25:14
PostgreSQL now() shown in New York and UTC with AT TIME ZONE, then after SET TIME ZONE America/Chicago
AT TIME ZONE gives wall-clock times; after SET TIME ZONE, the same instant prints as Chicago time.

AT TIME ZONE returns a plain timestamp in that zone. The catch: now()::timestamp follows the session, so Chicago got an hour earlier. Store that in a timestamp column and two sessions disagree.

That’s why I keep timestamptz columns. For text input, PostgreSQL to_timestamp parses strings into it.

NOW() precision: now(0) fails, CURRENT_TIMESTAMP(0) rounds

now() takes no arguments. The three ways to drop microseconds don’t agree:

SELECT current_timestamp(0)      AS rounded,
       date_trunc('second', now()) AS truncated,
       now()::timestamptz(0)     AS cast_0;
SELECT now(0);

Output and error:

        rounded         |       truncated        |         cast_0         
------------------------+------------------------+------------------------
 2026-10-28 03:25:14-04 | 2026-10-28 03:25:14-04 | 2026-10-28 03:25:14-04
(1 row)

psql:now-precision-milliseconds.sql:4: ERROR:  function now(integer) does not exist
LINE 1: SELECT now(0);
               ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
PostgreSQL CURRENT_TIMESTAMP(0) rounds, date_trunc truncates, and now(0) fails with function now(integer) does not exist
CURRENT_TIMESTAMP(0) and ::timestamptz(0) round to the nearest second; date_trunc always cuts down.

Rounding can land on the next second; truncating never does. If a report must never show a time later than the event, use date_trunc.

PostgreSQL current time functions compared

FunctionReturnsValue is fixed at
now()timestamptztransaction start
current_timestamp, transaction_timestamp()timestamptztransaction start
statement_timestamp()timestamptzstatement start
clock_timestamp()timestamptznever (every call)
timeofday()textnever (every call)
current_datedatetransaction start
localtimestamptimestamptransaction start

Speed isn’t a reason to pick one. For the SQL-standard spelling, see PostgreSQL CURRENT_TIMESTAMP.

Common mistakes with PostgreSQL NOW()

  • Timing a long transaction with now(): the end time equals the start time.
  • DEFAULT 'now' instead of DEFAULT now(), so every row gets the CREATE TABLE time.
  • Casting to timestamp and forgetting the session TimeZone decides the wall clock.

My rule of thumb: use now() when rows in one change must agree, clock_timestamp() when you’re measuring, and timestamptz columns everywhere. To measure the gap between two values, read PostgreSQL difference between two timestamps.

Next step, depending on what you’re building:

Frequently Asked Questions

Is NOW() the same as CURRENT_TIMESTAMP in PostgreSQL?

Yes. Both equal transaction_timestamp(). CURRENT_TIMESTAMP is the SQL-standard name, written without parentheses, and it accepts an optional precision (PostgreSQL 18 Current Date/Time docs).

How do I get the current UTC time in PostgreSQL?

Use now() AT TIME ZONE ‘UTC’, or the function form timezone(‘UTC’, now()). Both return a timestamp without time zone.

Does NOW() use the server clock or the client’s time zone?

The server clock supplies the instant. The session TimeZone, which a client can change with SET TIME ZONE or the PGTZ environment variable, only changes how it’s displayed.

What does timeofday() return in PostgreSQL?

The actual current time as a text string, like clock_timestamp() but not a timestamptz. It’s kept for backward compatibility.