Skip to content

Conversation

@CHOIJEWON
Copy link
Contributor

@CHOIJEWON CHOIJEWON commented Oct 29, 2025

closes: #11515

Description of change

This PR adds UTC timezone support for date type columns by introducing a new utc option to ColumnOptions.

Currently, date columns always use local timezone when converting dates to strings, making it difficult to maintain consistent
timezone across different environments. This can cause issues in distributed systems or when the application server timezone differs from the database timezone.

With the new utc flag, developers can explicitly store and retrieve dates in UTC timezone, ensuring data consistency regardless of the server's local timezone setting.

Example Usage:

  @Entity()
  class Event {
      @PrimaryGeneratedColumn()
      id: number

      @Column({ type: "date" })
      localDate: Date  // Uses local timezone (existing behavior)

      @Column({ type: "date", utc: true })
      utcDate: Date  // Uses UTC timezone (new feature)
  }

What changed

  • Added utc?: boolean option to ColumnOptions interface
  • Updated ColumnMetadata to store and propagate the UTC flag
  • Modified DateUtils.mixedDateToDateString() to support UTC conversion
  • Applied UTC flag handling across all database drivers (MySQL, PostgreSQL, SQLite, Oracle, SQL Server, Spanner, CockroachDB, SAP HANA, React Native)
  • Added comprehensive test case verifying UTC vs local timezone behavior

Backward Compatibility

✅ No breaking changes - utc defaults to false, maintaining existing local timezone behavior

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links relevant issues as Fixes #00000
  • There are new or updated unit tests validating the change
  • Documentation has been updated to reflect this change

Additional Notes

This is my first contribution to TypeORM and open source in general. I've done my best to follow the project's conventions and
guidelines, but I would greatly appreciate any feedback on:

  • Code style and structure
  • Test coverage and approach
  • Documentation clarity
  • Anything I might have missed or could improve

Thank you for taking the time to review this PR! I'm excited to contribute to TypeORM and look forward to learning from the
maintainers' feedback. 🙏

Summary by CodeRabbit

Release Notes

  • New Features

    • Added UTC timezone support for date columns, allowing dates to be stored and retrieved in UTC rather than local timezone.
    • New utc configuration option available for date column types across all supported databases.
  • Documentation

    • Updated documentation to describe the new UTC date column option and default behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

Walkthrough

This PR introduces UTC timezone support for date columns across the ORM. A new utc: boolean option added to column decorators allows storing/retrieving date values using UTC timezone instead of local timezone. The change propagates through metadata, date utilities, and all database drivers with updated functional tests.

Changes

Cohort / File(s) Summary
Documentation
docs/docs/entity/1-entities.md
Added documentation for the new utc: boolean column option for date columns, describing UTC storage behavior and defaults. Minor formatting fixes to arrow-style note blocks converted to blockquote-style.
Core Configuration & Metadata
src/decorator/options/ColumnOptions.ts, src/metadata/ColumnMetadata.ts
Added utc?: boolean public property to ColumnOptions interface and corresponding utc: boolean = false property to ColumnMetadata class. ColumnMetadata constructor populates utc from column options when provided.
Date Utility Logic
src/util/DateUtils.ts
Updated mixedDateToDateString() signature to accept optional utc parameter. When utc=true and value is a Date, uses UTC date components (getUTCFullYear, getUTCMonth, getUTCDate); otherwise uses local date components.
Database Drivers
src/driver/aurora-mysql/AuroraMysqlDriver.ts, src/driver/cockroachdb/CockroachDriver.ts, src/driver/mysql/MysqlDriver.ts, src/driver/oracle/OracleDriver.ts, src/driver/postgres/PostgresDriver.ts, src/driver/react-native/ReactNativeDriver.ts, src/driver/sap/SapDriver.ts, src/driver/spanner/SpannerDriver.ts, src/driver/sqlite-abstract/AbstractSqliteDriver.ts, src/driver/sqlserver/SqlServerDriver.ts
Updated date handling in both preparePersistentValue() and prepareHydratedValue() methods to pass columnMetadata.utc parameter to DateUtils date conversion functions for "date" type columns.
Functional Tests
test/functional/columns/date-utc/date-utc.ts, test/functional/columns/date-utc/entity/Event.ts
Added new test entity Event with localDate (default) and utcDate (utc: true) columns. Comprehensive test validates that same Date value stores as "2025-06-01" in UTC mode and "2025-05-31" in local mode (with TZ=America/New_York), verifying timezone handling correctness.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ORM as ORM<br/>(Save/Load)
    participant Driver
    participant DateUtils
    participant DB as Database

    User->>ORM: Save Entity with date value
    ORM->>Driver: preparePersistentValue()
    Driver->>DateUtils: mixedDateToDateString(value, utc?)
    alt utc = true
        DateUtils->>DateUtils: Use UTC components<br/>(getUTCFullYear, etc.)
    else utc = false (default)
        DateUtils->>DateUtils: Use local components<br/>(getFullYear, etc.)
    end
    DateUtils-->>Driver: Formatted date string
    Driver->>DB: Store formatted date
    
    DB-->>Driver: Retrieve date string
    Driver->>DateUtils: mixedDateToDateString(dateString, utc?)
    DateUtils-->>Driver: Parsed date
    Driver-->>ORM: prepareHydratedValue()
    ORM-->>User: Date object
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Primary complexity: Verifying UTC logic correctness in DateUtils and consistency across 10 database drivers; reviewing test assumptions about timezone behavior
  • Homogeneous patterns: Driver updates follow identical pattern across multiple files, reducing per-file review burden but requiring systematic verification
  • Attention areas:
    • Ensure getUTCFullYear, getUTCMonth, getUTCDate are used consistently when utc=true
    • Verify all database drivers correctly propagate the columnMetadata.utc parameter in both persistence paths
    • Validate that test correctly demonstrates timezone-aware behavior with process TZ environment variable
    • Confirm default utc=false preserves backward compatibility for existing date columns

Suggested reviewers

  • sgarner
  • alumni
  • gioboa
  • naorpeled

Poem

🐰 A bunny's ode to UTC dates:

In timezones near and timezones far,
Our dates now know just where they are—
With UTC flag set bright and true,
They store the world in one shared view!
No local drift shall lead astray,
When utc decides the day! 🗓️

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: add utc flag to date column" directly and accurately summarizes the primary change in the pull request, which introduces a new optional utc boolean flag to date-type columns. The title clearly describes what was added (a UTC flag) and where it applies (date columns), making it immediately understandable to reviewers scanning the commit history. The title is concise, specific, and avoids vague terminology, accurately reflecting the core feature implementation across the entire changeset including the new interface option, metadata handling, utility function updates, and driver-specific implementations.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

@CHOIJEWON CHOIJEWON changed the title Feat/add utc flag to date column feat:add utc flag to date column Oct 29, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/driver/sqlserver/SqlServerDriver.ts (1)

531-533: Bug: Persist path ignores utc for "date" (hydrates with UTC, persists without).

On persist, "date" uses DateUtils.mixedDateToDate(value) without the UTC flag, while on hydrate it uses utc. This can produce off‑by‑one day issues around timezone offsets. Align persist with hydrate by passing utc.

Apply this fix:

@@
-        } else if (columnMetadata.type === "date") {
-            return DateUtils.mixedDateToDate(value)
+        } else if (columnMetadata.type === "date") {
+            // honor per-column UTC semantics for date-only values
+            return DateUtils.mixedDateToDate(value, columnMetadata.utc)

This matches the behavior implemented in other drivers in this PR and the intent of ColumnOptions.utc.

Also applies to: 579-581

🧹 Nitpick comments (2)
src/decorator/options/ColumnOptions.ts (1)

203-214: Clarify doc: effect differs for Date vs string inputs.

If the entity property is a Date, UTC vs local components are used accordingly; if it’s already a "YYYY-MM-DD" string, it’s passed through unchanged. Suggest noting this to avoid confusion.

Apply this doc tweak:

 /**
  * Indicates if date values should be stored and retrieved in UTC timezone
  * instead of local timezone. Only applies to "date" column type.
  * Default value is "false" (uses local timezone for backward compatibility).
+ *
+ * Note: When the entity property is a Date, TypeORM formats it using either
+ * UTC or local date components based on this flag. When it is already a
+ * "YYYY-MM-DD" string, the value is used as-is.
  *
  * @example
  * @Column({ type: "date", utc: true })
  * birthDate: Date
  */
 utc?: boolean
src/util/DateUtils.ts (1)

28-31: Use lowercase boolean type annotation.

The parameter uses Boolean (the boxed object type) instead of boolean (the primitive type). TypeScript conventions favor the primitive type for better type safety.

Apply this diff:

     static mixedDateToDateString(
         value: string | Date,
-        utc: Boolean = false,
+        utc: boolean = false,
     ): string {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7867eb and 6b5ced0.

📒 Files selected for processing (16)
  • docs/docs/entity/1-entities.md (3 hunks)
  • src/decorator/options/ColumnOptions.ts (1 hunks)
  • src/driver/aurora-mysql/AuroraMysqlDriver.ts (2 hunks)
  • src/driver/cockroachdb/CockroachDriver.ts (2 hunks)
  • src/driver/mysql/MysqlDriver.ts (2 hunks)
  • src/driver/oracle/OracleDriver.ts (2 hunks)
  • src/driver/postgres/PostgresDriver.ts (2 hunks)
  • src/driver/react-native/ReactNativeDriver.ts (2 hunks)
  • src/driver/sap/SapDriver.ts (2 hunks)
  • src/driver/spanner/SpannerDriver.ts (2 hunks)
  • src/driver/sqlite-abstract/AbstractSqliteDriver.ts (2 hunks)
  • src/driver/sqlserver/SqlServerDriver.ts (1 hunks)
  • src/metadata/ColumnMetadata.ts (2 hunks)
  • src/util/DateUtils.ts (1 hunks)
  • test/github-issues/11515/entity/Event.ts (1 hunks)
  • test/github-issues/11515/issue-11515.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
src/driver/sap/SapDriver.ts (1)
src/util/DateUtils.ts (1)
  • DateUtils (7-296)
src/driver/react-native/ReactNativeDriver.ts (1)
src/util/DateUtils.ts (1)
  • DateUtils (7-296)
test/github-issues/11515/issue-11515.ts (1)
test/utils/test-utils.ts (3)
  • createTestingConnections (388-482)
  • closeTestingConnections (487-499)
  • reloadTestingDatabases (504-509)
src/driver/aurora-mysql/AuroraMysqlDriver.ts (1)
src/util/DateUtils.ts (1)
  • DateUtils (7-296)
src/driver/oracle/OracleDriver.ts (1)
src/util/DateUtils.ts (1)
  • DateUtils (7-296)
src/driver/postgres/PostgresDriver.ts (1)
src/util/DateUtils.ts (1)
  • DateUtils (7-296)
src/driver/sqlserver/SqlServerDriver.ts (1)
src/util/DateUtils.ts (1)
  • DateUtils (7-296)
test/github-issues/11515/entity/Event.ts (2)
src/decorator/columns/PrimaryGeneratedColumn.ts (1)
  • PrimaryGeneratedColumn (55-119)
src/decorator/columns/Column.ts (1)
  • Column (134-220)
src/driver/cockroachdb/CockroachDriver.ts (1)
src/util/DateUtils.ts (1)
  • DateUtils (7-296)
src/driver/spanner/SpannerDriver.ts (1)
src/util/DateUtils.ts (1)
  • DateUtils (7-296)
src/driver/sqlite-abstract/AbstractSqliteDriver.ts (1)
src/util/DateUtils.ts (1)
  • DateUtils (7-296)
src/driver/mysql/MysqlDriver.ts (1)
src/util/DateUtils.ts (1)
  • DateUtils (7-296)
🪛 Biome (2.1.2)
test/github-issues/11515/issue-11515.ts

[error] 26-26: Disallow duplicate setup and teardown hooks.

Disallow after duplicacy inside the describe function.

(lint/suspicious/noDuplicateTestHooks)

🔇 Additional comments (20)
src/driver/aurora-mysql/AuroraMysqlDriver.ts (1)

536-536: UTC threading for "date" looks good; verify Data API path also respects it.

Changes align with other drivers. If formatOptions.castParameters !== false, values flow through DataApiDriver’s preparePersistentValue/prepareHydratedValue — please confirm that implementation uses columnMetadata.utc as well.

Also applies to: 595-595

src/driver/spanner/SpannerDriver.ts (1)

402-402: LGTM — UTC handling for date-only values is consistent.

Also applies to: 437-437

src/driver/oracle/OracleDriver.ts (1)

533-538: LGTM — Oracle persist/hydrate paths now UTC-aware for "date".

Also applies to: 571-571

src/driver/sap/SapDriver.ts (1)

545-545: LGTM — SAP driver properly threads utc for date-only columns.

Also applies to: 587-587

src/driver/react-native/ReactNativeDriver.ts (2)

337-339: LGTM! UTC flag properly propagated in persistence path.

The implementation correctly passes columnMetadata.utc to DateUtils.mixedDateToDateString, enabling UTC-aware date formatting when persisting date-type columns.


409-410: LGTM! UTC flag properly propagated in hydration path.

The implementation correctly passes columnMetadata.utc to DateUtils.mixedDateToDateString, ensuring consistent UTC-aware date handling when hydrating date-type columns from the database.

src/driver/mysql/MysqlDriver.ts (2)

614-615: LGTM! UTC support properly integrated in MySQL driver.

The UTC flag is correctly threaded through the persistence path, consistent with the implementation across other drivers.


668-669: LGTM! UTC support properly integrated in hydration path.

The UTC flag is correctly threaded through the hydration path, ensuring symmetry with persistence behavior.

docs/docs/entity/1-entities.md (1)

473-473: LGTM! Clear documentation of the UTC feature.

The documentation accurately describes the new utc option, its purpose, applicability to date column type, and default behavior for backward compatibility.

src/driver/sqlite-abstract/AbstractSqliteDriver.ts (2)

333-334: LGTM! UTC support properly integrated in SQLite driver persistence.

The implementation follows the established pattern across other drivers, correctly passing the UTC flag during date persistence.


408-409: LGTM! UTC support properly integrated in SQLite driver hydration.

The implementation maintains consistency with the persistence path and other drivers.

src/metadata/ColumnMetadata.ts (2)

126-130: LGTM! UTC property properly declared with clear documentation.

The property is correctly typed, initialized with a backward-compatible default, and documented with clear scope (applies to "date" column type only).


397-398: LGTM! UTC flag properly populated from column options.

The constructor correctly reads the UTC flag from column options, following the established pattern for other column properties.

test/github-issues/11515/entity/Event.ts (1)

1-15: LGTM! Well-structured test entity for UTC validation.

The entity provides good test coverage with both UTC-enabled (utcDate) and default local timezone (localDate) date columns, allowing tests to verify the distinct behavior of each.

test/github-issues/11515/issue-11515.ts (1)

30-47: LGTM! Test logic correctly validates UTC behavior.

The test properly sets up a timezone-dependent scenario (America/New_York) and validates that:

  • utcDate with utc: true stores as UTC date: "2025-06-01"
  • localDate without UTC flag stores as local date: "2025-05-31" (20:00 EDT on May 31 = 00:00 UTC on June 1)

This effectively demonstrates the UTC flag functionality.

src/util/DateUtils.ts (1)

33-41: LGTM! UTC conversion logic is correct.

The implementation properly uses UTC date components (getUTCFullYear, getUTCMonth, getUTCDate) when the UTC flag is true, maintaining consistent date representation regardless of the server's timezone.

src/driver/cockroachdb/CockroachDriver.ts (2)

387-388: LGTM! UTC flag correctly propagated for date persistence.

The change correctly passes columnMetadata.utc to DateUtils.mixedDateToDateString, enabling UTC-aware date formatting when persisting date-type columns. When utc: true, dates will be formatted using UTC components; otherwise, the existing local timezone behavior is preserved.


447-448: LGTM! UTC flag correctly propagated for date hydration.

The change correctly passes columnMetadata.utc to DateUtils.mixedDateToDateString, enabling UTC-aware date formatting when hydrating date-type columns from the database. This mirrors the persistence logic on line 388 and ensures consistency in both directions.

src/driver/postgres/PostgresDriver.ts (2)

653-654: LGTM! UTC flag correctly applied for date persistence.

The implementation correctly passes columnMetadata.utc to DateUtils.mixedDateToDateString() when persisting date values. When utc: true, this ensures UTC date components are extracted from Date objects, maintaining consistent date values across timezones.


752-753: LGTM! UTC flag symmetrically applied for date hydration.

The hydration path correctly mirrors the persistence path by passing columnMetadata.utc to DateUtils.mixedDateToDateString(). This maintains consistency between reads and writes. Note that PostgreSQL typically returns DATE columns as strings, so the UTC flag primarily ensures correctness in edge cases where the driver returns Date objects.

@CHOIJEWON CHOIJEWON closed this Oct 29, 2025
@CHOIJEWON CHOIJEWON reopened this Oct 29, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/github-issues/11515/issue-11515.ts (1)

30-47: Consider clarifying the test description.

The test description states "should save the date column in UTC when the utc flag is true", but the test actually validates both UTC and local timezone behavior by checking both utcDate and localDate fields. A more comprehensive description would better reflect what the test covers.

Consider updating the test description:

-    it("should save the date column in UTC when the utc flag is true", () =>
+    it("should save date columns in UTC when utc flag is true and in local timezone when false", () =>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b5ced0 and 978bae7.

📒 Files selected for processing (3)
  • src/driver/sqlserver/SqlServerDriver.ts (2 hunks)
  • src/util/DateUtils.ts (1 hunks)
  • test/github-issues/11515/issue-11515.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/util/DateUtils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/driver/sqlserver/SqlServerDriver.ts
🧰 Additional context used
🧬 Code graph analysis (1)
test/github-issues/11515/issue-11515.ts (1)
test/utils/test-utils.ts (3)
  • createTestingConnections (388-482)
  • closeTestingConnections (487-499)
  • reloadTestingDatabases (504-509)

process.env.TZ = originalTZ
await closeTestingConnections(connections)
})
beforeEach(() => reloadTestingDatabases(connections))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix async handling in beforeEach hook.

The reloadTestingDatabases function returns a Promise, but the beforeEach callback neither awaits it nor returns the promise. This can cause race conditions where tests execute before the database is fully reloaded.

Apply this diff to fix the issue:

-    beforeEach(() => reloadTestingDatabases(connections))
+    beforeEach(() => reloadTestingDatabases(connections))

Wait, that's the same. Let me correct that - you need to return the promise:

-    beforeEach(() => reloadTestingDatabases(connections))
+    beforeEach(async () => await reloadTestingDatabases(connections))

Or more simply, pass the function directly since it already returns a promise:

-    beforeEach(() => reloadTestingDatabases(connections))
+    beforeEach(async () => {
+        await reloadTestingDatabases(connections)
+    })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
beforeEach(() => reloadTestingDatabases(connections))
beforeEach(async () => {
await reloadTestingDatabases(connections)
})
🤖 Prompt for AI Agents
In test/github-issues/11515/issue-11515.ts around line 28, the beforeEach hook
must return the Promise from reloadTestingDatabases to ensure async completion;
change the hook so it returns the promise (e.g., beforeEach(() => { return
reloadTestingDatabases(connections); }) ) or make it async and await the call
(e.g., beforeEach(async () => await reloadTestingDatabases(connections)));
either approach ensures the test runner waits for the DB reload to finish.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 29, 2025

commit: 8cc1ddd

Copy link
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

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

Thanks for your help @CHOIJEWON

@coveralls
Copy link

coveralls commented Oct 29, 2025

Coverage Status

coverage: 80.773% (+0.007%) from 80.766%
when pulling 8cc1ddd on CHOIJEWON:feat/add-utc-flag-to-date-column
into 67f793f on typeorm:master.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
test/functional/columns/date-utc/date-utc.ts (1)

28-28: Fix async handling in beforeEach hook.

The reloadTestingDatabases function returns a Promise, but the callback neither awaits it nor returns the promise. This can cause race conditions where tests execute before the database is fully reloaded.

Apply this diff to fix the async handling:

-    beforeEach(() => reloadTestingDatabases(connections))
+    beforeEach(async () => {
+        await reloadTestingDatabases(connections)
+    })

Or more concisely, since the function signature matches:

-    beforeEach(() => reloadTestingDatabases(connections))
+    beforeEach(() => reloadTestingDatabases(connections))

Wait, that's identical. The simplest fix is:

-    beforeEach(() => reloadTestingDatabases(connections))
+    beforeEach(async () => await reloadTestingDatabases(connections))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 978bae7 and 2950548.

📒 Files selected for processing (2)
  • test/functional/columns/date-utc/date-utc.ts (1 hunks)
  • test/functional/columns/date-utc/entity/Event.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
test/functional/columns/date-utc/entity/Event.ts (2)
src/decorator/columns/PrimaryGeneratedColumn.ts (1)
  • PrimaryGeneratedColumn (55-119)
src/decorator/columns/Column.ts (1)
  • Column (134-220)
test/functional/columns/date-utc/date-utc.ts (1)
test/utils/test-utils.ts (3)
  • createTestingConnections (388-482)
  • closeTestingConnections (487-499)
  • reloadTestingDatabases (504-509)
🔇 Additional comments (6)
test/functional/columns/date-utc/entity/Event.ts (3)

1-3: LGTM!

The imports are correct and necessary for defining the test entity.


5-10: LGTM!

The entity definition and primary key configuration follow TypeORM conventions correctly.


12-16: LGTM!

The date column definitions correctly demonstrate the new UTC flag feature, with localDate using default local timezone behavior and utcDate explicitly enabling UTC storage with utc: true.

test/functional/columns/date-utc/date-utc.ts (3)

1-9: LGTM!

All imports are necessary and correctly configured for the functional test.


15-21: LGTM!

The before hook properly sets up the test environment by saving the original timezone, setting it to America/New_York for testing, and creating database connections with proper async handling.


23-26: LGTM!

The after hook properly cleans up by restoring the original timezone and closing database connections with appropriate async handling.

Comment on lines +30 to +49
it("should save date columns in UTC when utc flag is true and in local timezone when false", () =>
Promise.all(
connections.map(async (connection) => {
const event = new Event()
const testDate = new Date(Date.UTC(2025, 5, 1)) // 2025-06-01 in UTC

event.localDate = testDate
event.utcDate = testDate

const savedEvent = await connection.manager.save(event)
const result = await connection.manager.findOneBy(Event, {
id: savedEvent.id,
})

// UTC flag true: should save as 2025-06-01 (UTC date)
expect(result!.utcDate).to.equal("2025-06-01")
// UTC flag false (default): should save as 2025-05-31 (local timezone)
expect(result!.localDate).to.equal("2025-05-31")
}),
))
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify type consistency in date assertions.

The test logic correctly validates the UTC vs local timezone behavior. However, there's a potential type mismatch:

  • Lines 45 and 47 compare result!.utcDate and result!.localDate (typed as Date in the entity) directly to string literals "2025-06-01" and "2025-05-31".
  • This comparison will work if the database driver returns date columns as strings, but the entity definition declares these fields as Date type.

Ensure that:

  1. The database drivers actually return date columns as strings (which appears to be the case based on the test expectations), OR
  2. The assertions should convert the Date objects to strings for comparison, OR
  3. The entity field types should be string | Date to reflect the actual runtime behavior.

Run the following script to check how other date column tests handle assertions:


🏁 Script executed:

#!/bin/bash
# Search for similar date column tests to verify the assertion pattern
rg -n -A3 -B3 'type.*:.*"date"' test/functional --type ts | head -50

# Check how date comparisons are typically done in tests
rg -n 'expect.*Date.*to\.equal' test/functional --type ts | head -20

Length of output: 1455


Add transformer to Event entity date columns to match established pattern.

The script output reveals the root cause: The Post entity uses a transformer on its date column to convert between Date objects and strings (lines 32-34), but the Event entity at lines 12 and 15 has no transformer.

When the test retrieves the date columns and compares them to string literals ("2025-06-01" and "2025-05-31"), the comparison will fail because:

  • result!.utcDate and result!.localDate are typed as Date but have no transformer
  • Without the transformer, they'll be returned as Date objects (or driver-specific formats), not strings
  • The equality check against string literals will fail

Fix: Add the same transformer pattern used in Post.ts to the Event entity:

@Column({ 
  type: "date",
  transformer: {
    from: (value: any) => new Date(value),
    to: (value?: Date) => value?.toISOString(),
  }
})
localDate: Date

@Column({ 
  type: "date", 
  utc: true,
  transformer: {
    from: (value: any) => new Date(value),
    to: (value?: Date) => value?.toISOString(),
  }
})
utcDate: Date

This ensures date columns are consistently handled across the codebase and the test assertions will work correctly.

🤖 Prompt for AI Agents
In test/functional/columns/date-utc/date-utc.ts around lines 30 to 49, the test
expects string dates but the Event entity date columns lack the transformer used
elsewhere, so retrieved values are Date objects (or driver-specific) causing
assertion failures; update the Event entity (lines referenced in the comment:
~12 and ~15) to add the same transformer pattern as Post.ts that converts from
DB value to a Date (from: value => new Date(value)) and from Date to DB string
(to: value => value?.toISOString()), and keep the utc: true flag on the utcDate
column so saved/retrieved values match the test string comparisons.

@CHOIJEWON
Copy link
Contributor Author

@gioboa

I’ve updated the test directory location, but I’m not entirely sure if it’s the correct place. Please let me know if there’s anything that needs to be adjusted!

@CHOIJEWON
Copy link
Contributor Author

@alumni @sgarner @naorpeled @pkuczynski

Hi there — sorry for tagging you when you're busy.
I wanted to kindly ask if there's anything else I should work on for this PR to move forward.
If not, would it be possible for you to review it when you have a moment?

Thank you!

@alumni
Copy link
Collaborator

alumni commented Nov 24, 2025

I'm thinking that the problem is a bit more complex - the server timezone is also important (and might be different than the one the app uses) - but I'm thinking we could let the users figure it out for now and fix it later (maybe here the docs/tsdoc for utc could be improved).

I'm also not sure which database clients (maybe mssql?) already return a Date object (and also not sure how the client decides how to create the Date object).

In my case (SAP) I'm getting strings either in the server's TZ or in UTC, depending which function is called. TypeORM uses the non-UTC functions for create/update/delete date columns (so this option won't work with them). Telling it to use CURRENT_UTCTIMESTAMP instead of CURRENT_TIMESTAMP depending on this option might add to the complexity.

I would still recommend using UTC everywhere just to be safe, but at least this partially solves the problem on the application side :)

@alumni
Copy link
Collaborator

alumni commented Nov 24, 2025

@CHOIJEWON one thing: this method is called for "date" not for "timestamp"/"datetime"? What's the purpose then?

@alumni alumni self-requested a review November 24, 2025 17:14
@CHOIJEWON
Copy link
Contributor Author

@alumni Thank you for your review 🙌

Regarding the scope being limited to "date" type:

This implementation specifically addresses issue #11515, which reports timezone problems with the DATE column type. I intentionally
kept the scope limited to DATE columns to stay focused on the reported issue.

Even though DATE columns only store year-month-day without time information, the conversion still has timezone issues. When
mixedDateToDateString() uses local timezone methods (getFullYear(), getMonth(), getDate()), a JavaScript Date object can
convert to the wrong calendar date depending on the application's timezone.

The utc flag provides an opt-in solution for users who want consistent DATE storage across different application timezones by
using UTC methods (getUTCFullYear(), getUTCMonth(), getUTCDate()) instead.

Regarding DATETIME/TIMESTAMP complexity:

As you pointed out, DATETIME/TIMESTAMP columns involve additional complexities (server timezone, database function choices like
CURRENT_TIMESTAMP vs CURRENT_UTCTIMESTAMP, client behavior). I agree with your recommendation that users should handle those
with UTC everywhere at the database level.

This PR keeps the solution simple and focused on DATE columns where timezone handling is purely application-side :)

@CHOIJEWON CHOIJEWON force-pushed the feat/add-utc-flag-to-date-column branch from 2950548 to 507e421 Compare November 25, 2025 01:50
@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Nov 25, 2025

PR Code Suggestions ✨

Latest suggestions up to 8cc1ddd

CategorySuggestion                                                                                                                                    Impact
Possible issue
Inconsistent date handling for SQL Server

The preparePersistentValue method in SqlServerDriver uses mixedDateToDate, which
is inconsistent with other drivers using mixedDateToDateString. Verify this
implementation to ensure consistent behavior of the utc flag.

src/driver/sqlserver/SqlServerDriver.ts [534-535]

 } else if (columnMetadata.type === "date") {
-    return DateUtils.mixedDateToDate(value, columnMetadata.utc)
+    return DateUtils.mixedDateToDateString(value, {
+        utc: columnMetadata.utc,
+    })
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies an inconsistency in date handling between the SQL Server driver and other drivers. While using a Date object might be intentional for this specific driver, the inconsistency is worth investigating to ensure the utc flag behaves as expected across all supported databases.

Medium
  • More

Previous suggestions

Suggestions up to commit 86c6d39
CategorySuggestion                                                                                                                                    Impact
Possible issue
Handle UTC conversion for string dates

Modify mixedDateToDateString to handle UTC conversion for string date values,
not just Date objects. This ensures consistent behavior for the utc option
across different input types.

src/util/DateUtils.ts [28-53]

 static mixedDateToDateString(
     value: string | Date,
     options?: { utc?: boolean },
 ): string {
     const utc = options?.utc ?? false
     if (value instanceof Date) {
         if (utc) {
             return (
                 this.formatZerolessValue(value.getUTCFullYear(), 4) +
                 "-" +
                 this.formatZerolessValue(value.getUTCMonth() + 1) +
                 "-" +
                 this.formatZerolessValue(value.getUTCDate())
             )
         }
         return (
             this.formatZerolessValue(value.getFullYear(), 4) +
             "-" +
             this.formatZerolessValue(value.getMonth() + 1) +
             "-" +
             this.formatZerolessValue(value.getDate())
         )
     }
 
+    if (utc && typeof value === "string") {
+        const date = new Date(value)
+        return (
+            this.formatZerolessValue(date.getUTCFullYear(), 4) +
+            "-" +
+            this.formatZerolessValue(date.getUTCMonth() + 1) +
+            "-" +
+            this.formatZerolessValue(date.getUTCDate())
+        )
+    }
+
     return value
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that string-based dates are not handled by the new utc logic, leading to inconsistent behavior compared to Date objects. Applying the change would fix this potential bug and make the feature more robust.

Medium
Suggestions up to commit 1b560d7
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix inconsistent date handling method

In SqlServerDriver.ts, use DateUtils.mixedDateToDateString instead of
DateUtils.mixedDateToDate for date columns in preparePersistentValue to ensure
consistent UTC date handling across all drivers.

src/driver/sqlserver/SqlServerDriver.ts [534-535]

 } else if (columnMetadata.type === "date") {
-    return DateUtils.mixedDateToDate(value, columnMetadata.utc)
+    return DateUtils.mixedDateToDateString(value, {
+        utc: columnMetadata.utc,
+    })
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that SqlServerDriver uses mixedDateToDate for date types, which is inconsistent with other drivers that use mixedDateToDateString. This can lead to incorrect date persistence, as mixedDateToDate does not handle the utc flag in the same way as the newly modified mixedDateToDateString. Aligning the implementation improves correctness and consistency across drivers.

Medium
Suggestions up to commit db8a2b2
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix SQL Server date persistence method

In SqlServerDriver, change the call in preparePersistentValue for date columns
from DateUtils.mixedDateToDate to DateUtils.mixedDateToDateString to align with
other drivers and ensure correct date persistence.

src/driver/sqlserver/SqlServerDriver.ts [534-535]

 } else if (columnMetadata.type === "date") {
-    return DateUtils.mixedDateToDate(value, columnMetadata.utc)
+    return DateUtils.mixedDateToDateString(value, columnMetadata.utc)
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that SqlServerDriver uses mixedDateToDate for date type persistence, which is inconsistent with all other drivers in the PR that use mixedDateToDateString. This could lead to incorrect date storage, making it a valid and important bug fix.

Medium
Suggestions up to commit 585ec09
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix UTC handling for SQL Server dates

Fix an issue in SqlServerDriver where the utc flag is incorrectly passed to
DateUtils.mixedDateToDate, which does not support it, leading to incorrect date
persistence.

src/driver/sqlserver/SqlServerDriver.ts [534-535]

 } else if (columnMetadata.type === "date") {
-    return DateUtils.mixedDateToDate(value, columnMetadata.utc)
+    if (columnMetadata.utc && value instanceof Date) {
+        return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()))
+    }
+    return DateUtils.mixedDateToDate(value)
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical bug where the utc parameter is passed to DateUtils.mixedDateToDate, a function that does not accept it, causing the UTC setting to be ignored for SQL Server.

High
Suggestions up to commit f803689
CategorySuggestion                                                                                                                                    Impact
General
Handle UTC conversion for string dates

In mixedDateToDateString, handle UTC conversion for date values provided as
strings. Currently, only Date objects are converted, and strings are returned
as-is.

src/util/DateUtils.ts [28-52]

 static mixedDateToDateString(
     value: string | Date,
     utc: boolean = false,
 ): string {
     if (value instanceof Date) {
         if (utc) {
             return (
                 this.formatZerolessValue(value.getUTCFullYear(), 4) +
                 "-" +
                 this.formatZerolessValue(value.getUTCMonth() + 1) +
                 "-" +
                 this.formatZerolessValue(value.getUTCDate())
             )
         }
         return (
             this.formatZerolessValue(value.getFullYear(), 4) +
             "-" +
             this.formatZerolessValue(value.getMonth() + 1) +
             "-" +
             this.formatZerolessValue(value.getDate())
         )
     }
 
+    if (typeof value === "string" && utc) {
+        const date = new Date(value)
+        return (
+            this.formatZerolessValue(date.getUTCFullYear(), 4) +
+            "-" +
+            this.formatZerolessValue(date.getUTCMonth() + 1) +
+            "-" +
+            this.formatZerolessValue(date.getUTCDate())
+        )
+    }
+
     return value
 }
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that string date values are not processed for UTC conversion, but the proposed fix using new Date(value) can lead to inconsistent parsing. While it highlights a valid edge case, the improvement is minor as the primary use case with Date objects is handled correctly by the PR.

Low

@CHOIJEWON CHOIJEWON force-pushed the feat/add-utc-flag-to-date-column branch from 507e421 to f803689 Compare November 28, 2025 09:49
static mixedDateToDateString(value: string | Date): string {
static mixedDateToDateString(
value: string | Date,
utc: boolean = false,
Copy link
Member

Choose a reason for hiding this comment

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

nit:
Imo we should make this an object param so if we ever need more options here, we won't need to deal with breaking changes, what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry for the delayed reply — the day is just starting here in Korea 🙂
Your feedback is completely reasonable. I’ll apply the updates and push a new commit shortly.

Copy link
Member

@naorpeled naorpeled left a comment

Choose a reason for hiding this comment

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

LGTM, left a nit comment, let me know what you think about it 🙏
After that's addressed I think we'll be good to merge

  - Fix SQL Server driver to pass UTC flag in preparePersistentValue
    Ensures persist and hydrate operations handle UTC consistently

  - Fix nested after hook in test file
    Move closeTestingConnections call to top-level after hook

  - Use lowercase boolean type in DateUtils
    Follow TypeScript best practices for primitive types

  Addresses CodeRabbit AI review comments
  Move tests from github-issues to test/functional/columns/date-utc
  following maintainer feedback. Improve test descriptions and add
  explanatory comments
@CHOIJEWON CHOIJEWON force-pushed the feat/add-utc-flag-to-date-column branch from 585ec09 to db8a2b2 Compare November 29, 2025 04:07
  Changed the `mixedDateToDateString` function signature from using a boolean parameter (`utc: boolean = false`) to an object parameter (`options?: { utc?: boolean }`) to allow
  for future extensibility without breaking changes.

  This approach follows industry best practices and makes it easier to add additional options in the future while maintaining backward compatibility through optional parameters.

  Updated all call sites across:
  - 10 database drivers (MySQL, Postgres, Oracle, SQL Server, SQLite, etc.)
  - SubjectChangedColumnsComputer for column comparison logic
  - Maintained backward compatibility with test files using single-parameter calls
@qodo-free-for-open-source-projects

PR Code Suggestions ✨

No code suggestions found for the PR.

@CHOIJEWON
Copy link
Contributor Author

CHOIJEWON commented Nov 29, 2025

@naorpeled

Thank you for your valuable feedback! I've updated the implementation as you suggested.

Changes Made:

I've refactored the mixedDateToDateString function parameter from a simple boolean to an object parameter to ensure future extensibility without breaking changes:

src/util/DateUtils.ts:28-32

static mixedDateToDateString(
    value: string | Date,
    options?: { utc?: boolean },
): string {
    const utc = options?.utc ?? false

All database drivers (MySQL, PostgreSQL, SQLite, Oracle, SQL Server, Spanner, CockroachDB, SAP HANA, React Native) have been updated to use the new parameter format:

DateUtils.mixedDateToDateString(value, { utc: columnMetadata.utc })

This approach allows for potential future options to be added without requiring API changes.

Could you please review the updated implementation when you have a moment? I'd greatly appreciate any additional feedback.

Thank you!

@CHOIJEWON CHOIJEWON requested a review from naorpeled November 29, 2025 07:43
Copy link
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

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

Thanks for your help @CHOIJEWON

@gioboa gioboa requested review from alumni and removed request for alumni November 29, 2025 08:50
@CHOIJEWON
Copy link
Contributor Author

@gioboa

I just wanted to kindly ask if there’s anything else I should address in this PR.
If everything looks good from your perspective, I’m happy to wait for the merge 🙂

@gioboa gioboa merged commit 55cd8e2 into typeorm:master Nov 30, 2025
64 checks passed
Copy link
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

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

Merged 💪

@CHOIJEWON CHOIJEWON deleted the feat/add-utc-flag-to-date-column branch December 1, 2025 02:23
ThbltLmr pushed a commit to ThbltLmr/typeorm that referenced this pull request Dec 2, 2025
mgohin pushed a commit to mgohin/typeorm that referenced this pull request Jan 15, 2026
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.

DateUtils.mixedDateToDateString should support UTC

5 participants