Skip to content

Commit d623476

Browse files
Use UTC for system dates in Umbraco (#19822)
* 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 <[email protected]> * Further updates from code review. --------- Co-authored-by: Laura Neto <[email protected]> * 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 <[email protected]>
1 parent b427a8c commit d623476

File tree

176 files changed

+951
-5459
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

176 files changed

+951
-5459
lines changed

src/Umbraco.Cms.Api.Management/Controllers/Security/BackOfficeController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ public async Task<IActionResult> Token()
254254
if (associatedUser is not null)
255255
{
256256
// log current datetime as last login (this also ensures that the user is not flagged as inactive)
257-
associatedUser.LastLoginDateUtc = DateTime.UtcNow;
257+
associatedUser.LastLoginDate = DateTime.UtcNow;
258258
await _backOfficeUserManager.UpdateAsync(associatedUser);
259259

260260
return await SignInBackOfficeUser(associatedUser, request);

src/Umbraco.Cms.Persistence.SqlServer/Services/SqlServerSyntaxProvider.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -312,11 +312,8 @@ protected override string FormatIdentity(ColumnDefinition column) =>
312312
return "NEWID()";
313313
case SystemMethods.CurrentDateTime:
314314
return "GETDATE()";
315-
316-
// case SystemMethods.NewSequentialId:
317-
// return "NEWSEQUENTIALID()";
318-
// case SystemMethods.CurrentUTCDateTime:
319-
// return "GETUTCDATE()";
315+
case SystemMethods.CurrentUTCDateTime:
316+
return "GETUTCDATE()";
320317
}
321318

322319
return null;

src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSyntaxProvider.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,13 +205,14 @@ private static string GetColumnName(PropertyInfo column)
205205
/// <inheritdoc />
206206
protected override string? FormatSystemMethods(SystemMethods systemMethod)
207207
{
208-
// TODO: SQLite
209208
switch (systemMethod)
210209
{
211210
case SystemMethods.NewGuid:
212-
return "NEWID()"; // No NEWID() in SQLite perhaps try RANDOM()
211+
return null; // Not available in SQLite.
213212
case SystemMethods.CurrentDateTime:
214-
return "DATE()"; // No GETDATE() trying DATE()
213+
return null; // Not available in SQLite.
214+
case SystemMethods.CurrentUTCDateTime:
215+
return "CURRENT_TIMESTAMP";
215216
}
216217

217218
return null;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Umbraco.
2+
// See LICENSE for more details.
3+
4+
using System.ComponentModel;
5+
6+
namespace Umbraco.Cms.Core.Configuration.Models;
7+
8+
/// <summary>
9+
/// Typed configuration options used for migration of system dates to UTC from a Umbraco 16 or lower solution.
10+
/// </summary>
11+
[UmbracoOptions(Constants.Configuration.ConfigSystemDateMigration)]
12+
public class SystemDateMigrationSettings
13+
{
14+
private const bool StaticEnabled = true;
15+
16+
/// <summary>
17+
/// Gets or sets a value indicating whether the migration is enabled.
18+
/// </summary>
19+
[DefaultValue(StaticEnabled)]
20+
public bool Enabled { get; set; } = StaticEnabled;
21+
22+
/// <summary>
23+
/// Gets or sets the local server timezone standard name.
24+
/// If not provided, the local server time zone is detected.
25+
/// </summary>
26+
[DefaultValue(null)]
27+
public string? LocalServerTimeZone { get; set; }
28+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) Umbraco.
2+
// See LICENSE for more details.
3+
4+
using Microsoft.Extensions.Options;
5+
6+
namespace Umbraco.Cms.Core.Configuration.Models.Validation;
7+
8+
/// <summary>
9+
/// Validator for configuration representated as <see cref="SystemDateMigrationSettings" />.
10+
/// </summary>
11+
public class SystemDateMigrationSettingsValidator
12+
: IValidateOptions<SystemDateMigrationSettings>
13+
{
14+
/// <inheritdoc />
15+
public ValidateOptionsResult Validate(string? name, SystemDateMigrationSettings options)
16+
{
17+
if (string.IsNullOrWhiteSpace(options.LocalServerTimeZone))
18+
{
19+
return ValidateOptionsResult.Success;
20+
}
21+
22+
if (TimeZoneInfo.TryFindSystemTimeZoneById(options.LocalServerTimeZone, out _) is false)
23+
{
24+
return ValidateOptionsResult.Fail(
25+
$"Configuration entry {Constants.Configuration.ConfigSystemDateMigration} contains an invalid time zone: {options.LocalServerTimeZone}.");
26+
}
27+
28+
return ValidateOptionsResult.Success;
29+
}
30+
}

src/Umbraco.Core/Constants-Configuration.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public static class Configuration
2828
public const string ConfigActiveDirectory = ConfigPrefix + "ActiveDirectory";
2929
public const string ConfigMarketplace = ConfigPrefix + "Marketplace";
3030
public const string ConfigLegacyPasswordMigration = ConfigPrefix + "LegacyPasswordMigration";
31+
public const string ConfigSystemDateMigration = ConfigPrefix + "SystemDateMigration";
3132
public const string ConfigContent = ConfigPrefix + "Content";
3233
public const string ConfigDeliveryApi = ConfigPrefix + "DeliveryApi";
3334
public const string ConfigCoreDebug = ConfigCorePrefix + "Debug";

src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder)
4747
builder.Services.AddSingleton<IValidateOptions<RequestHandlerSettings>, RequestHandlerSettingsValidator>();
4848
builder.Services.AddSingleton<IValidateOptions<UnattendedSettings>, UnattendedSettingsValidator>();
4949
builder.Services.AddSingleton<IValidateOptions<SecuritySettings>, SecuritySettingsValidator>();
50+
builder.Services.AddSingleton<IValidateOptions<SystemDateMigrationSettings>, SystemDateMigrationSettingsValidator>();
5051

5152
// Register configuration sections.
5253
builder
@@ -86,7 +87,8 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder)
8687
.AddUmbracoOptions<HelpPageSettings>()
8788
.AddUmbracoOptions<DataTypesSettings>()
8889
.AddUmbracoOptions<WebhookSettings>()
89-
.AddUmbracoOptions<CacheSettings>();
90+
.AddUmbracoOptions<CacheSettings>()
91+
.AddUmbracoOptions<SystemDateMigrationSettings>();
9092

9193
// Configure connection string and ensure it's updated when the configuration changes
9294
builder.Services.AddSingleton<IConfigureOptions<ConnectionStrings>, ConfigureConnectionStrings>();
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
namespace Umbraco.Cms.Core.Extensions;
2+
3+
/// <summary>
4+
/// Provides extensions on <see cref="DateTime"/> purely when building entities from persistence DTOs.
5+
/// </summary>
6+
public static class EntityFactoryDateTimeExtensions
7+
{
8+
/// <summary>
9+
/// Ensures the provided DateTime is in UTC format.
10+
/// </summary>
11+
/// <remarks>
12+
/// We need this in the particular cases of building entities from persistence DTOs. NPoco isn't consistent in what it returns
13+
/// here across databases, sometimes providing a Kind of Unspecified. We are consistently persisting UTC for Umbraco's system
14+
/// dates so we should enforce this Kind on the entity before exposing it further within the Umbraco application.
15+
/// </remarks>
16+
public static DateTime EnsureUtc(this DateTime dateTime)
17+
{
18+
if (dateTime.Kind == DateTimeKind.Utc)
19+
{
20+
return dateTime;
21+
}
22+
23+
if (dateTime.Kind == DateTimeKind.Local)
24+
{
25+
return dateTime.ToUniversalTime();
26+
}
27+
28+
return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
29+
}
30+
}

src/Umbraco.Core/Models/AuditEntry.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public string? PerformingIp
4949
}
5050

5151
/// <inheritdoc />
52-
public DateTime EventDateUtc
52+
public DateTime EventDate
5353
{
5454
get => CreateDate;
5555
set => CreateDate = value;

src/Umbraco.Core/Models/ContentBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ public void SetCultureName(string? name, string? culture)
307307
// set
308308
else if (GetCultureName(culture) != name)
309309
{
310-
this.SetCultureInfo(culture!, name, DateTime.Now);
310+
this.SetCultureInfo(culture!, name, DateTime.UtcNow);
311311
}
312312
}
313313

0 commit comments

Comments
 (0)