Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4ae9ffd
Updated persistence DTOs defining default dates to use UTC.
AndyButland Jul 8, 2025
b195591
Remove ForceToUtc = false from all persistence DTO attributes (defaul…
AndyButland Jul 8, 2025
92fd29e
Removed use of SpecifyKind setting dates to local.
AndyButland Jul 8, 2025
3315267
Removed unnecessary Utc suffixes on properties.
AndyButland Jul 8, 2025
d21d7f1
Persist current date time with UtcNow.
AndyButland Jul 8, 2025
275e986
Removed further necessary Utc suffixes and fixed failing unit tests.
AndyButland Jul 8, 2025
f64b696
Added migration for SQL server to update database date default constr…
AndyButland Jul 8, 2025
f6d1864
Added comment justifying not providing a migration for SQLite default…
AndyButland Jul 8, 2025
5ceb7c3
Ensure UTC for datetimes created from persistence DTOs.
AndyButland Jul 10, 2025
4c013f3
Ensure UTC when creating dates for published content rendering in Raz…
AndyButland Jul 10, 2025
84d79e7
Fixed migration SQL syntax.
AndyButland Jul 10, 2025
a18366a
Merge branch 'v17/feature/utc-system-dates' into v17/feature/utc-syst…
AndyButland Jul 10, 2025
9f3717f
Introduced AuditItemFactory for creating entries for the backoffice d…
AndyButland Jul 11, 2025
1e9b784
Ensured UTC dates are retrieved for document versions.
AndyButland Jul 11, 2025
3427438
Ensured UTC is returned for backoffice display of last edited and pub…
AndyButland Jul 11, 2025
b0e5202
Fixed SQLite syntax for default current datetime.
AndyButland Jul 11, 2025
e489401
Merge branch 'v17/feature/utc-system-dates' into v17/feature/utc-syst…
AndyButland Jul 23, 2025
bfba823
Apply suggestions from code review
AndyButland Jul 29, 2025
2ef6352
Further updates from code review.
AndyButland Jul 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ public async Task<IActionResult> Token()
if (associatedUser is not null)
{
// log current datetime as last login (this also ensures that the user is not flagged as inactive)
associatedUser.LastLoginDateUtc = DateTime.UtcNow;
associatedUser.LastLoginDate = DateTime.UtcNow;
await _backOfficeUserManager.UpdateAsync(associatedUser);

return await SignInBackOfficeUser(associatedUser, request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,11 +312,8 @@ protected override string FormatIdentity(ColumnDefinition column) =>
return "NEWID()";
case SystemMethods.CurrentDateTime:
return "GETDATE()";

// case SystemMethods.NewSequentialId:
// return "NEWSEQUENTIALID()";
// case SystemMethods.CurrentUTCDateTime:
// return "GETUTCDATE()";
case SystemMethods.CurrentUTCDateTime:
return "GETUTCDATE()";
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,14 @@ private static string GetColumnName(PropertyInfo column)
/// <inheritdoc />
protected override string? FormatSystemMethods(SystemMethods systemMethod)
{
// TODO: SQLite
switch (systemMethod)
{
case SystemMethods.NewGuid:
return "NEWID()"; // No NEWID() in SQLite perhaps try RANDOM()
return null; // Not available in SQLite.
case SystemMethods.CurrentDateTime:
return "DATE()"; // No GETDATE() trying DATE()
return null; // Not available in SQLite.
case SystemMethods.CurrentUTCDateTime:
return "CURRENT_TIMESTAMP";
}

return null;
Expand Down
30 changes: 30 additions & 0 deletions src/Umbraco.Core/Extensions/EntityFactoryDateTimeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Umbraco.Cms.Core.Extensions;

/// <summary>
/// Provides extensions on <see cref="DateTime"/> purely when building entities from persistence DTOs.
/// </summary>
public static class EntityFactoryDateTimeExtensions
{
/// <summary>
/// Ensures the provided DateTime is in UTC format.
/// </summary>
/// <remarks>
/// We need this in the particular cases of building entities from persistence DTOs. NPoco isn't consistent in what it returns
/// here across databases, sometimes providing a Kind of Unspecified. We are consistently persisting UTC for Umbraco's system
/// dates so we should enforce this Kind on the entity before exposing it further within the Umbraco application.
/// </remarks>
public static DateTime EnsureUtc(this DateTime dateTime)
{
if (dateTime.Kind == DateTimeKind.Utc)
{
return dateTime;
}

if (dateTime.Kind == DateTimeKind.Local)
{
return dateTime.ToUniversalTime();
}

return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
}
}
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Models/AuditEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public string? PerformingIp
}

/// <inheritdoc />
public DateTime EventDateUtc
public DateTime EventDate
{
get => CreateDate;
set => CreateDate = value;
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Models/ContentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ public void SetCultureName(string? name, string? culture)
// set
else if (GetCultureName(culture) != name)
{
this.SetCultureInfo(culture!, name, DateTime.Now);
this.SetCultureInfo(culture!, name, DateTime.UtcNow);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Models/ContentRepositoryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public static void TouchCulture(this IContentBase content, string? culture)
return;
}

content.CultureInfos?.AddOrUpdate(culture!, infos.Name, DateTime.Now);
content.CultureInfos?.AddOrUpdate(culture!, infos.Name, DateTime.UtcNow);
}

/// <summary>
Expand Down
6 changes: 4 additions & 2 deletions src/Umbraco.Core/Models/ContentVersionMeta.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Umbraco.Cms.Core.Extensions;

namespace Umbraco.Cms.Core.Models;

public class ContentVersionMeta
Expand Down Expand Up @@ -47,7 +49,7 @@ public ContentVersionMeta(

public string? Username { get; }

public void SpecifyVersionDateKind(DateTimeKind kind) => VersionDate = DateTime.SpecifyKind(VersionDate, kind);

public override string ToString() => $"ContentVersionMeta(versionId: {VersionId}, versionDate: {VersionDate:s}";

public void EnsureUtc() => VersionDate = VersionDate.EnsureUtc();
}
4 changes: 2 additions & 2 deletions src/Umbraco.Core/Models/Entities/EntityExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static class EntityExtensions
/// </summary>
public static void UpdatingEntity(this IEntity entity)
{
DateTime now = DateTime.Now;
DateTime now = DateTime.UtcNow;

if (entity.CreateDate == default)
{
Expand All @@ -32,7 +32,7 @@ public static void UpdatingEntity(this IEntity entity)
/// </summary>
public static void AddingEntity(this IEntity entity)
{
DateTime now = DateTime.Now;
DateTime now = DateTime.UtcNow;
var canBeDirty = entity as ICanBeDirty;

// set the create and update dates, if not already set
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Models/IAuditEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public interface IAuditEntry : IEntity, IRememberBeingDirty
/// <summary>
/// Gets or sets the date and time of the audited event.
/// </summary>
DateTime EventDateUtc { get; set; }
DateTime EventDate { get; set; }

/// <summary>
/// Gets or sets the identifier of the user affected by the audited event.
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Models/ReadOnlyRelation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public ReadOnlyRelation(int id, int parentId, int childId, int relationTypeId, D
}

public ReadOnlyRelation(int parentId, int childId, int relationTypeId)
: this(0, parentId, childId, relationTypeId, DateTime.Now, string.Empty)
: this(0, parentId, childId, relationTypeId, DateTime.UtcNow, string.Empty)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public InternalPublishedContent(IPublishedContentType contentType)
// initialize boring stuff
TemplateId = 0;
WriterId = CreatorId = 0;
CreateDate = UpdateDate = DateTime.Now;
CreateDate = UpdateDate = DateTime.UtcNow;
Version = Guid.Empty;
Path = string.Empty;
ContentType = contentType;
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Services/AuditEntryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ private IAuditEntry WriteInner(
PerformingUserKey = performingUserKey,
PerformingDetails = performingDetails,
PerformingIp = performingIp,
EventDateUtc = eventDateUtc,
EventDate = eventDateUtc,
AffectedUserId = affectedUserId ?? Constants.Security.UnknownUserId, // Default to UnknownUserId as it is non-nullable
AffectedUserKey = affectedUserKey,
AffectedDetails = affectedDetails,
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Services/ConsentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public IConsent RegisterConsent(string source, string context, string action, Co
Source = source,
Context = context,
Action = action,
CreateDate = DateTime.Now,
CreateDate = DateTime.UtcNow,
State = state,
Comment = comment,
};
Expand Down
12 changes: 6 additions & 6 deletions src/Umbraco.Core/Services/ContentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1240,7 +1240,7 @@ public PublishResult Publish(IContent content, string[] cultures, int userId = C

// publish the culture(s)
// we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
var publishTime = DateTime.Now;
var publishTime = DateTime.UtcNow;
foreach (CultureImpact? impact in impacts)
{
content.PublishCulture(impact, publishTime, _propertyEditorCollection);
Expand Down Expand Up @@ -1955,7 +1955,7 @@ private bool PublishBranch_PublishCultures(IContent content, HashSet<string> cul
// variant content type - publish specified cultures
// invariant content type - publish only the invariant culture

var publishTime = DateTime.Now;
var publishTime = DateTime.UtcNow;
if (content.ContentType.VariesByCulture())
{
return culturesToPublish.All(culture =>
Expand Down Expand Up @@ -2370,7 +2370,7 @@ public void DeleteVersion(int id, int versionId, bool deletePriorVersions, int u
if (deletePriorVersions)
{
IContent? content = GetVersion(versionId);
DeleteVersions(id, content?.UpdateDate ?? DateTime.Now, userId);
DeleteVersions(id, content?.UpdateDate ?? DateTime.UtcNow, userId);
}

scope.WriteLock(Constants.Locks.ContentTree);
Expand Down Expand Up @@ -3141,7 +3141,7 @@ private PublishResult StrategyCanPublish(
.ToArray();

// publish the culture(s)
var publishTime = DateTime.Now;
var publishTime = DateTime.UtcNow;
if (!impactsToPublish.All(impact => content.PublishCulture(impact, publishTime, _propertyEditorCollection)))
{
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
Expand Down Expand Up @@ -3409,7 +3409,7 @@ private PublishResult StrategyUnpublish(IContent content, EventMessages evtMsgs)
// otherwise it would remain released == published
ContentScheduleCollection contentSchedule = _documentRepository.GetContentSchedule(content.Id);
IReadOnlyList<ContentSchedule> pastReleases =
contentSchedule.GetPending(ContentScheduleAction.Expire, DateTime.Now);
contentSchedule.GetPending(ContentScheduleAction.Expire, DateTime.UtcNow);
foreach (ContentSchedule p in pastReleases)
{
contentSchedule.Remove(p);
Expand Down Expand Up @@ -3684,7 +3684,7 @@ public IContent CreateBlueprintFromContent(
scope.Complete();
}

DateTime now = DateTime.Now;
DateTime now = DateTime.UtcNow;
foreach (var culture in cultures)
{
foreach (IProperty property in blueprint.Properties)
Expand Down
6 changes: 3 additions & 3 deletions src/Umbraco.Core/Services/KeyValueService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ public void SetValue(string key, string value)
IKeyValue? keyValue = _repository.Get(key);
if (keyValue == null)
{
keyValue = new KeyValue { Identifier = key, Value = value, UpdateDate = DateTime.Now };
keyValue = new KeyValue { Identifier = key, Value = value, UpdateDate = DateTime.UtcNow };
}
else
{
keyValue.Value = value;
keyValue.UpdateDate = DateTime.Now;
keyValue.UpdateDate = DateTime.UtcNow;
}

_repository.Save(keyValue);
Expand Down Expand Up @@ -80,7 +80,7 @@ public bool TrySetValue(string key, string originalValue, string newValue)
}

keyValue.Value = newValue;
keyValue.UpdateDate = DateTime.Now;
keyValue.UpdateDate = DateTime.UtcNow;
_repository.Save(keyValue);

scope.Complete();
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Services/LogViewerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ private LogTimePeriod GetTimePeriod(DateTimeOffset? startDate, DateTimeOffset? e
{
if (startDate is null || endDate is null)
{
DateTime now = DateTime.Now;
DateTime now = DateTime.UtcNow;
if (startDate is null)
{
startDate = now.AddDays(-1);
Expand Down
4 changes: 2 additions & 2 deletions src/Umbraco.Core/Services/ServerRegistrationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ public void TouchServer(string serverAddress, TimeSpan staleTimeout)

if (server == null)
{
server = new ServerRegistration(serverAddress, serverIdentity, DateTime.Now);
server = new ServerRegistration(serverAddress, serverIdentity, DateTime.UtcNow);
}
else
{
server.ServerAddress = serverAddress; // should not really change but it might!
server.UpdateDate = DateTime.Now;
server.UpdateDate = DateTime.UtcNow;
}

server.IsActive = true;
Expand Down
4 changes: 2 additions & 2 deletions src/Umbraco.Core/Services/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,7 @@ public async Task<Attempt<UserInvitationResult, UserOperationStatus>> InviteAsyn
throw new PanicException("Was unable to get user after creating it");
}

invitedUser.InvitedDate = DateTime.Now;
invitedUser.InvitedDate = DateTime.UtcNow;
invitedUser.ClearGroups();
foreach(IUserGroup userGroup in userGroups)
{
Expand Down Expand Up @@ -827,7 +827,7 @@ public async Task<Attempt<UserInvitationResult, UserOperationStatus>> ResendInvi
}

// re-inviting so update invite date
invitedUser.InvitedDate = DateTime.Now;
invitedUser.InvitedDate = DateTime.UtcNow;
await userStore.SaveAsync(invitedUser);

Attempt<UserInvitationResult, UserOperationStatus> invitationAttempt = await SendInvitationAsync(performingUser, serviceScope, invitedUser, model.Message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public Task RunJobAsync()
}


var count = _service.PerformContentVersionCleanup(DateTime.Now).Count;
var count = _service.PerformContentVersionCleanup(DateTime.UtcNow).Count;

if (count > 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task RunJobAsync()
using (ICoreScope scope = _coreScopeProvider.CreateCoreScope())
{
scope.ReadLock(Constants.Locks.WebhookLogs);
webhookLogs = await _webhookLogRepository.GetOlderThanDate(DateTime.Now - TimeSpan.FromDays(_webhookSettings.KeepLogsForDays));
webhookLogs = await _webhookLogRepository.GetOlderThanDate(DateTime.UtcNow - TimeSpan.FromDays(_webhookSettings.KeepLogsForDays));
scope.Complete();
}

Expand Down
Loading
Loading