Skip to content

Use UTC for system dates in Umbraco #19822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: v17/dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using System.ComponentModel;

namespace Umbraco.Cms.Core.Configuration.Models;

/// <summary>
/// Typed configuration options used for migration of system dates to UTC from a Umbraco 16 or lower solution.
/// </summary>
[UmbracoOptions(Constants.Configuration.ConfigSystemDateMigration)]
public class SystemDateMigrationSettings
{
private const bool StaticEnabled = true;

/// <summary>
/// Gets or sets a value indicating whether the migration is enabled.
/// </summary>
[DefaultValue(StaticEnabled)]
public bool Enabled { get; set; } = StaticEnabled;

/// <summary>
/// Gets or sets the local server timezone standard name.
/// If not provided, the local server time zone is detected.
/// </summary>
[DefaultValue(null)]
public string? LocalServerTimeZone { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.

using Microsoft.Extensions.Options;

namespace Umbraco.Cms.Core.Configuration.Models.Validation;

/// <summary>
/// Validator for configuration representated as <see cref="SystemDateMigrationSettings" />.
/// </summary>
public class SystemDateMigrationSettingsValidator
: IValidateOptions<SystemDateMigrationSettings>
{
/// <inheritdoc />
public ValidateOptionsResult Validate(string? name, SystemDateMigrationSettings options)
{
if (string.IsNullOrWhiteSpace(options.LocalServerTimeZone))
{
return ValidateOptionsResult.Success;
}

if (TimeZoneInfo.TryFindSystemTimeZoneById(options.LocalServerTimeZone, out _) is false)
{
return ValidateOptionsResult.Fail(
$"Configuration entry {Constants.Configuration.ConfigSystemDateMigration} contains an invalid time zone: {options.LocalServerTimeZone}.");
}

return ValidateOptionsResult.Success;
}
}
1 change: 1 addition & 0 deletions src/Umbraco.Core/Constants-Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static class Configuration
public const string ConfigActiveDirectory = ConfigPrefix + "ActiveDirectory";
public const string ConfigMarketplace = ConfigPrefix + "Marketplace";
public const string ConfigLegacyPasswordMigration = ConfigPrefix + "LegacyPasswordMigration";
public const string ConfigSystemDateMigration = ConfigPrefix + "SystemDateMigration";
public const string ConfigContent = ConfigPrefix + "Content";
public const string ConfigDeliveryApi = ConfigPrefix + "DeliveryApi";
public const string ConfigCoreDebug = ConfigCorePrefix + "Debug";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder)
builder.Services.AddSingleton<IValidateOptions<RequestHandlerSettings>, RequestHandlerSettingsValidator>();
builder.Services.AddSingleton<IValidateOptions<UnattendedSettings>, UnattendedSettingsValidator>();
builder.Services.AddSingleton<IValidateOptions<SecuritySettings>, SecuritySettingsValidator>();
builder.Services.AddSingleton<IValidateOptions<SystemDateMigrationSettings>, SystemDateMigrationSettingsValidator>();

// Register configuration sections.
builder
Expand Down Expand Up @@ -86,7 +87,8 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder)
.AddUmbracoOptions<HelpPageSettings>()
.AddUmbracoOptions<DataTypesSettings>()
.AddUmbracoOptions<WebhookSettings>()
.AddUmbracoOptions<CacheSettings>();
.AddUmbracoOptions<CacheSettings>()
.AddUmbracoOptions<SystemDateMigrationSettings>();

// Configure connection string and ensure it's updated when the configuration changes
builder.Services.AddSingleton<IConfigureOptions<ConnectionStrings>, ConfigureConnectionStrings>();
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 @@ -1248,7 +1248,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 @@ -1963,7 +1963,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 @@ -2378,7 +2378,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 @@ -3162,7 +3162,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 @@ -3430,7 +3430,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 @@ -3705,7 +3705,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
Loading