Skip to content

feat!: use DateTime for all timestamp fields - #1663

Open
spydon wants to merge 6 commits into
mainfrom
fix/datetime-timestamps
Open

feat!: use DateTime for all timestamp fields#1663
spydon wants to merge 6 commits into
mainfrom
fix/datetime-timestamps

Conversation

@spydon

@spydon spydon commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Closes #1449

What

Every timestamp the SDK returns is now a DateTime in UTC, parsed once when the response is decoded, instead of a raw ISO 8601 String or a Unix timestamp int.

Type Fields Before After
Session expiresAt int? (Unix seconds) DateTime?
User createdAt String DateTime
User confirmationSentAt, recoverySentAt, emailChangeSentAt, invitedAt, confirmedAt, emailConfirmedAt, phoneConfirmedAt, lastSignInAt, updatedAt String? DateTime?
UserIdentity createdAt, lastSignInAt, updatedAt String? DateTime?
OAuthClient createdAt, updatedAt String DateTime
Bucket createdAt, updatedAt String DateTime
FileObject createdAt, updatedAt, lastAccessedAt String? DateTime?
FileObjectV2 createdAt / updatedAt, lastAccessedAt, lastModified String / String? DateTime / DateTime?
PaginatedFile createdAt, updatedAt String? DateTime?

The wire format is unchanged: toJson() still writes ISO 8601 strings for the User timestamps and Unix seconds for Session.expires_at, so sessions persisted by v2 are still readable.

How

Parsing now lives in supabase_common (parseIso8601, tryParseIso8601, parseUnixSeconds, tryParseUnixSeconds, dateTimeFromUnixSeconds, unixSecondsFromDateTime) and replaces the per-type parsers that had grown in mfa.dart, passkey.dart, custom_oauth_provider.dart and storage_client/types.dart. All of them normalize to UTC, so the timestamps in Factor, Passkey, AuthMFAChallengeResponse, AMREntry, AnalyticsBucket and CustomOAuthProvider are consistent with the newly converted ones.

Three consequences worth calling out:

  • Unix timestamps are UTC now. AuthMFAChallengeResponse.expiresAt, the passkey challenge expiries and AMREntry.timestamp were converted with DateTime.fromMillisecondsSinceEpoch without isUtc, so they came back in local time. They point at the same instant as before, but DateTime equality takes the time zone flag into account, so comparisons against DateTime(...) need to become DateTime.utc(...).
  • OAuthAuthorizationDetailsResponse.user is an OAuthAuthorizingUser. The OAuth 2.1 server only returns {id, email} for that user, so building a full User from it required defaulting createdAt to an empty string. auth-js models it as an inline {id, email} object, and the new type matches that. id and email keep their names.
  • User.createdAt is parsed strictly. It used to fall back to '' when the field was missing; a malformed payload now throws a FormatException instead of handing back an unusable value.

Also fixes User.hashCode and UserIdentity.hashCode, which hashed their metadata maps by identity while == compared them deeply. Equal instances could hash differently, which breaks HashSet/HashMap membership. The existing tests only passed because const map literals are canonicalized.

MIGRATION.md has the v2 to v3 entries, and sdk-compliance.yaml registers the new OAuthAuthorizingUser symbols.

Not included

Session.expiresIn and the storage createSignedUrl(path, expiresIn) parameters are still int seconds rather than Duration. AuthMFAVerifyResponse.expiresIn is already a Duration, so there is an inconsistency to clean up there, but it spans the storage signed-URL API too and is a separate change from this one.

Test plan

  • dart test in gotrue, storage_client, supabase, supabase_common, realtime_client and flutter test in supabase_flutter, all against a local supabase start stack: all pass.
  • New supabase_common/test/timestamp_test.dart covers the helpers, including offset normalization and the failure modes.
  • New cases for User.fromJson (missing/invalid/offset created_at), the storage timestamp fields and OAuthAuthorizationDetailsResponse with a non-object user.
  • dart analyze --fatal-infos clean across all packages and examples; capability-matrix symbol, drift and schema checks pass locally.

Summary by CodeRabbit

  • New Features
    • SDK timestamp fields now use UTC DateTime values across authentication, storage, and session models.
    • Added shared ISO 8601 and Unix timestamp conversion utilities.
    • Added OAuthAuthorizingUser for OAuth authorization details.
  • Bug Fixes
    • Improved timestamp validation, UTC normalization, nullable handling, and invalid-value errors.
    • Standardized timestamp parsing across OAuth, MFA, passkey, realtime, and storage features.
  • Documentation
    • Added v3 migration guidance for timestamp and OAuth type changes.

@spydon
spydon requested a review from a team as a code owner August 6, 2026 12:56
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6cb300c3-ea4c-425c-a715-5ea9fd4d44fd

📥 Commits

Reviewing files that changed from the base of the PR and between cf61c3e and 38c4395.

📒 Files selected for processing (3)
  • packages/gotrue/lib/src/gotrue_oauth_api.dart
  • packages/supabase_common/lib/src/timestamp.dart
  • packages/supabase_common/test/timestamp_test.dart
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/supabase_common/test/timestamp_test.dart
  • packages/gotrue/lib/src/gotrue_oauth_api.dart
  • packages/supabase_common/lib/src/timestamp.dart

📝 Walkthrough

Walkthrough

The SDK adds shared UTC timestamp utilities and changes authentication, storage, and realtime timestamp fields from strings or Unix integers to DateTime values. OAuth authorization responses use OAuthAuthorizingUser. Tests and migration documentation cover parsing, serialization, validation, and UTC behavior.

Changes

UTC timestamp migration

Layer / File(s) Summary
Shared timestamp utilities
packages/supabase_common/lib/src/timestamp.dart, packages/supabase_common/lib/supabase_common.dart, packages/supabase_common/test/timestamp_test.dart
Adds strict ISO 8601 and Unix-second parsing, nullable variants, UTC normalization, conversions, and tests.
GoTrue timestamp models and session expiry
packages/gotrue/lib/src/types/*, packages/gotrue/lib/src/gotrue_client.dart, packages/gotrue/test/*
Timestamp fields now use UTC DateTime values. Session expiry converts between JWT seconds and DateTime. Shared parsing helpers handle GoTrue models.
OAuth authorization user contract
packages/gotrue/lib/src/gotrue_oauth_api.dart, packages/gotrue/test/src/gotrue_oauth_api_test.dart, sdk-compliance.yaml
Adds OAuthAuthorizingUser, validates authorization user data, and updates the public response type and compliance mapping.
Storage and realtime timestamp handling
packages/storage_client/lib/src/*, packages/storage_client/test/*, packages/realtime_client/lib/src/types.dart, packages/realtime_client/test/types_test.dart, examples/storage_transforms/lib/models.dart
Storage fields use DateTime with shared parsing. Vector and realtime timestamps normalize to UTC. Storage transforms forward createdAt directly.
Migration guidance
MIGRATION.md
Documents timestamp type changes, serialization behavior, timezone handling, strict parsing, and the OAuth user type change.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

Suggested labels: v3

Suggested reviewers: vinzent03, tr00d

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: replacing timestamp fields with DateTime values.
Linked Issues check ✅ Passed The PR changes Session.expiresAt to DateTime and applies the same change to other applicable timestamp fields, satisfying issue #1449.
Out of Scope Changes check ✅ Passed The changes support the stated timestamp migration, OAuth type update, documentation, compliance mapping, and related tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/datetime-timestamps

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/storage_client/lib/src/types.dart (1)

418-441: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add PaginatedFile timestamp parsing tests.

PaginatedFile.fromJson now converts timestamps to DateTime?. The supplied PaginatedListResult test fixture omits both timestamp fields. Add coverage for UTC conversion from an offset timestamp and for absent timestamp fields.

As per coding guidelines, “Add or maintain tests for modified package behavior and run the package's test suite.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/storage_client/lib/src/types.dart` around lines 418 - 441, Add tests
covering PaginatedFile.fromJson timestamp behavior: verify an offset timestamp
is converted to the equivalent UTC DateTime, and verify missing updated_at and
created_at fields produce null values. Update the PaginatedListResult fixture as
needed and run the storage_client package test suite.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/gotrue/lib/src/gotrue_oauth_api.dart`:
- Around line 52-56: Update OAuthAuthorizingUser.fromJson to validate that both
id and email exist and are strings before constructing the object, throwing
FormatException for missing or invalid fields instead of relying on casts. Add
tests covering missing and non-string id/email values while preserving the
existing successful parsing behavior.

In `@packages/realtime_client/lib/src/types.dart`:
- Around line 319-322: Add focused tests for PostgresChangePayload timestamp
parsing: verify an offset-form commit_timestamp produces a DateTime where
commitTimestamp.isUtc is true, and verify both missing and malformed timestamps
return the UTC epoch fallback. Place these alongside the existing transformer
tests, while retaining their payload-enrichment coverage, and run the package
test suite.

In `@packages/storage_client/lib/src/vector_types.dart`:
- Around line 51-55: Add regression tests for VectorBucket.fromJson and
VectorIndex.fromJson using numeric Unix-second creationTime values, asserting
the parsed timestamps are UTC; also verify non-numeric creationTime values
produce null. Place the coverage alongside the package’s existing vector model
tests and run the package test suite.

In `@packages/supabase_common/lib/src/timestamp.dart`:
- Around line 14-21: Update the timestamp parsing logic around DateTime.tryParse
to reject overflowed date components instead of accepting normalized values,
while preserving UTC conversion for valid timestamps. Add invalid-date cases
such as 2025-02-30 and 2023-13-01 to
packages/supabase_common/test/timestamp_test.dart lines 53-64, asserting that
each throws FormatException.

---

Outside diff comments:
In `@packages/storage_client/lib/src/types.dart`:
- Around line 418-441: Add tests covering PaginatedFile.fromJson timestamp
behavior: verify an offset timestamp is converted to the equivalent UTC
DateTime, and verify missing updated_at and created_at fields produce null
values. Update the PaginatedListResult fixture as needed and run the
storage_client package test suite.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2b3da086-d544-4707-a598-36a1b5063d53

📥 Commits

Reviewing files that changed from the base of the PR and between e06f230 and 4a44424.

📒 Files selected for processing (27)
  • MIGRATION.md
  • examples/storage_transforms/lib/models.dart
  • packages/gotrue/lib/src/gotrue_client.dart
  • packages/gotrue/lib/src/gotrue_oauth_api.dart
  • packages/gotrue/lib/src/types/custom_oauth_provider.dart
  • packages/gotrue/lib/src/types/mfa.dart
  • packages/gotrue/lib/src/types/passkey.dart
  • packages/gotrue/lib/src/types/session.dart
  • packages/gotrue/lib/src/types/types.dart
  • packages/gotrue/lib/src/types/user.dart
  • packages/gotrue/test/client_test.dart
  • packages/gotrue/test/passkey_test.dart
  • packages/gotrue/test/src/gotrue_oauth_api_test.dart
  • packages/gotrue/test/src/set_session_test.dart
  • packages/gotrue/test/src/types/mfa_test.dart
  • packages/gotrue/test/src/types/passkey_test.dart
  • packages/gotrue/test/src/types/session_test.dart
  • packages/gotrue/test/src/types/user_test.dart
  • packages/realtime_client/lib/src/types.dart
  • packages/storage_client/lib/src/types.dart
  • packages/storage_client/lib/src/vector_types.dart
  • packages/storage_client/test/basic_test.dart
  • packages/storage_client/test/types_test.dart
  • packages/supabase_common/lib/src/timestamp.dart
  • packages/supabase_common/lib/supabase_common.dart
  • packages/supabase_common/test/timestamp_test.dart
  • sdk-compliance.yaml

Comment thread packages/gotrue/lib/src/gotrue_oauth_api.dart Outdated
Comment thread packages/realtime_client/lib/src/types.dart Outdated
Comment thread packages/storage_client/lib/src/vector_types.dart
Comment thread packages/supabase_common/lib/src/timestamp.dart

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a breaking change across the Supabase Flutter SDK: timestamp fields returned by the SDK are now represented as UTC DateTime values, with shared parsing/conversion utilities centralized in supabase_common. It also adjusts OAuth authorization details modeling and updates tests, examples, and migration guidance to match the new types.

Changes:

  • Added shared UTC timestamp parsing/conversion helpers to supabase_common and comprehensive unit tests.
  • Migrated GoTrue + Storage models and parsing logic from String/Unix seconds to UTC DateTime, and updated affected tests.
  • Introduced OAuthAuthorizingUser for OAuth authorization details and updated docs/compliance metadata.

Reviewed changes

Copilot reviewed 29 out of 29 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
sdk-compliance.yaml Registers new OAuthAuthorizingUser symbols for compliance tracking.
packages/supabase_common/test/timestamp_test.dart Adds unit coverage for ISO 8601 + Unix-seconds parsing and UTC normalization.
packages/supabase_common/lib/supabase_common.dart Exports the new timestamp utilities as part of the public API.
packages/supabase_common/lib/src/timestamp.dart Implements shared timestamp parsing/conversion helpers used across packages.
packages/storage_client/test/vector_test.dart Updates/adds tests for vector API timestamp parsing behavior.
packages/storage_client/test/types_test.dart Updates storage type tests to assert DateTime timestamps and strict parsing.
packages/storage_client/test/basic_test.dart Fixes test fixture timestamps to valid ISO 8601 strings.
packages/storage_client/lib/src/vector_types.dart Reuses shared Unix-seconds conversion helper for vector timestamps.
packages/storage_client/lib/src/types.dart Converts bucket/file timestamp fields to DateTime and updates JSON parsing accordingly.
packages/realtime_client/test/types_test.dart Adds coverage for commit timestamp UTC normalization/fallback behavior.
packages/realtime_client/lib/src/types.dart Normalizes commit timestamp parsing to UTC and uses UTC epoch fallback.
packages/gotrue/test/src/types/user_test.dart Updates user model tests for DateTime fields, strict parsing, and hash/equality behavior.
packages/gotrue/test/src/types/session_test.dart Updates session tests for DateTime-based expiresAt and Unix-seconds serialization.
packages/gotrue/test/src/types/passkey_test.dart Updates passkey expiry expectations to UTC DateTime.
packages/gotrue/test/src/types/mfa_test.dart Updates MFA timestamp expectations to UTC DateTime.
packages/gotrue/test/src/set_session_test.dart Updates set-session tests to compare against UTC DateTime expiry.
packages/gotrue/test/src/gotrue_oauth_api_test.dart Updates OAuth authorization details tests for new user shape and validation.
packages/gotrue/test/passkey_test.dart Updates passkey tests for UTC expiry.
packages/gotrue/test/client_test.dart Updates client tests for expiresAt as UTC DateTime.
packages/gotrue/lib/src/types/user.dart Migrates user/identity timestamps to DateTime, updates JSON serialization, and fixes hashCode deep hashing.
packages/gotrue/lib/src/types/types.dart Migrates OAuth client timestamps to DateTime and uses shared ISO parsing.
packages/gotrue/lib/src/types/session.dart Migrates session expiry to DateTime, updates expiry checks and JSON serialization.
packages/gotrue/lib/src/types/passkey.dart Replaces local timestamp parsing with shared helpers; ensures expiry is UTC.
packages/gotrue/lib/src/types/mfa.dart Replaces per-type timestamp parsing with shared helpers and normalizes to UTC.
packages/gotrue/lib/src/types/custom_oauth_provider.dart Uses shared ISO parsing for created/updated timestamps.
packages/gotrue/lib/src/gotrue_oauth_api.dart Introduces OAuthAuthorizingUser and updates authorization details response parsing.
packages/gotrue/lib/src/gotrue_client.dart Updates auto-refresh tick calculation for DateTime-based expiry.
MIGRATION.md Adds v3 migration guidance for DateTime timestamps and OAuth user type change.
examples/storage_transforms/lib/models.dart Simplifies example to use DateTime? timestamps directly from storage types.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/supabase_common/lib/src/timestamp.dart Outdated
Comment thread packages/gotrue/lib/src/gotrue_oauth_api.dart
Comment thread MIGRATION.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/realtime_client/test/types_test.dart`:
- Around line 41-48: Update PostgresChangePayload.fromPayload to type-check
payload['commit_timestamp'] before parsing instead of casting it directly to
String?. Preserve the UTC epoch fallback for malformed values, including
non-string numeric timestamps, and add a test covering a numeric
commit_timestamp.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cd2774af-ee8b-40e1-b965-a196a1400135

📥 Commits

Reviewing files that changed from the base of the PR and between 4a44424 and 111499a.

📒 Files selected for processing (7)
  • MIGRATION.md
  • packages/gotrue/lib/src/gotrue_oauth_api.dart
  • packages/gotrue/test/src/gotrue_oauth_api_test.dart
  • packages/realtime_client/test/types_test.dart
  • packages/storage_client/test/vector_test.dart
  • packages/supabase_common/lib/src/timestamp.dart
  • packages/supabase_common/test/timestamp_test.dart
🚧 Files skipped from review as they are similar to previous changes (4)
  • MIGRATION.md
  • packages/gotrue/test/src/gotrue_oauth_api_test.dart
  • packages/gotrue/lib/src/gotrue_oauth_api.dart
  • packages/supabase_common/lib/src/timestamp.dart

Comment thread packages/realtime_client/test/types_test.dart
@spydon
spydon requested a review from Vinzent03 August 6, 2026 14:56
spydon added 5 commits August 6, 2026 16:58
Every timestamp the SDK returns is now a DateTime in UTC, parsed once when
the response is decoded, instead of an ISO 8601 String or Unix seconds int.

- Session.expiresAt: int? -> DateTime?
- User and UserIdentity timestamps: String/String? -> DateTime/DateTime?
- OAuthClient.createdAt, updatedAt: String -> DateTime
- Bucket, FileObject, FileObjectV2 and PaginatedFile timestamps:
  String/String? -> DateTime/DateTime?

The parsing lives in shared supabase_common helpers, which also replace the
per-type parsers in mfa.dart, passkey.dart and custom_oauth_provider.dart.
Unix timestamps that used to be converted to local time (MFA challenge
expiry, passkey challenge expiry, AMR entries) are now UTC like the rest.

OAuthAuthorizationDetailsResponse.user is no longer a User: the OAuth 2.1
server only returns an id and an email there, so it is an
OAuthAuthorizingUser, matching what the other client libraries expose.

Also fixes User.hashCode and UserIdentity.hashCode, which hashed their
metadata maps by identity while == compared them deeply, so equal instances
could hash differently.
DateTime.tryParse carries out-of-range components over into the next
larger one instead of rejecting them, so parseIso8601 accepted
'2020-01-42' as 2020-02-11 and '2019-02-29' as 2019-03-01. Reject those
so a malformed payload surfaces instead of a plausible wrong timestamp.

Also adds the tests for the UTC handling in PostgresChangePayload and the
vector index creationTime, both of which changed in this branch without
direct coverage.
unixSecondsFromDateTime truncated towards zero, so an instant before the
Unix epoch landed in a second that does not contain it.

OAuthAuthorizingUser.fromJson cast its two required fields, which raises a
TypeError for a malformed payload where the enclosing parse contract
promises a FormatException.

Also corrects the MIGRATION.md wire-format note: the storage types have no
toJson, in v2 or v3, so only Session, User and UserIdentity are relevant to
the claim.
Casting commit_timestamp to String? raised a TypeError for a non-null
non-string value, which the FormatException fallback around the parse does
not catch, so a realtime payload with a numeric timestamp threw instead of
falling back to the epoch. Type-check the value instead of casting it.

Also spells out the abbreviated locals this branch introduced: exp becomes
expiresAtSeconds and commitTimestampStr becomes commitTimestampValue.
The deprecated API removal on main deletes User.confirmedAt and both
lastAccessedAt fields, so listing them as converted to DateTime contradicted
the removal section further down the same guide.
@spydon
spydon force-pushed the fix/datetime-timestamps branch from 1469b0e to cf61c3e Compare August 6, 2026 15:05
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

The timestamp helpers passed the whole JSON object as the FormatException
source. FormatException.toString() prints part of it, and User.fromJson
payloads carry an email, a phone number and user metadata, so a malformed
timestamp put personal data into the exception text. supabase_flutter logs
that text when session recovery fails.

The messages already name the key and the offending value, which is what a
malformed timestamp needs, so the source is dropped. The two OAuth user
parse errors get the same treatment, naming the unexpected type instead of
dumping a payload that contains the user's email.

Also narrows the parseIso8601 doc comment to what the validation does: it
covers the YYYY-MM-DD and YYYYMMDD forms, not the expanded year form.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use DateTime for expiresAt and other applicable fields

3 participants