Get Date From Timestamp In Postgresql

8 min read

Working with temporal data is a fundamental aspect of database management, and PostgreSQL offers one of the most dependable sets of tools for handling it. A common requirement developers face is isolating the calendar date from a full TIMESTAMP value, which includes both date and time components down to microseconds. Whether you are generating daily sales reports, partitioning data by day, or simply displaying a user-friendly birthdate, knowing how to efficiently get date from timestamp in PostgreSQL is essential for writing clean, performant queries.

This guide explores the various methods available, ranging from standard SQL casting to PostgreSQL-specific shortcuts, along with performance considerations and timezone nuances that often trip up even experienced engineers.

Understanding the Data Types

Before diving into the syntax, it is crucial to distinguish between the primary temporal data types involved.

  • TIMESTAMP (or TIMESTAMP WITHOUT TIME ZONE): Stores a date and time (year, month, day, hour, minute, second, fractional seconds) but does not store any timezone information. It represents a "local" time.
  • TIMESTAMPTZ (or TIMESTAMP WITH TIME ZONE): Stores a date and time internally in UTC. When you insert a value with a timezone offset, PostgreSQL converts it to UTC for storage. Upon retrieval, it converts it back to the session's TimeZone setting.
  • DATE: Stores only the calendar date (year, month, day) without any time component or timezone awareness.

The conversion logic differs slightly depending on whether your source column is a TIMESTAMP or TIMESTAMPTZ, primarily because of how timezones affect the "current date."

Method 1: The Standard SQL CAST Operator

The most portable and ANSI-SQL compliant way to extract the date is using the CAST function. This syntax works across almost all relational databases (MySQL, SQL Server, Oracle, PostgreSQL), making it the safest choice for cross-database compatibility The details matter here. And it works..

SELECT CAST(created_at AS DATE) AS created_date
FROM events;

How it works:

  • For TIMESTAMP WITHOUT TIME ZONE: It simply truncates the time portion. 2023-10-25 14:30:00 becomes 2023-10-25.
  • For TIMESTAMPTZ: It converts the UTC stored value to the current session timezone (defined by the TimeZone parameter) and then extracts the date.

Why this matters: If your server runs in UTC but your user session is set to America/New_York, a TIMESTAMPTZ value stored as 2023-10-26 01:00:00+00 (UTC) will be cast to 2023-10-25 in New York (which is UTC-4). This is usually the desired behavior for user-facing reports but can cause confusion if you expect the "raw" UTC date Simple as that..

Method 2: The PostgreSQL :: Cast Operator

PostgreSQL provides a historical, shorthand syntax for casting using the double-colon :: operator. It is functionally identical to CAST() but is more concise and idiomatic to Postgres.

SELECT created_at::DATE AS created_date
FROM events;

This is widely preferred in the PostgreSQL community for its readability and brevity. It follows the exact same timezone conversion rules as the standard CAST function when applied to TIMESTAMPTZ.

Method 3: The DATE_TRUNC Function

While casting is the direct way to change the data type, DATE_TRUNC is a powerful function used to truncate a timestamp to a specific precision (year, month, day, hour, etc.). It returns a TIMESTAMP (or TIMESTAMPTZ), not a DATE type, but it effectively "zeros out" the time component.

SELECT DATE_TRUNC('day', created_at) AS day_start
FROM events;

Output: 2023-10-25 00:00:00 (or 2023-10-25 00:00:00+00 for timestamptz).

When to use this:

  • When you need to group by day but want to keep the result as a timestamp for further time-series calculations.
  • When you need the start of the day (midnight) rather than just the date label.
  • It is often faster for GROUP BY operations on indexed timestamp columns because the database can potentially use an index on the expression DATE_TRUNC('day', column) whereas casting to DATE might require a separate functional index.

Method 4: The EXTRACT Function

The EXTRACT function pulls specific sub-fields (like year, month, day) from a temporal value. It returns a NUMERIC (or DOUBLE PRECISION in older versions), not a DATE type.

SELECT EXTRACT(DAY FROM created_at) AS day_num,
       EXTRACT(MONTH FROM created_at) AS month_num,
       EXTRACT(YEAR FROM created_at) AS year_num
FROM events;

Use case: This is rarely used to "get the date" as a single object. Instead, it is used when you need to filter or aggregate by specific date parts (e.g., "Show me all records for the 15th of any month" or "Group by ISO week number").

The Critical Timezone Trap (TIMESTAMPTZ vs TIMESTAMP)

This is the single most common source of bugs when extracting dates in PostgreSQL.

Scenario

Imagine a global application. The database server runs in UTC. A user in Los Angeles (UTC-7) creates a record at 2023-11-05 22:00:00 local time Most people skip this — try not to..

  • The TIMESTAMPTZ column stores this as 2023-11-06 05:00:00+00 (UTC).
  • The TIMESTAMP column (if used incorrectly) stores 2023-11-05 22:00:00 (no zone info).

Querying the Date

-- Session TimeZone is 'America/Los_Angeles'
SELECT created_at_tz::DATE, created_at_no_tz::DATE
FROM events;

Results:

  1. created_at_tz::DATE -> 2023-11-05 (Correct for the user). Postgres converts UTC 05:00 back to LA 22:00 previous day, then extracts date.
  2. created_at_no_tz::DATE -> 2023-11-05 (Coincidentally correct here, but dangerous). It just truncates the stored literal time.

The Danger Zone

Now, run the exact same query from a reporting script where the session TimeZone is set to UTC (default for many drivers) That alone is useful..

Results:

  1. created_at_tz::DATE -> 2023-11-06. The UTC time is 05:00 on the 6th. The date flips!
  2. created_at_no_tz::DATE -> 2023-11-05. It remains static because it has no timezone awareness.

Best Practice: Always be explicit about the timezone context when casting TIMESTAMPTZ to DATE. If you want the date in a specific

Best Practice: Always be explicit about the timezone context when casting TIMESTAMPTZ to DATE. If you want the date in a specific zone (e.g., the user's local timezone or a business reporting zone like 'America/New_York'), use the AT TIME ZONE construct before casting:

-- Force interpretation in a specific zone regardless of session setting
SELECT (created_at_tz AT TIME ZONE 'America/Los_Angeles')::DATE AS user_local_date
FROM events;

This converts the TIMESTAMPTZ to a TIMESTAMP WITHOUT TIME ZONE representing the wall-clock time in the target zone, then truncates to the date. This guarantees that 2023-11-06 05:00:00+00 reliably becomes 2023-11-05 for your Los Angeles reports, even if the database session is running in UTC.

Performance & Indexing Considerations

Choosing a method isn't just about syntax—it dictates whether your queries use indexes.

Method Expression Index Strategy
Cast (::DATE) created_at::DATE Requires a functional index: CREATE INDEX ON events ((created_at::DATE));
DATE_TRUNC DATE_TRUNC('day', created_at) Requires functional index on the exact expression: CREATE INDEX ON events (DATE_TRUNC('day', created_at));
EXTRACT EXTRACT(DAY FROM created_at) Cannot be easily indexed for range scans. Use only for filtering/grouping, not for driving index lookups on large tables.

Pro Tip: If you frequently query "today's data" or "last 7 days," avoid wrapping the column in a function in the WHERE clause (e.g., WHERE created_at::DATE = CURRENT_DATE). This prevents standard B-Tree index usage unless a matching functional index exists. Instead, use range queries on the raw timestamp column, which are SARGable (Search ARGument Able):

-- Fast: Uses standard index on created_at
WHERE created_at >= '2023-11-05' 
  AND created_at <  '2023-11-06'  -- Exclusive upper bound

Summary Cheat Sheet

Goal Recommended Syntax Returns
Current Date (Session TZ) CURRENT_DATE DATE
Current Date (UTC) (NOW() AT TIME ZONE 'UTC')::DATE DATE
Strip Time (Keep TZ Context) timestamptz_col::DATE DATE
Strip Time (Force Specific TZ) (timestamptz_col AT TIME ZONE 'Zone')::DATE DATE
Truncate to Midnight (TS) DATE_TRUNC('day', timestamp_col) TIMESTAMP
Truncate to Midnight (TSTZ) DATE_TRUNC('day', timestamptz_col) TIMESTAMPTZ
Get Part for Grouping EXTRACT(DOW FROM date_col) NUMERIC

Conclusion

PostgreSQL’s richness in temporal types is a double-edged sword: it offers precision but demands intent. The "correct" way to get a date depends entirely on what that date represents Easy to understand, harder to ignore..

  • Use CURRENT_DATE for application logic tied to the user's "today."
  • Use ::DATE for simple display or grouping when the session timezone is guaranteed correct.
  • Use AT TIME ZONE 'Zone' ::DATE for canonical business reporting (e.g., "Sales per day in HQ time") to eliminate timezone drift bugs.
  • Use DATE_TRUNC when you need to preserve the timestamp type for window functions or continuous time-series math.
  • Use Range Queries (>= / <) on raw columns for high-performance filtering.

Mastering these distinctions transforms date handling from a source of subtle production bugs into a reliable, predictable foundation for your data layer The details matter here..

Just Shared

Newly Added

Readers Went Here

Covering Similar Ground

Thank you for reading about Get Date From Timestamp In Postgresql. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
⌂ Back to Home