dynobase-icon
Dynobase

DynamoDB Timestamps: Store, Query, Sort and Update Dates

Charlie Fish

Written by Charlie Fish

Last updated: October 23rd, 2026

Not sure what's really in your table? Dynobase's schema explorer finds nested paths, missing attributes and mixed types in your data. Schema explorer in Dynobase

DynamoDB has no native date or timestamp data type. Store time as a Number containing Unix epoch seconds or milliseconds, or as a String in a consistent UTC ISO 8601 format. To query efficiently by time, make the timestamp a sort key under a known partition key; TTL requires a separate Number in epoch seconds when your application timestamps use another format.

Representing timestamps in DynamoDB

Choose a representation for each field and use it consistently across writers. DynamoDB compares numbers numerically and strings by their UTF-8 bytes. The attribute name does not tell DynamoDB whether a number represents seconds, milliseconds or anything else.

RepresentationExampleUseful forMain concern
Epoch seconds as Number1640995200TTL, arithmetic, second-resolution eventsMultiple events can share the same second
Epoch milliseconds as Number1640995200000Application timestamps and subsecond rangesCannot be used directly as TTL
UTC ISO 8601 as String2022-01-01T00:00:00.000ZReadable event keys and date-prefix queriesKeep time zone and precision consistent

These examples represent the same instant. Low-level DynamoDB JSON encodes a number as {"N":"1640995200000"}; the quotes around its wire value do not turn it into a DynamoDB String. The document client takes a native JavaScript number instead.

Both numeric and properly normalized ISO strings can support ordered time queries. Numbers make duration arithmetic straightforward. Strings are easier to inspect and can support begins_with on a date prefix. AWS documents these choices under supported data types.

Timestamp precision, time zones and collisions

Normalize to UTC before writing. ISO values with mixed offsets may represent the correct instant but sort differently as strings. Mixing 2026-10-23T12:00:00Z with 2026-10-23T12:00:00.100Z also breaks the expected ordering at the fractional-second boundary. Pick one fixed fractional precision and always emit it.

JavaScript's Date.now() returns epoch milliseconds. Math.floor(Date.now() / 1000) returns epoch seconds. Python's time.time() returns seconds with a fractional part; use an integer or Decimal representation suitable for your selected field rather than pass a Python float to boto3's DynamoDB serializer.

Extra precision does not ensure unique keys. If two events share the same partition key and timestamp sort key, they target the same item. Use a stable event ID suffix for string keys, a separate event identifier in the base key, or another explicit collision strategy. A condition can prevent an accidental overwrite, but it cannot store both events under the same key.

Client timestamps are also not a total ordering of distributed writes. Clocks can differ, and an event can arrive late. Keep event_time separate from ingested_at when you need both, and use a version or source sequence for concurrency rules. See data versioning.

How to store a timestamp on creation

The examples use one consistent schema:

  • Base table: Application, with string partition key uuid and no sort key.
  • Global secondary index: index, with string partition key pk and numeric sort key updated_at.
  • pk groups orders by user, such as order_A; updated_at stores epoch milliseconds.

This keeps an order's identity stable while updates change its position in the index. The index must project the attributes you expect to return. To create the initial order with SDK v3:

DynamoDB does not automatically fill created_at or updated_at. Your application or a configured mapping extension must do that. UpdateExpression has no server-side NOW() function; pass the timestamp as a value.

For a fixed UTC example, this complete low-level PutItem payload sets the time to midnight on January 1, 2022:

Choose either the dynamic or fixed example for initial creation; their shared create-only condition deliberately rejects writing the same key twice. A normal put without that condition replaces the complete item.

Querying DynamoDB based on timestamps

A timestamp stored as an ordinary attribute cannot be used as a Query key condition. You need a table or index whose key schema supports the access pattern. Otherwise you would scan and filter, paying to evaluate unrelated items.

A partition key groups the data you want to read together, while the sort key orders it. An exact timestamp as the partition key is often inconvenient because a range query still requires equality on that partition key. It is not inherently bad because it creates many small key groups. The relevant question is whether your requests can name the right group and whether writes are distributed adequately.

For events across many users, a time bucket such as month or day can be part of a grouping key. A single busy bucket can become hot; large workloads may need shards and a merge of their query results. Avoid an all-data partition just to obtain one global chronological ordering.

Querying between two timestamps

BETWEEN includes both endpoints. For integer-millisecond data, query a whole month by calculating the start of the next month and subtracting one millisecond. Ending at midnight on the last day would omit almost all of that day.

This SDK v3 function retrieves all orders for user A updated during January 2022, following every result page:

The corresponding low-level values are 1640995200000 for the start and 1643673599999 for the inclusive end. The one-millisecond subtraction is valid because this schema requires integer milliseconds. For microseconds, seconds or strings, construct boundaries for that representation rather than copying the same arithmetic.

The Query API reference specifies one sort-key condition. You cannot express a half-open interval by combining separate greater-than-or-equal and less-than sort-key comparisons in one key expression. Use an appropriate inclusive bound, a time prefix for string keys, or another access pattern.

Query the latest timestamp

For the same numeric GSI, query one user's partition in descending order:

This returns the indexed item with the highest updated_at for user B. It is not a table-wide latest-item query, and equal timestamps do not supply a meaningful tie-break order. If that matters, choose a sort-key design with an explicit tie breaker.

GSIs are eventually consistent, so the result can lag a recent base-table write. You cannot make this query strong with ConsistentRead: true. If immediate read-after-write behavior is required, read the base item by uuid or use a base-table access pattern designed for the latest state. See read consistency.

Sort or order all results by timestamp

Use ScanIndexForward: false for newest first and true for oldest first; ascending is the default. Remove Limit: 1 and follow LastEvaluatedKey to read the complete ordered group.

Sorting applies only within the queried partition and to its configured sort key. A scan does not provide chronological ordering. Also, adding a filter to a latest-item query can produce an empty page because Limit applies before filtering. Continue pagination or model the filter as part of a queryable key.

How to update a timestamp

Update the stable base-table key, not the GSI's key. This boto3 request preserves created_at while changing status and updated_at:

Because updated_at is a GSI key attribute rather than the base-table key, it can change with UpdateItem. The index reflects the change asynchronously. If the timestamp were the base table's sort key, changing it would require a put under the new key and a delete of the old item.

The existence condition avoids accidentally creating a partial order. If the state change depends on the version the user saw, add an expected version or status condition. A more precise clock does not replace optimistic locking.

Time to Live and timestamp fields

TTL consumes a Number containing epoch seconds, regardless of the units used by other timestamp fields. Keep a separate attribute such as expires_at when updated_at is in milliseconds or ISO format:

The seven-day retention is an example application policy. Enable TTL on exactly expires_at. A millisecond number is interpreted as seconds and lands far in the future; an ISO string is ignored because it has the wrong type.

Expired items are typically removed within a few days, so TTL is not an exact-time scheduler. Enforce expiration in reads or application logic when expired data must stop being used immediately. A query filter hides expired results but does not refund their read capacity. The TTL guide and epoch converter covers setup, global-table delete charges and MRSC restrictions.

Common timestamp mistakes

Keep units in field names or schema documentation, validate incoming types, and use explicit UTC offsets when parsing external dates. Never mix seconds and milliseconds in the same numeric sort-key attribute: values will be ordered by magnitude, not by the instant you intended.

Make retry behavior deliberate. Regenerating updated_at on every retry changes the request and can conflict with a transaction's idempotency token. Preserve the event's original timestamp when retrying a logical event; record a separate processing time if needed.

A field that records the latest update cannot also preserve every prior change. If audit queries need historical states, store immutable events or revisions under separate keys rather than repeatedly overwriting the same timestamped order.

Frequently Asked Questions

What data type should a DynamoDB timestamp use?

Use a Number for epoch seconds or milliseconds, or a String for normalized UTC ISO 8601. DynamoDB has no native timestamp type. Use numeric epoch seconds specifically for TTL.

Can DynamoDB query a date range?

Yes, when the date is a sort key and you supply equality on its partition key. Use a range key condition and paginate the results. An ordinary timestamp attribute cannot become a Query key condition by itself.

Does DynamoDB set created_at and updated_at automatically?

No. Your writer or configured mapping layer supplies those values. Update expressions do not offer a server-side NOW function.

How do I get the latest item by timestamp?

Query the appropriate partition with the timestamp as its sort key, ScanIndexForward set to false and Limit set to 1. Account for GSI lag and equal timestamps.

Does BETWEEN include the end date?

It includes both supplied endpoints. For integer milliseconds, a whole-month query can end one millisecond before the next month's start. Midnight on the final day does not include the rest of that day.

Dynobase is a Professional GUI Client for DynamoDB

Start your 7-day free trial today

Product Features

Download
/
Pricing
/
Member Portal
/
Privacy
/
EULA
/
Twitter
© 2026 Dynobase