What Is a PostgreSQL Timestamp?

PostgreSQL features two primary timestamp data types to store combined dates and times: TIMESTAMP (without time zone) and TIMESTAMPTZ (with time zone). Both data types require 8 bytes of storage and offer a precision of up to 1 microsecond.

The main differences:

Feature TIMESTAMP (without time zone) TIMESTAMPTZ (with time zone)
How it’s Stored Stores the exact text/literal date and time input. Converts input to UTC internally for storage.
Time zone awareness Completely ignores time zone offsets or session contexts. Dynamically shifts based on the client session time zone.
Best used for Abstract or local generic wall-clock times (e.g., “Store opens at 9:00 AM everywhere”). Specific, real-world chronological instants (e.g., “Order placed at 14:32:01 UTC”).

Note: Writing just timestamp defaults to timestamp without time zone. To build global applications, the community universally recommends using TIMESTAMPTZ.

Timestamps are essential for tracking events, recording history, and enabling time-based queries within databases. They allow applications to store when a record was created, modified, or when an event occurred, making them fundamental for auditing, logging, and scheduling tasks.

PostgreSQL Timestamp Data Types

timestamp without time zone

The timestamp without time zone data type stores a date and time value without any time zone information. PostgreSQL records the exact date and time provided and does not perform any time zone conversion when the value is stored or retrieved.

This type is useful when the stored value represents a local time that should remain unchanged regardless of the user’s location. Common examples include business hours, appointment times in a specific location, or historical records where time zone context is not required.

Example:

timestamp with time zone / timestamptz

The timestamp with time zone data type, commonly abbreviated as timestamptz, stores a point in time and normalizes it to UTC internally. When a value is inserted, PostgreSQL converts it based on the specified time zone or the current session time zone.

When queried, PostgreSQL converts the stored value to the session’s configured time zone for display. This ensures that the same moment in time can be viewed correctly by users in different regions.

Example:

timestamp vs. timestamptz

The main difference between timestamp and timestamptz is how PostgreSQL handles time zones. A timestamp stores only the date and time as entered, while a timestamptz stores an absolute point in time and automatically manages time zone conversions.

Use timestamp when the value should always represent a local time without conversion. Use timestamptz when recording events, transactions, logs, or any data that may be accessed across different time zones.

For most applications that track real-world events, timestamptz is the recommended choice because it prevents ambiguity and ensures consistent time calculations across systems and locations.

Related content: Read our guide to PostgreSQL data types and how they map to SQL, JDBC, and Java.

Creating and Storing Timestamp Columns

Creating timestamp columns

Timestamp columns are created by specifying either the timestamp or timestamptz data type when defining a table. The choice depends on whether the application needs time zone awareness.

Example using timestamp:

Example using timestamptz:

Both data types support date and time values with microsecond precision. PostgreSQL also allows an optional precision value, such as TIMESTAMP(3), to control the number of fractional second digits stored.

Using DEFAULT now()

A common practice is to automatically populate timestamp columns with the current date and time when a row is inserted. This is typically done using the DEFAULT now() expression.

Example:

When a new row is inserted without specifying created_at, PostgreSQL automatically uses the current timestamp:

The now() function returns the current transaction timestamp. Other functions such as CURRENT_TIMESTAMP provide equivalent behavior and are commonly used for the same purpose.

created_at and updated_at Patterns

Many applications include created_at and updated_at columns to track when records are created and last modified. These columns simplify auditing, debugging, and synchronization tasks.

A typical table definition looks like this:

The created_at value is usually set once when the row is inserted. The updated_at value should be refreshed whenever the row changes. This is commonly implemented with an UPDATE statement or a database trigger that automatically sets updated_at = now() before each update.

Using this pattern provides a reliable history of record creation and modification times without requiring application code to manage timestamps manually.

Tips from the expert

Perry Clark

Perry Clark

Professional Services Consultant

Perry Clark is a seasoned open source consultant with NetApp. Perry is passionate about delivering high-quality solutions and has a strong background in various open source technologies and methodologies, making him a valuable asset to any project.

In my experience, here are tips that can help you better work with PostgreSQL timestamps:

  1. Use clock_timestamp() when measuring query durations: now() and CURRENT_TIMESTAMP are fixed at the start of the transaction. If you need the actual wall-clock time during long-running operations, use clock_timestamp(), otherwise duration calculations inside a transaction can be misleading.
  2. Store the user’s IANA time zone separately: Even when using TIMESTAMPTZ, store values like America/New_York or Europe/Berlin in a separate column when user-local rendering matters. UTC alone cannot reconstruct future daylight-saving behavior if business rules depend on the user’s locale.
  3. Partition large event tables by timestamp: For billions of rows, native range partitioning on created_at often provides larger gains than indexing alone. Monthly or daily partitions allow PostgreSQL to eliminate entire partitions during scans.
  4. Be careful with daylight-saving transitions: Local times such as 2026-11-01 01:30 may occur twice in some regions. When processing financial transactions, audit logs, or scheduling systems, always convert to UTC before performing comparisons or duration calculations.
  5. Use generated columns for reporting periods: If analysts frequently group by date, week, or month, consider generated columns such as created_date DATE GENERATED ALWAYS AS (...) STORED. This can reduce repeated computation and simplify indexing strategies.

Inserting and Updating Timestamp Values

Inserting timestamp literals

Timestamp values can be inserted directly using string literals in a format that PostgreSQL recognizes. The most common format is YYYY-MM-DD HH:MI:SS.

Example:

You can also use the TIMESTAMP keyword to make the data type explicit:

PostgreSQL supports fractional seconds as well:

Inserting Timestamps with Time Zone Offsets

When working with timestamptz columns, you can include a time zone offset in the inserted value. PostgreSQL converts the value to UTC internally while preserving the exact point in time.

Example:

You can also specify the offset using hours and minutes:

Named time zones are supported as well:

Regardless of the format used, PostgreSQL stores the same absolute moment and converts it to the session time zone when queried.

Updating Timestamp Columns

Timestamp columns can be updated using standard UPDATE statements. This is often used to record when an event occurred or when data was modified.

Example:

You can also assign a specific timestamp value:

Updates can target multiple rows based on conditions, making it easy to maintain time-related data across a table.

Auto-Updating updated_at

A common requirement is to automatically update an updated_at column whenever a row changes. PostgreSQL does not provide this behavior automatically, but it can be implemented with a trigger.

First, create a trigger function:

Then create a trigger that runs before each update:

With this setup, the updated_at column is automatically refreshed whenever a row is modified, ensuring that the timestamp always reflects the most recent change.

 

Common PostgreSQL Timestamp Operations and Functions

Fetching the Current Time

PostgreSQL provides several functions for retrieving the current date and time. These functions are commonly used when inserting records, generating reports, or performing time-based calculations.

Get the current timestamp:

Using the SQL-standard equivalent:

Get only the current date:

Get only the current time:

For most use cases, now() and CURRENT_TIMESTAMP are interchangeable and return the current date and time with time zone information.

Shifting Time Zones (AT TIME ZONE)

The AT TIME ZONE operator converts timestamps between time zones. It is useful when displaying data for users in different regions or when converting stored values to a specific local time.

Convert a timestamptz value to a local time zone:

Interpret a timestamp as belonging to a specific time zone:

This operator helps ensure that timestamps are displayed correctly while preserving the underlying point in time.

Truncating Timestamps

The date_trunc() function rounds timestamps down to a specified unit such as hour, day, month, or year. It is frequently used for grouping and reporting.

Truncate to the hour:

Truncate to the day:

Truncate to the month:

A common use case is aggregating records by day or month:

Formatting and Parsing

PostgreSQL provides the to_char() function for formatting timestamps as strings and to_timestamp() for converting strings into timestamp values.

Format a timestamp:

Example output:

Convert a string to a timestamp:

These functions are useful when importing data, generating reports, or creating custom date and time formats.

Extracting Date Parts

The extract() function retrieves individual components from a timestamp, such as the year, month, day, or hour.

Extract the year:

Extract the month:

Extract the day of the week:

Extract the hour:

This function is commonly used for filtering, grouping, and analyzing time-based data. For example, you can count events by month, identify activity by hour, or generate reports based on specific date components.

Querying and Comparing PostgreSQL Timestamps

PostgreSQL supports a range of operators for filtering, comparing, and calculating timestamp values. These operations are commonly used in reporting, auditing, monitoring, and time-based application logic.

Filter records after a specific timestamp:

Filter records within a date range:

Find records created in the last 24 hours:

Find records older than 30 days:

Timestamp values can also be compared directly using standard comparison operators:

Operator Description
= Equal to
!= or <> Not equal to
< Earlier than
> Later than
<= Earlier than or equal to
>= Later than or equal to

For example:

PostgreSQL allows arithmetic operations with timestamps and intervals. Subtracting two timestamps returns the time difference between them:

Example result:

Adding or subtracting intervals shifts a timestamp by a specified amount of time:

When querying large tables, timestamp columns are often indexed to improve performance:

Indexes can significantly speed up range queries and time-based filtering, especially for tables that store large volumes of historical data.

PostgreSQL Timestamp Performance Tips

Here are some tips to consider when using timestamps in PostgreSQL.

1. Indexing Timestamp Columns

Timestamp columns used in filters, joins, or sorting should usually be indexed. This is especially important for tables that store events, logs, orders, or audit records.

Example:

This index helps queries that search by time range:

 B-tree indexes are the default and work well for most timestamp comparisons, including =, <, >, <=, >=, and ORDER BY.

2. Writing Efficient Timestamp Range Queries

Use half-open ranges when filtering timestamp values. A half-open range includes the start time and excludes the end time.

Instead of this:

 Use this:

This avoids missing rows that occur later on the final day, such as 2025-05-31 18:45:00. It also avoids relying on artificial end values like 23:59:59.999999.

3. Avoiding Functions on Indexed Timestamp Columns

Applying a function to an indexed timestamp column can prevent PostgreSQL from using a normal index efficiently.

Avoid this pattern:

Use a range condition instead:

The second query allows PostgreSQL to use an index on created_at directly. This pattern is usually faster on large tables.

4. Using Partial Indexes for Recent Data

Partial indexes index only rows that match a condition. They are useful when most queries target recent data, such as the last few days or months.

Example:

Queries that match the same condition can use the smaller index:

Partial indexes reduce index size and maintenance cost, but the condition should match real query patterns. Avoid using moving expressions such as now() in the index predicate, because the index definition does not automatically move forward over time.

5. Using BRIN Indexes for Append-Only Time-Series Tables

BRIN indexes are useful for very large tables where rows are inserted in timestamp order. They store summaries of page ranges instead of indexing every row, so they are much smaller than B-tree indexes.

Example:

BRIN indexes work well for append-only tables such as logs, metrics, and event streams:

They are less precise than B-tree indexes, but they can greatly reduce scanning on large time-series tables. For smaller tables or highly selective lookups, a B-tree index is often better.

Managing time-based PostgreSQL data at scale with Instaclustr

Whether you are storing audit trails, event logs, or transaction histories with TIMESTAMPTZ, getting reliable, performant time-based data in production depends on a well-run database. Instaclustr customizes and optimizes the configuration of PostgreSQL (also known as Postgres) instances on all major cloud providers and on-premises data centers, delivering a production-ready, fully hosted and managed PostgreSQL cluster backed by 24×7 support so your team can focus on building applications instead of operating infrastructure.

Key capabilities of Instaclustr for PostgreSQL:

  • Fully managed, 100% open source: Run PostgreSQL in your own cloud provider account or Instaclustr’s, with 24×7 support, built-in monitoring, and SOC2, ISO27001, and ISO27018 certifications, provisioned via console, API, or Terraform provider with no proprietary lock-in.
  • 99.99% availability SLA: Industry-leading SLAs for PostgreSQL that set the managed platform apart for production workloads.
  • Multi-region replication: Read replicas can be created in secondary regions for high availability, minimizing latency and maximizing uptime across geographies.
  • Multi-version concurrency control (MVCC): PostgreSQL snapshots data as it was at the start of each transaction, enabling reads without locking and significantly enhanced read/write performance, which keeps time-stamped, concurrent writes consistent.
  • PGBouncer connection pooling: A lightweight connection pooler that enhances database performance and scalability through efficient connection management, scalable performance, and resource optimization.
  • Continuous maintenance and DevOps-friendly access: Automated provisioning and configuration, continuous maintenance and version upgrades, and REST API or Terraform provisioning with Prometheus and REST-based monitoring integrations.

Eliminate the burden of self-managed PostgreSQL and run reliable, time-aware databases in the cloud or on-premises. Learn more about managed PostgreSQL on the Instaclustr platform.