Skip to content

Persist and expose Umbraco system dates as UTC#19705

Merged
AndyButland merged 19 commits intov17/feature/utc-system-datesfrom
v17/feature/utc-system-dates-database-dates-persist-utc
Jul 30, 2025
Merged

Persist and expose Umbraco system dates as UTC#19705
AndyButland merged 19 commits intov17/feature/utc-system-datesfrom
v17/feature/utc-system-dates-database-dates-persist-utc

Conversation

@AndyButland
Copy link
Contributor

@AndyButland AndyButland commented Jul 10, 2025

Prerequisites

  • I have added steps to test this contribution in the description below

Description

This is a first PR toward making Umbraco's handling of system dates more consistent by using UTC throughout. It's targeted at v17/feature/utc-system-dates, a feature branch taken from v17/dev, with the idea that we create a few, smaller PRs into the feature branch and then finally merge it into v17/dev when we are happy with it.

When merged, this PR will make the following changes:

  • Remove NPoco attributes that would ensure we didn't treat retrieved datetimes as UTC, as we now do want to do this and use the default behaviour.
  • Removed cases where we use SpecifyKind to ensure we have local server time when we have otherwise retrieved UTC. Again UTC is what we want now.
  • Removed some UTC suffixes from property names. Now we are using UTC throughout, we don't need to have this suffix to indicate this.
  • Updated all cases relating to persisted system dates where we explicitly set a date to DateTime.Now to use DateTime.UtcNow.
  • Migrate existing database date constraints from server datetime to UTC datetime (SQLServer only - as noted in comments, I don't think this is necessary for SQLite given the limited ALTER TABLE support, the existing constraints aren't correct, and we don't rely on them in application code).
  • Updated the persistence DTOs such that they no longer create database constraints on new installs for the current datetime, rather for the UTC datetime.
  • Added an extension method that ensures UTC when we expect it, as currently, at least with some databases, NPoco returns an unspecified Kind, and it's the same when retrieving from the hybrid cache. Used this in factory methods to ensure entities and published content are built with datetimes as UTC.
    • It would be nice to avoid this, as it has led to a few cases where I've had to chase down dates that were coming through incorrectly in the management API and fix them in the appropriate repository or factory method. But it seems it's necessary. Doing it here is at least encapsulated somewhat in factories where entities are built, and this is the point where we know for sure we have UTC and by setting the Kind we ensure anywhere downstream gets this too.

Note that the following is not part of this PR, so isn't expected to work correctly:

  • Migration of existing system dates from server time to UTC
  • No changes have been made to content dates via the datetime picker.

Note also that I've removed some tests from files that were completely commented out (since Umbraco 15 I believe, with the introduction of the Hybrid cache). Most of them used dates and would get out of sync if we kept them, so I thought it was better now to remove.

Testing

Date Persistence

Easiest for testing to start with is to start off with a clean database and install Umbraco. Using database queries like the following you can verify that the default data installed uses UTC:

select key, updated from umbracoKeyValue
select id,text,createDate from umbracoNode
select nodeId, versionDate from umbracoContentVersion
select * from umbracoContentSchedule
select userGroupAlias, createDate,updateDate from umbracoUserGroup
select id, lastPasswordChangeDate, lastLoginDate, createDate, updateDate from umbracoUser
select eventDetails, eventDateUtc from umbracoAudit
select logComment, datestamp from umbracoLog

Then make some updates that will affect dates, e.g.:

  • Logout and in as a user
  • Update a user group
  • Create a document type
  • Create some content based on that document type, save it, and schedule it.
  • Verify with the above and similar database queries that the expected UTC dates are stored.

Date Presentation

Management API

Verify management API endpoints are returning the expected dates, e.g.:

GET /umbraco/management/api/v1/document/{id}

Should return datetime responses like:

  ...
  "variants": [
    {
      "state": "Draft",
      "publishDate": null,
      "scheduledPublishDate": "2025-07-17T10:49:00+00:00",
      "scheduledUnpublishDate": null,
      "createDate": "2025-07-10T10:45:49.3874959+00:00",
      "updateDate": "2025-07-10T10:45:49.3874959+00:00",
      "culture": null,
      "segment": null,
      "name": "Test"
    }
  ]
  ...
Backoffice

Verify that the backoffice is displaying the dates in the user's local time. I haven't touched this, but seems they "just work" due to the use of localized dates, so long as we are providing a date with timezone information from the management API, which we are.

Delivery API

Verify delivery API endpoints are returning the expected dates, e.g.:

GET /umbraco/delivery/api/v2/content/item/{id}

Should return datetime responses like:

{
    "contentType": "testPage",
    "name": "Test",
    "createDate": "2025-07-10T10:45:49.3874959Z",
    "updateDate": "2025-07-10T12:40:35.9592863Z",
Templates

Verify that Razor rendering makes available the system dates as UTC, e.g. with:

<div>Created On: @Model.CreateDate (Kind: @Model.CreateDate.Kind)</div>
<div>Updated On: @Model.UpdateDate (Kind: @Model.UpdateDate.Kind)</div>

Migration of Database DateTime Default Constraints

For this one take an Umbraco database for Umbraco 16, and verify for example that the umbracoNode.createDate table has a default value of getdate().

Then connect the database to the code in this PR and restart so the migration runs.

You should find that the default is now getutcdate().

@AndyButland AndyButland marked this pull request as ready for review July 10, 2025 17:26
@AndyButland AndyButland changed the title Persist Umbraco system dates as UTC Persist and expose Umbraco system dates as UTC Jul 11, 2025
Copy link
Contributor

Copilot AI left a comment

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 systematically replaces DateTime.Now with DateTime.UtcNow across test files to align with Umbraco's move to using UTC for system dates. This change ensures test data uses UTC timestamps consistently, supporting the broader goal of making Umbraco's datetime handling more predictable by standardizing on UTC throughout the system.

  • Replace all test builders and mock objects to use UTC timestamps instead of local time
  • Update property references from UTC-suffixed names to standard names (e.g., LastLoginDateUtcLastLoginDate)
  • Remove large commented-out test files that were disabled since Umbraco 15

Reviewed Changes

Copilot reviewed 169 out of 169 changed files in this pull request and generated no comments.

Show a summary per file
File Description
Multiple test builder files Updated to use DateTime.UtcNow instead of DateTime.Now for consistent UTC timestamps
MemberUserStoreTests.cs Updated property references and assertions to use non-UTC-suffixed property names
AuditEntryServiceTests.cs Updated audit entry date property reference from EventDateUtc to EventDate
PublishedRouterTests.cs Updated mock content creation to use UTC timestamps
Multiple PublishedCache test files Removed entirely commented-out test files to clean up codebase
Comments suppressed due to low confidence (2)

tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs:195

  • The test assertion on line 195 directly compares DateTime values without considering potential precision differences that could occur during database round-trips or serialization. Consider using a tolerance-based comparison or ensuring both values use the same precision.
        Assert.AreEqual(fakeUser.LastPasswordChangeDate, mockMember.LastPasswordChangeDate);

tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberUserStoreTests.cs:256

  • Similar to the previous assertion, this direct DateTime comparison could be brittle if there are precision differences between the expected and actual values. Consider using Assert.That with a tolerance or ensuring consistent DateTime precision.
        Assert.AreEqual(fakeUser.LastLoginDate.Value, mockMember.LastLoginDate);

Copy link
Contributor

@lauraneto lauraneto left a comment

Choose a reason for hiding this comment

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

Still testing, but already noticed a few things from reviewing the changes.

AndyButland and others added 2 commits July 29, 2025 15:03
Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
Copy link
Contributor

@lauraneto lauraneto left a comment

Choose a reason for hiding this comment

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

Looks good! 🙌
I do think we will need to re-test this for existing databases, when merged with #19798.

@AndyButland AndyButland merged commit a3861e9 into v17/feature/utc-system-dates Jul 30, 2025
4 checks passed
@AndyButland AndyButland deleted the v17/feature/utc-system-dates-database-dates-persist-utc branch July 30, 2025 09:21
AndyButland added a commit that referenced this pull request Aug 22, 2025
* Persist and expose Umbraco system dates as UTC (#19705)

* Updated persistence DTOs defining default dates to use UTC.

* Remove ForceToUtc = false from all persistence DTO attributes (default when not specified is true).

* Removed use of SpecifyKind setting dates to local.

* Removed unnecessary Utc suffixes on properties.

* Persist current date time with UtcNow.

* Removed further necessary Utc suffixes and fixed failing unit tests.

* Added migration for SQL server to update database date default constraints.

* Added comment justifying not providing a migration for SQLite default date constraints.

* Ensure UTC for datetimes created from persistence DTOs.

* Ensure UTC when creating dates for published content rendering in Razor and outputting in delivery API.

* Fixed migration SQL syntax.

* Introduced AuditItemFactory for creating entries for the backoffice document history, so we can control the UTC setting on the retrieved persisted dates.

* Ensured UTC dates are retrieved for document versions.

* Ensured UTC is returned for backoffice display of last edited and published for variant content.

* Fixed SQLite syntax for default current datetime.

* Apply suggestions from code review

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

* Further updates from code review.

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>

* Migrate system dates from local server time to UTC (#19798)

* Add settings for the migration.

* Add migration and implement for SQL server.

* Implement for SQLite.

* Fixes from testing with SQL Server.

* Fixes from testing with SQLite.

* Code tidy.

* Cleaned up usings.

* Removed audit log date from conversion.

* Removed webhook log date from conversion.

* Updated update date initialization on saving dictionary items.

* Updated filter on log queries.

* Use timezone ID instead of system name to work cross-culture.

---------

Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants