Dates and times

What is GETDATE() in PostgreSQL?

By Owen Middleton · Updated September 2026 · Examples run on PostgreSQL 17

PostgreSQL has no GETDATE(). Use now() for the current date and time, or current_date for the date alone. Write current_date with no parentheses at all: the grammar gives it no argument list, so even current_date(3) is a syntax error. now() keeps its own.

PostgreSQL fixes now() to the time the current transaction started, so you read the same value for every row and every statement in that transaction. When you need the clock to keep moving, use clock_timestamp().

The queries that read a table read one, orders: one row per order, with ordered_at stored as a timestamp with time zone. Its columns are below, so you can see where every value on this page comes from.

Paste any query on this page into an editor here and run it. There are two exercises at the end. See our other guides to SQL dates in PostgreSQL.

Schema · ecommerce1 table? = nullable
orders
idinteger
customer_id?integer
ordered_attimestamptz
statustext
total_amountnumeric
ordersfirst_orderlast_order
200 2022-02-10 2025-03-22
the shape of the table every result below is drawn from

What happens if you run GETDATE() in PostgreSQL?

Run it and you get an error, because PostgreSQL has no function by that name:

SELECT GETDATE();
PostgreSQL responds function getdate() does not exist

You can get this wrong in two more ways. Without the parentheses, PostgreSQL looks for a column:

SELECT GETDATE;
PostgreSQL responds column "getdate" does not exist

And current_date is a reserved SQL keyword rather than a function name, so there is no argument list to write:

SELECT current_date();
PostgreSQL responds syntax error at or near "("

That rule is narrower than it sounds. The neighbouring keywords take an optional precision, so current_timestamp(3) and localtimestamp(3) both parse. current_date takes no argument at all, so current_date(3) fails the same way current_date() does, and empty parentheses are a syntax error after every one of them.

We seeded the editor below with the failing query. Delete it and run the commented line to see your own clock. You get UTC back, because we pin this sandbox to UTC.

-- PostgreSQL has no GETDATE(). Run this line instead:
-- SELECT now(), current_date, clock_timestamp();
SELECT GETDATE();

What is the difference between now() and current_date?

Run now() and you get a timestamp with a time zone. Run current_date and you get a date, with no time at all. PostgreSQL ships a family of these. Here are six, and the type you get back:

SELECT fn, type
FROM (VALUES ('now()',             pg_typeof(now())::text),
             ('current_timestamp', pg_typeof(current_timestamp)::text),
             ('current_date',      pg_typeof(current_date)::text),
             ('localtimestamp',    pg_typeof(localtimestamp)::text),
             ('clock_timestamp()', pg_typeof(clock_timestamp())::text),
             ('timeofday()',       pg_typeof(timeofday())::text)) AS t(fn, type);
fntype
now() timestamp with time zone
current_timestamp timestamp with time zone
current_date date
localtimestamp timestamp without time zone
clock_timestamp() timestamp with time zone
timeofday() text
the types only, because a clock value printed here would be the moment we built the page

now() and current_timestamp are the same thing under two names. localtimestamp is the same instant read in your session’s zone, with the time zone dropped. Run timeofday() and you get text, not a timestamp. Avoid it for anything you plan to compare or sort.

Four more are worth knowing. Run current_time and you get a time with time zone, run localtime and you get a time without time zone, and run transaction_timestamp() or statement_timestamp() and you get a timestamp with time zone.

Why does now() return the same time for a whole transaction?

Because PostgreSQL defines now() as the time the transaction started, and holds it there until the transaction ends, however long that takes. Read it 200,000 times in a single statement and it answers with one value, while clock_timestamp() keeps moving across the same scan:

SELECT count(DISTINCT now()) AS distinct_now_values,
       count(DISTINCT clock_timestamp()) > 1 AS clock_moved,
       count(*) AS rows_scanned
FROM generate_series(1, 200000);
distinct_now_valuesclock_movedrows_scanned
1 true 200000
200,000 rows, one value from now(), more than one from clock_timestamp()

A bare statement is its own transaction, so run SELECT now() twice in the editor above and you get two different times. That is not a contradiction. In your own client, put both statements between BEGIN and COMMIT and you get one time for both.

PostgreSQL confirms what now() is tied to:

SELECT now() = transaction_timestamp() AS same_as_transaction_start,
       now() = current_timestamp AS same_as_current_timestamp;
same_as_transaction_startsame_as_current_timestamp
true true

This is usually what you want: you stamp every row in one transaction with the same timestamp, and you read one consistent "now" through a long report. Use clock_timestamp() when you are timing something inside a transaction. The PostgreSQL documentation lists the rest of the family.

How do you filter rows from today in SQL?

Compare against a range, not a single date. An equality test against current_date is the commonest way to lose today's rows. Treating 2025-03-20 as today, a day holding two orders, the obvious filter returns nothing:

SELECT id, ordered_at
FROM orders
WHERE ordered_at = DATE '2025-03-20';
idordered_at
0 rows, although two orders were placed that day

No error, and no rows. PostgreSQL reads the date as midnight at the start of that day and compares the whole timestamp against it, so you match only an order placed at exactly 00:00:00:

SELECT DATE '2025-03-20' = TIMESTAMPTZ '2025-03-20 00:00:00' AS at_midnight,
       DATE '2025-03-20' = TIMESTAMPTZ '2025-03-20 09:00:00' AS at_nine;
at_midnightat_nine
true false

You hit exactly this every day you write WHERE ordered_at = current_date. Two fixes. Cast the timestamp to a date:

SELECT id, ordered_at
FROM orders
WHERE ordered_at::date = DATE '2025-03-20'
ORDER BY id;
idordered_at
145 2025-03-20 09:00:00+00
200 2025-03-20 11:00:00+00
both orders

Or ask for everything from midnight up to, but not including, the next midnight:

SELECT id, ordered_at
FROM orders
WHERE ordered_at >= DATE '2025-03-20'
  AND ordered_at < DATE '2025-03-20' + 1
ORDER BY id;
idordered_at
145 2025-03-20 09:00:00+00
200 2025-03-20 11:00:00+00
the same two orders

You get the same rows either way. The range form is the one to reach for, because it leaves the column untouched and an ordinary index on ordered_at can serve it. Wrapping the column in a cast puts that index out of reach: an index has to be built on the same expression the query writes.

You can index the cast, but only after you pin the zone. CREATE INDEX ON orders((ordered_at::date)) fails with functions in index expression must be marked IMMUTABLE, because casting a timestamptz to a date reads your session time zone and so returns different answers in different sessions. Name the zone inside the expression and PostgreSQL builds the index: CREATE INDEX ON orders (((ordered_at AT TIME ZONE 'UTC')::date)). date_trunc('day', ordered_at, 'UTC') takes its zone as an argument the same way, and is indexable for the same reason, on PostgreSQL 16 and later where that third argument exists. Either way the query then has to spell the expression exactly as the index does, which is the cost: everyone reading the table has to write the filter your way.

For today's rows, write ordered_at >= current_date AND ordered_at < current_date + 1. Run that against the table on this page and it returns nothing, because these orders stop in March 2025 and current_date is today: the shape of the filter is the point, not the row count.

Try it: change ordered_at = to ordered_at::date = and run it again. You go from zero rows to two.

SELECT id, ordered_at
FROM orders
WHERE ordered_at = DATE '2025-03-20';

Which time zone does now() use?

Your session’s TimeZone setting, not the location of the server. Change it and now() prints a different wall clock for the same instant, because a timestamptz is rendered in your session zone rather than stored in one. Near midnight that shift can also move current_date onto another day. This sandbox runs in UTC:

SHOW TimeZone;
TimeZone
UTC

Keep those two apart. Read now() in two zones and you get one instant printed two ways: 2025-03-20 09:00:00+00 and 2025-03-20 05:00:00-04 are equal in PostgreSQL. Read current_date in two zones and you can get two different days, because PostgreSQL takes midnight from the same setting. At any moment, current_date in Pacific/Kiritimati is a later date than in Pacific/Niue, so you can run one report in two zones and put the same order on different days.

Run SET TIME ZONE 'America/New_York' in your own client to change it for the rest of your session. Run it in an editor here and you change every editor on this page at once, because they share one database. We undo it before each practice check, so you cannot lose a grade to it, and that check leaves you back in UTC.

Watch one thing while it is set. We rendered every result table above in UTC, so you stop reading a timestamptz as the text those tables show: order 145 is 2025-03-20 09:00:00+00 here and 2025-03-20 05:00:00-04 in New York, one instant either way. Run SET TIME ZONE 'UTC' to go back. RESET TIME ZONE restores your server’s own default instead of the value we set, so reach for it only when you know what that default is.

To see one value in another zone without changing anything, use now() AT TIME ZONE 'America/New_York' and you get that zone’s local time back as a timestamp without a time zone. Try that in either editor above.

To work forward or back from today, add to it: see adding days and months to a date for current_date + 7, and why you get a different type from + INTERVAL '7 days'.

Practice: filter by date the safe way

You learn the difference between a date and a timestamp in the date and time types of our free SQL course, and date arithmetic is the lesson after it. No account, nothing to install, nothing to pay.