diff --git a/src/Umbraco.Cms.Api.Management/Factories/DocumentVersionPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DocumentVersionPresentationFactory.cs
index 2c3b80fd3c..6166d38d2d 100644
--- a/src/Umbraco.Cms.Api.Management/Factories/DocumentVersionPresentationFactory.cs
+++ b/src/Umbraco.Cms.Api.Management/Factories/DocumentVersionPresentationFactory.cs
@@ -26,7 +26,7 @@ internal sealed class DocumentVersionPresentationFactory : IDocumentVersionPrese
new ReferenceByIdModel(_entityService.GetKey(contentVersion.ContentTypeId, UmbracoObjectTypes.DocumentType)
.Result),
new ReferenceByIdModel(await _userIdKeyResolver.GetAsync(contentVersion.UserId)),
- new DateTimeOffset(contentVersion.VersionDate, TimeSpan.Zero), // todo align with datetime offset rework
+ new DateTimeOffset(contentVersion.VersionDate),
contentVersion.CurrentPublishedVersion,
contentVersion.CurrentDraftVersion,
contentVersion.PreventCleanup);
diff --git a/src/Umbraco.Core/Models/ContentVersionMeta.cs b/src/Umbraco.Core/Models/ContentVersionMeta.cs
index cf95257716..b9b1be6080 100644
--- a/src/Umbraco.Core/Models/ContentVersionMeta.cs
+++ b/src/Umbraco.Core/Models/ContentVersionMeta.cs
@@ -37,7 +37,7 @@ public class ContentVersionMeta
public int UserId { get; }
- public DateTime VersionDate { get; }
+ public DateTime VersionDate { get; private set; }
public bool CurrentPublishedVersion { get; }
@@ -47,5 +47,7 @@ public class 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}";
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/ContentBaseFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/ContentBaseFactory.cs
index fed80e753f..ffcc6447eb 100644
--- a/src/Umbraco.Infrastructure/Persistence/Factories/ContentBaseFactory.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Factories/ContentBaseFactory.cs
@@ -39,8 +39,11 @@ internal class ContentBaseFactory
content.CreatorId = nodeDto.UserId ?? Constants.Security.UnknownUserId;
content.WriterId = contentVersionDto.UserId ?? Constants.Security.UnknownUserId;
- content.CreateDate = nodeDto.CreateDate;
- content.UpdateDate = contentVersionDto.VersionDate;
+
+ // Dates stored in the database are local server time, but for SQL Server, will be considered
+ // as DateTime.Kind = Utc. Fix this so we are consistent when later mapping to DataTimeOffset.
+ content.CreateDate = DateTime.SpecifyKind(nodeDto.CreateDate, DateTimeKind.Local);
+ content.UpdateDate = DateTime.SpecifyKind(contentVersionDto.VersionDate, DateTimeKind.Local);
content.Published = dto.Published;
content.Edited = dto.Edited;
@@ -52,7 +55,7 @@ internal class ContentBaseFactory
content.PublishedVersionId = publishedVersionDto.Id;
if (dto.Published)
{
- content.PublishDate = publishedVersionDto.ContentVersionDto.VersionDate;
+ content.PublishDate = DateTime.SpecifyKind(publishedVersionDto.ContentVersionDto.VersionDate, DateTimeKind.Local);
content.PublishName = publishedVersionDto.ContentVersionDto.Text;
content.PublisherId = publishedVersionDto.ContentVersionDto.UserId;
}
@@ -71,7 +74,7 @@ internal class ContentBaseFactory
}
///
- /// Builds an IMedia item from a dto and content type.
+ /// Builds a Media item from a dto and content type.
///
public static Core.Models.Media BuildEntity(ContentDto dto, IMediaType? contentType)
{
@@ -97,8 +100,8 @@ internal class ContentBaseFactory
content.CreatorId = nodeDto.UserId ?? Constants.Security.UnknownUserId;
content.WriterId = contentVersionDto.UserId ?? Constants.Security.UnknownUserId;
- content.CreateDate = nodeDto.CreateDate;
- content.UpdateDate = contentVersionDto.VersionDate;
+ content.CreateDate = DateTime.SpecifyKind(nodeDto.CreateDate, DateTimeKind.Local);
+ content.UpdateDate = DateTime.SpecifyKind(contentVersionDto.VersionDate, DateTimeKind.Local);
// reset dirty initial properties (U4-1946)
content.ResetDirtyProperties(false);
@@ -111,7 +114,7 @@ internal class ContentBaseFactory
}
///
- /// Builds an IMedia item from a dto and content type.
+ /// Builds a Member item from a dto and member type.
///
public static Member BuildEntity(MemberDto dto, IMemberType? contentType)
{
@@ -126,7 +129,9 @@ internal class ContentBaseFactory
content.Id = dto.NodeId;
content.SecurityStamp = dto.SecurityStampToken;
- content.EmailConfirmedDate = dto.EmailConfirmedDate;
+ content.EmailConfirmedDate = dto.EmailConfirmedDate.HasValue
+ ? DateTime.SpecifyKind(dto.EmailConfirmedDate.Value, DateTimeKind.Local)
+ : null;
content.PasswordConfiguration = dto.PasswordConfig;
content.Key = nodeDto.UniqueId;
content.VersionId = contentVersionDto.Id;
@@ -140,14 +145,20 @@ internal class ContentBaseFactory
content.CreatorId = nodeDto.UserId ?? Constants.Security.UnknownUserId;
content.WriterId = contentVersionDto.UserId ?? Constants.Security.UnknownUserId;
- content.CreateDate = nodeDto.CreateDate;
- content.UpdateDate = contentVersionDto.VersionDate;
+ content.CreateDate = DateTime.SpecifyKind(nodeDto.CreateDate, DateTimeKind.Local);
+ content.UpdateDate = DateTime.SpecifyKind(contentVersionDto.VersionDate, DateTimeKind.Local);
content.FailedPasswordAttempts = dto.FailedPasswordAttempts ?? default;
content.IsLockedOut = dto.IsLockedOut;
content.IsApproved = dto.IsApproved;
- content.LastLoginDate = dto.LastLoginDate;
- content.LastLockoutDate = dto.LastLockoutDate;
- content.LastPasswordChangeDate = dto.LastPasswordChangeDate;
+ content.LastLockoutDate = dto.LastLockoutDate.HasValue
+ ? DateTime.SpecifyKind(dto.LastLockoutDate.Value, DateTimeKind.Local)
+ : null;
+ content.LastLoginDate = dto.LastLoginDate.HasValue
+ ? DateTime.SpecifyKind(dto.LastLoginDate.Value, DateTimeKind.Local)
+ : null;
+ content.LastPasswordChangeDate = dto.LastPasswordChangeDate.HasValue
+ ? DateTime.SpecifyKind(dto.LastPasswordChangeDate.Value, DateTimeKind.Local)
+ : null;
// reset dirty initial properties (U4-1946)
content.ResetDirtyProperties(false);
@@ -186,7 +197,7 @@ internal class ContentBaseFactory
new ContentScheduleDto
{
Action = x.Action.ToString(),
- Date = x.Date,
+ Date = DateTime.SpecifyKind(x.Date, DateTimeKind.Local),
NodeId = entity.Id,
LanguageId = languageRepository.GetIdByIsoCode(x.Culture, false),
Id = x.Id,
@@ -261,7 +272,7 @@ internal class ContentBaseFactory
UserId = entity.CreatorId,
Text = entity.Name,
NodeObjectType = objectType,
- CreateDate = entity.CreateDate,
+ CreateDate = DateTime.SpecifyKind(entity.CreateDate, DateTimeKind.Local),
};
return dto;
@@ -275,7 +286,7 @@ internal class ContentBaseFactory
{
Id = entity.VersionId,
NodeId = entity.Id,
- VersionDate = entity.UpdateDate,
+ VersionDate = DateTime.SpecifyKind(entity.UpdateDate, DateTimeKind.Local),
UserId = entity.WriterId,
Current = true, // always building the current one
Text = entity.Name,
diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/UserFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/UserFactory.cs
index 9a8ae11386..60ec173ca5 100644
--- a/src/Umbraco.Infrastructure/Persistence/Factories/UserFactory.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Factories/UserFactory.cs
@@ -39,16 +39,25 @@ internal static class UserFactory
user.Language = dto.UserLanguage;
user.SecurityStamp = dto.SecurityStampToken;
user.FailedPasswordAttempts = dto.FailedLoginAttempts ?? 0;
- user.LastLockoutDate = dto.LastLockoutDate;
- user.LastLoginDate = dto.LastLoginDate;
- user.LastPasswordChangeDate = dto.LastPasswordChangeDate;
- user.CreateDate = dto.CreateDate;
- user.UpdateDate = dto.UpdateDate;
user.Avatar = dto.Avatar;
user.EmailConfirmedDate = dto.EmailConfirmedDate;
user.InvitedDate = dto.InvitedDate;
user.Kind = (UserKind)dto.Kind;
+ // Dates stored in the database are local server time, but for SQL Server, will be considered
+ // as DateTime.Kind = Utc. Fix this so we are consistent when later mapping to DataTimeOffset.
+ user.LastLockoutDate = dto.LastLockoutDate.HasValue
+ ? DateTime.SpecifyKind(dto.LastLockoutDate.Value, DateTimeKind.Local)
+ : null;
+ user.LastLoginDate = dto.LastLoginDate.HasValue
+ ? DateTime.SpecifyKind(dto.LastLoginDate.Value, DateTimeKind.Local)
+ : null;
+ user.LastPasswordChangeDate = dto.LastPasswordChangeDate.HasValue
+ ? DateTime.SpecifyKind(dto.LastPasswordChangeDate.Value, DateTimeKind.Local)
+ : null;
+ user.CreateDate = DateTime.SpecifyKind(dto.CreateDate, DateTimeKind.Local);
+ user.UpdateDate = DateTime.SpecifyKind(dto.UpdateDate, DateTimeKind.Local);
+
// reset dirty initial properties (U4-1946)
user.ResetDirtyProperties(false);
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs
index d7dc4f8161..e112e360d0 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs
@@ -29,7 +29,7 @@ internal class AuditRepository : EntityRepositoryBase, IAuditRe
List? dtos = Database.Fetch(sql);
- return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters, x.Datestamp)).ToList();
+ return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters, DateTime.SpecifyKind(x.Datestamp, DateTimeKind.Local))).ToList();
}
public void CleanLogs(int maximumAgeOfLogsInMinutes)
@@ -104,12 +104,12 @@ internal class AuditRepository : EntityRepositoryBase, IAuditRe
totalRecords = page.TotalItems;
var items = page.Items.Select(
- dto => new AuditItem(dto.NodeId, Enum.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, dto.Datestamp)).ToList();
+ dto => new AuditItem(dto.NodeId, Enum.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, DateTime.SpecifyKind(dto.Datestamp, DateTimeKind.Local))).ToList();
// map the DateStamp
for (var i = 0; i < items.Count; i++)
{
- items[i].CreateDate = page.Items[i].Datestamp;
+ items[i].CreateDate = DateTime.SpecifyKind(page.Items[i].Datestamp, DateTimeKind.Local);
}
return items;
@@ -149,7 +149,7 @@ internal class AuditRepository : EntityRepositoryBase, IAuditRe
LogDto? dto = Database.First(sql);
return dto == null
? null
- : new AuditItem(dto.NodeId, Enum.Parse(dto.Header), dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, dto.Datestamp);
+ : new AuditItem(dto.NodeId, Enum.Parse(dto.Header), dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, DateTime.SpecifyKind(dto.Datestamp, DateTimeKind.Local));
}
protected override IEnumerable PerformGetAll(params int[]? ids) => throw new NotImplementedException();
@@ -162,7 +162,7 @@ internal class AuditRepository : EntityRepositoryBase, IAuditRe
List? dtos = Database.Fetch(sql);
- return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters, x.Datestamp)).ToList();
+ return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters, DateTime.SpecifyKind(x.Datestamp, DateTimeKind.Local))).ToList();
}
protected override Sql GetBaseQuery(bool isCount)
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
index 72459bd755..1b4f2b1efd 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs
@@ -400,15 +400,17 @@ public class DocumentRepository : ContentRepositoryBase 0 && contentVariations.TryGetValue(content.PublishedVersionId, out contentVariation))
{
foreach (ContentVariation v in contentVariation)
{
- content.SetPublishInfo(v.Culture, v.Name, v.Date);
+ content.SetPublishInfo(v.Culture, v.Name, DateTime.SpecifyKind(v.Date, DateTimeKind.Local));
}
}
diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs
index e922ed3cdb..ef9dd67520 100644
--- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs
+++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentVersionRepository.cs
@@ -1,3 +1,4 @@
+using System.Data;
using NPoco;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
@@ -98,6 +99,16 @@ internal class DocumentVersionRepository : IDocumentVersionRepository
Page? page =
_scopeAccessor.AmbientScope?.Database.Page(pageIndex + 1, pageSize, query);
+ // Dates stored in the database are local server time, but for SQL Server, will be considered
+ // as DateTime.Kind = Utc. Fix this so we are consistent when later mapping to DataTimeOffset.
+ if (page is not null)
+ {
+ foreach (ContentVersionMeta item in page.Items)
+ {
+ item.SpecifyVersionDateKind(DateTimeKind.Local);
+ }
+ }
+
totalRecords = page?.TotalItems ?? 0;
return page?.Items;