Open Closed

Timezone postgreSQL - EnableLegacyTimestampBehavior #10771


User avatar
0
luciana created

Hi,

We have an tiered modular application, abp version 9.2.0, database postgresql. Not sure if it is related but mentioning, we migrated from mysql previously and upgrade from abp 8.3.0. We’re investigating a timezone/filtering problem with ABP audit/security logs and would like to confirm the correct approach.

Audit log and security log date filters behave differently between local and cloud hosted environments. The __timezone header is present on requests, but filtering still appears incorrect and does not return the results relevant to the timezone, even with the ClockOptions.Kind set to DateTimeKind.Utc and the app.UseAbpTimeZone() middleware. We also have EnableLegacyTimestampBehavior set to true in the DbContext, but i suspect that this part of the problem as the EF module snapshots still define the timestamp columns as timestamps with time zone.

Supporting information: Incoming request log (from our host probe): Audit timezone probe Path=/api/audit-logging/audit-logs __timezone=Africa/Johannesburg startTime=2026-07-02T14:02:00.000 endTime= DB sample audit rows are stored as UTC/timestamptz, e.g. 2026-07-02 11:40:59.564458+00. Code pointers in our repo (current state): AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true) exists in: EntityFrameworkCoreModule.cs Migrations/snapshots show columns created as timestamp with time zone across modules (e.g. src/EntityFrameworkCore/Migrations/* and module snapshots).

modelBuilder.Entity("Volo.Abp.Identity.IdentitySecurityLog", b =>
     {
         b.Property<Guid>("Id")
             .ValueGeneratedOnAdd()
             .HasColumnType("uuid");
         b.Property<DateTime>("CreationTime")
             .HasColumnType("timestamp with time zone");
     });

Questions:

Is my understanding correct that with Npgsql.EnableLegacyTimestampBehavior = true the runtime may interpret ambiguous/unspecified DateTime values using host-local timezone, causing behavior differences between local and UTC-hosted environments? Is that the intended/known effect of the legacy switch?

If we set Npgsql.EnableLegacyTimestampBehavior to false (or remove the AppContext.SetSwitch), will that cause incompatibility with existing data or require database migrations or data fixes? Any known upgrade pitfalls? Why is model snapshot still defining the timestamp related columns as timestamp with time zone if I have set Npgsql.EnableLegacyTimestampBehavior = true?

For an ABP v10 project, what is the recommended setting for Npgsql.EnableLegacyTimestampBehavior when using timestamp with time zone (timestamptz) for audit/security timestamps? Why do some ABP templates/solutions still set the switch to true by default — is that for backward compatibility? Are there official migration steps documented by ABP when switching off legacy behavior?

Do you recommend (a) keeping timestamptz and disabling legacy behavior or (b) switching to timestamp without time zone and handling conversion application-side? Template: app Version 9.2.0

Tiered: Yes

UI Framework: angular

Theme: leptonx

Theme Style: system

Progressive Web App: Yes

Database Provider: ef

Database Management System: postgresql

Separate Tenant Schema: Yes

Mobile Framework: none

Public Website: No

Optional Modules:

LanguageManagement

AuditLogging

SaaS

OpenIddictAdmin

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

2 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • ABP-side (timezone header / filtering): In ABP v9.2+, multi-timezone behavior is based on enabling UTC normalization (AbpClockOptions.Kind = DateTimeKind.Utc) and providing the timezone info (via the __timezone header/cookie/etc.) so ABP can convert between user time and UTC when needed. UseAbpTimeZone() is the middleware that reads __timezone and determines the effective timezone (settings first, then request, then server fallback). This part is independent from Npgsql’s legacy timestamp switch.

    • Npgsql-side (Npgsql.EnableLegacyTimestampBehavior): That switch controls how the Npgsql provider maps and interprets PostgreSQL timestamp types to .NET DateTime (especially around timestamp vs timestamptz, and DateTimeKind.Unspecified/Local/Utc). With legacy behavior enabled, it can indeed lead to environment-dependent behavior (e.g., a value materialized/parameterized as “unspecified” getting interpreted relative to the server/container local timezone), which is exactly the kind of difference you see between dev machines and UTC-configured cloud hosts. So your understanding of the risk is directionally correct.

    • Why your EF snapshots still show timestamp with time zone: EF Core’s model snapshot column type (timestamp with time zone) is about the database schema type mapping for the property and provider; it is not controlled by EnableLegacyTimestampBehavior. That switch changes runtime conversion behavior, not what EF thinks the underlying PostgreSQL column type is. So it’s expected that your snapshot still says timestamp with time zone.

    • If you remove the switch, do you need migrations / data fixes?
      Generally, changing Npgsql.EnableLegacyTimestampBehavior does not require schema migrations because it doesn’t change column types—your columns stay timestamptz. However, it can surface previously-hidden bugs where your application was:

      • sending DateTime values with Kind=Unspecified (or Local) as query parameters for filtering, and
      • relying on legacy provider behavior to “do something” with them.

      In that case, after disabling legacy behavior, you may need application-level fixes (ensuring filter inputs are converted to UTC correctly before querying, or using DateTimeOffset).

    • Recommended approach for ABP v10+ with PostgreSQL audit/security timestamps:
      For ABP multi-timezone setups, the safest/most consistent approach is:

      1. Keep storage in UTC (ABP recommends AbpClockOptions.Kind = DateTimeKind.Utc for multi-timezone normalization).
      2. Keep PostgreSQL columns as timestamp with time zone (timestamptz) for audit/security logs (storing an absolute instant).
      3. Prefer using DateTimeOffset for new domain properties where you control the schema and want fewer ambiguities (ABP also recommends DateTimeOffset for storing time because it includes timezone/offset information).
      4. Disable legacy timestamp behavior unless you have a known dependency on it (typically it exists only for backward compatibility with older Npgsql behavior).
    • (a) timestamptz + disable legacy vs (b) switch to timestamp without time zone:
      (a) Keep timestamptz and disable legacy behavior is the recommended direction for audit/security timestamps because:

      • audit/security times are “instants” and should not drift with server timezone,
      • timestamptz is designed to represent an absolute moment, and
      • ABP’s multi-timezone feature expects you to store/transport in UTC and convert for display/filtering at the boundaries.

      Switching to timestamp without time zone tends to reintroduce ambiguity (it becomes “a local date-time with no zone”), which is exactly what causes cross-environment inconsistencies.

    • Practical ABP fix for filtering issues (what to check/change):

      1. Ensure all incoming filter datetimes are normalized to UTC before querying audit/security logs (don’t rely on implicit provider conversions).
      2. Ensure your UI/API is sending ISO8601 with Z when it means UTC (e.g., 2026-07-02T12:02:00.000Z). If the UI sends 2026-07-02T14:02:00.000 without offset, it will be treated as ambiguous.
      3. Keep:
    Configure<AbpClockOptions>(options => { options.Kind = DateTimeKind.Utc; });
         app.UseAbpTimeZone();
    
    1. Remove AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true) and then regression-test audit/security log filtering in both environments.

    No verified source available for ABP-specific “official migration steps” about toggling Npgsql.EnableLegacyTimestampBehavior (this switch is owned by Npgsql/.NET data provider behavior rather than ABP), but ABP’s official multi-timezone mechanism and expectations (UTC normalization + __timezone handling via UseAbpTimeZone) are documented here:

    • https://abp.io/docs/latest/framework/infrastructure/timing
    • https://abp.io/community/articles/zk7fnrdq

    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Let me split this into the switch itself and the actual filtering behavior, because they're separate.

    The Npgsql switch

    Npgsql.EnableLegacyTimestampBehavior only changes how Npgsql interprets a DateTime at runtime — it doesn't touch the column type. So your snapshot keeping timestamp with time zone is expected and correct; that column type is Npgsql's default EF Core mapping for DateTime, not something the switch controls. The template sets it to true purely for backward compatibility with the pre-Npgsql-6 behavior, so older code passing non-UTC DateTime values doesn't start throwing after the provider upgrade.

    Turning it off does not require a schema migration or data fix — the columns stay timestamptz and your existing UTC data is untouched. It's a runtime/code change: with it off, any DateTime that isn't Kind=Utc throws immediately when written to a timestamptz column (... only UTC is supported) instead of being silently converted.

    Why the filter doesn't match the timezone (on 9.2)

    One thing to be aware of on 9.2: the __timezone header and UseAbpTimeZone() don't participate in binding the startTime/endTime filters at all — the model binder just normalizes the incoming value to Kind=Utc without any timezone conversion. So a query string value without an offset, like 2026-07-02T14:02:00, is treated as 14:02Z, not as 14:02 Johannesburg (12:02Z). Only a value that carries an explicit offset or Z is honored — e.g. 2026-07-02T14:02:00+02:00 is correctly read as 12:02Z.

    So if the UI sends the picked local time without an offset, the query ends up off by your UTC offset. On 9.2 the way to make it match is to send the filter bounds already converted to UTC (with Z), or with an explicit offset. This is the same on any host, so it isn't tied to the Npgsql switch.

    What changes on v10

    This is relevant to your v10 question. ABP 10.2+ changed the DateTime model binding so that a value with no offset is now converted from the current request timezone (the __timezone/UseAbpTimeZone value) to UTC before querying. So on 10.2+ the bare local time from the client is interpreted using __timezone as you'd expect, whereas on 9.2 you have to send UTC explicitly. If moving the timezone handling out of your client code is worth it, that's a reason to upgrade. The template still keeps EnableLegacyTimestampBehavior = true in v10, and there are no ABP-specific migration steps for toggling it, since the switch is owned by Npgsql.

    The local vs cloud difference

    I can't say for sure what's behind this part yet without seeing the actual request. What I can say is that the standard filter binding is host-independent — the bound value is the same on a UTC+2 box and a UTC box — so a genuine difference between environments would most likely come from a DateTime reaching the query that isn't Kind=Utc. The usual source is custom or reporting code that builds filters with DateTime.Now / DateTime.Today / .ToLocalTime() instead of IClock: those are Kind=Local, and with the legacy switch true they get converted using the host machine's timezone, which would explain different boundaries per host. That's a direction to check, not a conclusion.

    To narrow it down, it would help to capture the same filter in both environments and compare:

    1. the raw audit-logs request (the startTime/endTime query values) from local and from cloud, and
    2. the number of rows each returns.

    If the two requests carry different values, it's the client computing UTC differently; if the requests are identical but the results differ, it's a server-side path passing a non-UTC value. In that second case, temporarily setting the switch to false in a staging environment is a fast way to surface it — a non-UTC value then throws right at the query and the stack trace points at the code that built it (staging only; in production it would make every non-UTC write fail). A snippet of any custom code that queries the logs directly would also help.

    On (a) vs (b)

    Keep audit/security timestamps as timestamptz and store UTC. Don't switch to timestamp without time zone — that just reintroduces ambiguity; these logs are absolute instants, which is exactly what timestamptz represents.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 21, 2026, 06:18
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.