diff --git a/src/Abstractions/NexusMods.Abstractions.Games/RunGameTool.cs b/src/Abstractions/NexusMods.Abstractions.Games/RunGameTool.cs
index 8ca435f9c9..f4357d5c53 100644
--- a/src/Abstractions/NexusMods.Abstractions.Games/RunGameTool.cs
+++ b/src/Abstractions/NexusMods.Abstractions.Games/RunGameTool.cs
@@ -207,8 +207,8 @@ private async Task RunThroughHeroic(string type, long appId, CancellationToken c
try
{
- var start = DateTime.UtcNow;
- while (!cancellationToken.IsCancellationRequested && start + timeout > DateTime.UtcNow)
+ var start = TimeProvider.System.GetTimestamp();
+ while (!cancellationToken.IsCancellationRequested && TimeProvider.System.GetElapsedTime(start) > timeout)
{
var processes = Process.GetProcessesByName(processName);
var target = existingProcesses is not null
diff --git a/src/Abstractions/NexusMods.Abstractions.IO/StreamFactories/NativeFileStreamFactory.cs b/src/Abstractions/NexusMods.Abstractions.IO/StreamFactories/NativeFileStreamFactory.cs
index 3c09b84f42..3c9b7bbc4e 100644
--- a/src/Abstractions/NexusMods.Abstractions.IO/StreamFactories/NativeFileStreamFactory.cs
+++ b/src/Abstractions/NexusMods.Abstractions.IO/StreamFactories/NativeFileStreamFactory.cs
@@ -8,7 +8,6 @@ namespace NexusMods.Abstractions.IO.StreamFactories;
public class NativeFileStreamFactory : IStreamFactory
{
private AbsolutePath _file;
- private DateTime? _lastModifiedCache;
///
public Size Size => _file.FileInfo.Size;
diff --git a/src/Abstractions/NexusMods.Abstractions.Loadouts.Synchronizers/ALoadoutSynchronizer.cs b/src/Abstractions/NexusMods.Abstractions.Loadouts.Synchronizers/ALoadoutSynchronizer.cs
index 2287d2a27c..15c45c7a7a 100644
--- a/src/Abstractions/NexusMods.Abstractions.Loadouts.Synchronizers/ALoadoutSynchronizer.cs
+++ b/src/Abstractions/NexusMods.Abstractions.Loadouts.Synchronizers/ALoadoutSynchronizer.cs
@@ -340,7 +340,7 @@ await ActionExtractToDisk(groupings, register, tx,
tx.Add(gameMetadataId, GameInstallMetadata.LastSyncedLoadout, loadout.Id);
tx.Add(gameMetadataId, GameInstallMetadata.LastSyncedLoadoutTransaction, EntityId.From(tx.ThisTxId.Value));
tx.Add(gameMetadataId, GameInstallMetadata.LastScannedDiskStateTransaction, EntityId.From(tx.ThisTxId.Value));
- tx.Add(loadout.Id, Loadout.LastAppliedDateTime, DateTime.UtcNow);
+ tx.Add(loadout.Id, Loadout.LastAppliedDateTime, TimeProvider.System.GetLocalNow());
await tx.Commit();
loadout = loadout.Rebase();
@@ -499,7 +499,7 @@ await _fileStore.ExtractFiles(toExtract.Select(item =>
{
tx.Add(entry.Disk.Value.Id, DiskStateEntry.Hash, entry.LoadoutFileHash.Value);
tx.Add(entry.Disk.Value.Id, DiskStateEntry.Size, entry.LoadoutFileSize.Value);
- tx.Add(entry.Disk.Value.Id, DiskStateEntry.LastModified, DateTime.UtcNow);
+ tx.Add(entry.Disk.Value.Id, DiskStateEntry.LastModified, TimeProvider.System.GetLocalNow());
}
else
{
@@ -508,7 +508,7 @@ await _fileStore.ExtractFiles(toExtract.Select(item =>
Path = entry.Path.ToGamePathParentTuple(gameMetadataId),
Hash = entry.LoadoutFileHash.Value,
Size = entry.LoadoutFileSize.Value,
- LastModified = DateTime.UtcNow,
+ LastModified = TimeProvider.System.GetLocalNow(),
GameId = gameMetadataId,
};
}
@@ -591,7 +591,7 @@ private async Task ActionIngestFromDisk(SyncActionGroupings groupi
tx.Add(prevLoadoutFile.Id, LoadoutFile.Hash, file.Disk.Value.Hash);
tx.Add(prevLoadoutFile.Id, LoadoutFile.Size, file.Disk.Value.Size);
- tx.Add(file.Disk.Value.Id, DiskStateEntry.LastModified, DateTime.UtcNow);
+ tx.Add(file.Disk.Value.Id, DiskStateEntry.LastModified, TimeProvider.System.GetLocalNow());
continue;
}
}
@@ -624,7 +624,7 @@ private async Task ActionIngestFromDisk(SyncActionGroupings groupi
LoadoutFileEntry = loadoutFile,
}
);
- tx.Add(file.Disk.Value.Id, DiskStateEntry.LastModified, DateTime.UtcNow);
+ tx.Add(file.Disk.Value.Id, DiskStateEntry.LastModified, TimeProvider.System.GetLocalNow());
}
if (added.Count > 0)
diff --git a/src/Abstractions/NexusMods.Abstractions.Loadouts/DiskStateEntry.cs b/src/Abstractions/NexusMods.Abstractions.Loadouts/DiskStateEntry.cs
index 7b26cec6c3..aed25153c0 100644
--- a/src/Abstractions/NexusMods.Abstractions.Loadouts/DiskStateEntry.cs
+++ b/src/Abstractions/NexusMods.Abstractions.Loadouts/DiskStateEntry.cs
@@ -36,7 +36,7 @@ public partial class DiskStateEntry : IModelDefinition
///
/// The last modified time of the file
///
- public static readonly DateTimeAttribute LastModified = new(Namespace, nameof(LastModified));
+ public static readonly TimestampAttribute LastModified = new(Namespace, nameof(LastModified));
///
/// The owning game installation
diff --git a/src/Abstractions/NexusMods.Abstractions.Loadouts/Loadout.cs b/src/Abstractions/NexusMods.Abstractions.Loadouts/Loadout.cs
index 60d61bd094..eb8020991d 100644
--- a/src/Abstractions/NexusMods.Abstractions.Loadouts/Loadout.cs
+++ b/src/Abstractions/NexusMods.Abstractions.Loadouts/Loadout.cs
@@ -54,14 +54,9 @@ public partial class Loadout : IModelDefinition
///
/// DateTime when the loadout was last applied.
- /// Returns DateTime.MinValue if the loadout has never been applied.
///
- public static readonly DateTimeAttribute LastAppliedDateTime = new(Namespace, nameof(LastAppliedDateTime))
- {
- IsOptional = true,
- DefaultValue = DateTime.MinValue,
- };
-
+ public static readonly TimestampAttribute LastAppliedDateTime = new(Namespace, nameof(LastAppliedDateTime)) { IsOptional = true, };
+
///
/// All items in the Loadout.
///
diff --git a/src/Abstractions/NexusMods.Abstractions.MnemonicDB.Attributes/DateTimeAttribute.cs b/src/Abstractions/NexusMods.Abstractions.MnemonicDB.Attributes/DateTimeAttribute.cs
deleted file mode 100644
index 3193676425..0000000000
--- a/src/Abstractions/NexusMods.Abstractions.MnemonicDB.Attributes/DateTimeAttribute.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using NexusMods.MnemonicDB.Abstractions;
-using NexusMods.MnemonicDB.Abstractions.Attributes;
-using NexusMods.MnemonicDB.Abstractions.ElementComparers;
-using NexusMods.MnemonicDB.Abstractions.ValueSerializers;
-
-namespace NexusMods.Abstractions.MnemonicDB.Attributes;
-
-///
-/// A MneumonicDB attribute for a DateTime value
-///
-///
-/// Depending on use case, consider using transaction timestamps instead of a dedicated DateTimeAttribute.
-///
-public class DateTimeAttribute(string ns, string name) : ScalarAttribute(ns, name)
-{
- ///
- protected override long ToLowLevel(DateTime value) => value.ToBinary();
-
- ///
- protected override DateTime FromLowLevel(long value, AttributeResolver resolver)
- => DateTime.FromBinary(value);
-}
diff --git a/src/Abstractions/NexusMods.Abstractions.MnemonicDB.Attributes/Extensions/EntityExtensions.cs b/src/Abstractions/NexusMods.Abstractions.MnemonicDB.Attributes/Extensions/EntityExtensions.cs
index fbd34baa46..2cfa454ca1 100644
--- a/src/Abstractions/NexusMods.Abstractions.MnemonicDB.Attributes/Extensions/EntityExtensions.cs
+++ b/src/Abstractions/NexusMods.Abstractions.MnemonicDB.Attributes/Extensions/EntityExtensions.cs
@@ -24,11 +24,11 @@ public static TxId MostRecentTxId(this IReadOnlyModel model)
///
/// A default value to return if the model doesn't exist.
///
- public static DateTimeOffset GetCreatedAt(this T model, DateTime? dateTime = null)
+ public static DateTimeOffset GetCreatedAt(this T model, DateTimeOffset? dateTime = null)
where T : IReadOnlyModel
{
if (model.Count == 0)
- return dateTime ?? DateTime.MinValue;
+ return dateTime ?? DateTimeOffset.MinValue;
var tx = new Transaction.ReadOnly(model.Db, EntityId.From(model.Min(m => m.T).Value));
return Transaction.Timestamp.Get(tx);
}
diff --git a/src/Abstractions/NexusMods.Abstractions.NexusModsLibrary/NexusModsFileMetadata.cs b/src/Abstractions/NexusMods.Abstractions.NexusModsLibrary/NexusModsFileMetadata.cs
index 3467b6f823..35cc2dd02f 100644
--- a/src/Abstractions/NexusMods.Abstractions.NexusModsLibrary/NexusModsFileMetadata.cs
+++ b/src/Abstractions/NexusMods.Abstractions.NexusModsLibrary/NexusModsFileMetadata.cs
@@ -35,8 +35,8 @@ public partial class NexusModsFileMetadata : IModelDefinition
///
/// The date the file was uploaded at.
///
- public static readonly DateTimeAttribute UploadedAt = new(Namespace, nameof(UploadedAt));
-
+ public static readonly TimestampAttribute UploadedAt = new(Namespace, nameof(UploadedAt));
+
///
/// The size in bytes of the file.
///
diff --git a/src/Abstractions/NexusMods.Abstractions.NexusModsLibrary/NexusModsModPageMetadata.cs b/src/Abstractions/NexusMods.Abstractions.NexusModsLibrary/NexusModsModPageMetadata.cs
index e627201442..9b902c5c6f 100644
--- a/src/Abstractions/NexusMods.Abstractions.NexusModsLibrary/NexusModsModPageMetadata.cs
+++ b/src/Abstractions/NexusMods.Abstractions.NexusModsLibrary/NexusModsModPageMetadata.cs
@@ -40,7 +40,7 @@ public partial class NexusModsModPageMetadata : IModelDefinition
///
/// The last time the mod page was updated (UTC). This is useful for cache invalidation.
///
- public static readonly DateTimeAttribute UpdatedAt = new(Namespace, nameof(UpdatedAt));
+ public static readonly TimestampAttribute UpdatedAt = new(Namespace, nameof(UpdatedAt));
///
/// Uri for the full sized picture of the mod.
diff --git a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/GameInfo.cs b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/GameInfo.cs
index 4c8f9c8c17..83edff398c 100644
--- a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/GameInfo.cs
+++ b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/GameInfo.cs
@@ -89,7 +89,7 @@ public class GameInfo : IJsonArraySerializable
///
/// Timestamp of when the game was approved by the site staff, expressed as UTC, Coordinated Universal Time.
///
- public DateTime ApprovedDateUtc => DateTimeOffset.FromUnixTimeSeconds(ApprovedDate).UtcDateTime;
+ public DateTimeOffset ApprovedDateUtc => DateTimeOffset.FromUnixTimeSeconds(ApprovedDate);
///
/// Number of views on this file.
diff --git a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ModFile.cs b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ModFile.cs
index 1167260354..93b6d52bc6 100644
--- a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ModFile.cs
+++ b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ModFile.cs
@@ -113,7 +113,7 @@ public class ModFile : IJsonSerializable
/// Expressed as ISO 8601 compatible date/time notation.
///
[JsonPropertyName("uploaded_time")]
- public DateTime UploadedTime { get; set; }
+ public DateTimeOffset UploadedTime { get; set; }
///
/// Version of the mod. See for more details.
diff --git a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ModInfo.cs b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ModInfo.cs
index c7f3ce0425..a3bda3cc9c 100644
--- a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ModInfo.cs
+++ b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ModInfo.cs
@@ -64,13 +64,13 @@ public class ModInfo : IJsonSerializable
public int CreatedTimestamp { get; set; }
[JsonPropertyName("created_time")]
- public DateTime CreatedTime { get; set; }
+ public DateTimeOffset CreatedTime { get; set; }
[JsonPropertyName("updated_timestamp")]
public int UpdatedTimestamp { get; set; }
[JsonPropertyName("updated_time")]
- public DateTime UpdatedTime { get; set; }
+ public DateTimeOffset UpdatedTime { get; set; }
[JsonPropertyName("author")]
public string Author { get; set; } = "";
diff --git a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ModUpdate.cs b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ModUpdate.cs
index c2b86c3f62..25c2ed8548 100644
--- a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ModUpdate.cs
+++ b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ModUpdate.cs
@@ -39,7 +39,7 @@ public class ModUpdate : IJsonArraySerializable
///
/// The last time a file on the mod page was updated.
///
- public DateTime LatestFileUpdatedUtc => DateTimeOffset.FromUnixTimeSeconds(LatestFileUpdated).UtcDateTime;
+ public DateTimeOffset LatestFileUpdatedUtc => DateTimeOffset.FromUnixTimeSeconds(LatestFileUpdated);
///
/// The last time any change was made to the mod page.
@@ -53,7 +53,7 @@ public class ModUpdate : IJsonArraySerializable
///
/// The last time any change was made to the mod page.
///
- public DateTime LatestModActivityUtc => DateTimeOffset.FromUnixTimeSeconds(LatestModActivity).UtcDateTime;
+ public DateTimeOffset LatestModActivityUtc => DateTimeOffset.FromUnixTimeSeconds(LatestModActivity);
///
public static JsonTypeInfo GetArrayTypeInfo() => ModUpdateArrayContext.Default.ModUpdateArray;
diff --git a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ResponseMetadata.cs b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ResponseMetadata.cs
index 70bee92888..6201b96723 100644
--- a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ResponseMetadata.cs
+++ b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/DTOs/ResponseMetadata.cs
@@ -21,7 +21,7 @@ public class ResponseMetadata
///
/// [Rate Limit] Stores the time when the daily limit is next reset.
///
- public DateTime DailyReset { get; set; }
+ public DateTimeOffset DailyReset { get; set; }
///
/// [Rate Limit] Stores the limit your will be reset to once
@@ -37,7 +37,7 @@ public class ResponseMetadata
///
/// [Rate Limit] Stores the time when the hourly limit is next reset.
///
- public DateTime HourlyReset { get; set; }
+ public DateTimeOffset HourlyReset { get; set; }
///
/// Time taken to execute the request server side, in seconds.
@@ -58,11 +58,11 @@ void ParseInt(string headerName, out int output)
output = limit;
}
- void ParseDateTime(string headerName, out DateTime output)
+ void ParseDateTime(string headerName, out DateTimeOffset output)
{
output = default;
if (result.Headers.TryGetValues(headerName, out var values))
- if (DateTime.TryParse(values.First(), out var limit))
+ if (DateTimeOffset.TryParse(values.First(), out var limit))
output = limit;
}
diff --git a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/INexusApiClient.cs b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/INexusApiClient.cs
index 5573c5fd28..80a25b5a60 100644
--- a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/INexusApiClient.cs
+++ b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/INexusApiClient.cs
@@ -63,8 +63,7 @@ public interface INexusApiClient
///
/// Currently available for Premium users only; with some minor exceptions [nxm links].
///
- Task> DownloadLinksAsync(string domain, ModId modId, FileId fileId, NXMKey key, DateTime expireTime, CancellationToken token = default);
-
+ Task> DownloadLinksAsync(string domain, ModId modId, FileId fileId, NXMKey key, DateTimeOffset expireTime, CancellationToken token = default);
///
/// Get the download links for a collection.
diff --git a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/Types/NXMUrl.cs b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/Types/NXMUrl.cs
index 698707cb67..f909aae944 100644
--- a/src/Abstractions/NexusMods.Abstractions.NexusWebApi/Types/NXMUrl.cs
+++ b/src/Abstractions/NexusMods.Abstractions.NexusWebApi/Types/NXMUrl.cs
@@ -16,12 +16,12 @@ public class NXMUrl
///
/// if applicable, the time the url becomes invalid
///
- public DateTime? ExpireTime
+ public DateTimeOffset? ExpireTime
{
get
{
var expires = Query.Get("expires");
- return expires != null ? DateTime.UnixEpoch.AddSeconds(ulong.Parse(expires)) : null;
+ return expires != null ? DateTimeOffset.UnixEpoch.AddSeconds(ulong.Parse(expires)) : null;
}
}
diff --git a/src/Abstractions/NexusMods.Abstractions.Resources.DB/PersistedDbResource.cs b/src/Abstractions/NexusMods.Abstractions.Resources.DB/PersistedDbResource.cs
index b37b116349..6b1b31e66f 100644
--- a/src/Abstractions/NexusMods.Abstractions.Resources.DB/PersistedDbResource.cs
+++ b/src/Abstractions/NexusMods.Abstractions.Resources.DB/PersistedDbResource.cs
@@ -1,5 +1,6 @@
using JetBrains.Annotations;
using NexusMods.Abstractions.MnemonicDB.Attributes;
+using NexusMods.MnemonicDB.Abstractions.Attributes;
using NexusMods.MnemonicDB.Abstractions.Models;
namespace NexusMods.Abstractions.Resources.DB;
@@ -20,7 +21,7 @@ public partial class PersistedDbResource : IModelDefinition
///
/// The expiration date.
///
- public static readonly DateTimeAttribute ExpiresAt = new(Namespace, nameof(ExpiresAt));
+ public static readonly TimestampAttribute ExpiresAt = new(Namespace, nameof(ExpiresAt));
///
/// The resource identifier as a hash.
@@ -32,6 +33,6 @@ public partial struct ReadOnly
///
/// Whether the resource is expired.
///
- public bool IsExpired => DateTime.UtcNow > ExpiresAt;
+ public bool IsExpired => TimeProvider.System.GetLocalNow() > ExpiresAt;
}
}
diff --git a/src/Abstractions/NexusMods.Abstractions.Resources.IO/HttpLoader.cs b/src/Abstractions/NexusMods.Abstractions.Resources.IO/HttpLoader.cs
index 9d4473b8c2..af9f63f40f 100644
--- a/src/Abstractions/NexusMods.Abstractions.Resources.IO/HttpLoader.cs
+++ b/src/Abstractions/NexusMods.Abstractions.Resources.IO/HttpLoader.cs
@@ -35,19 +35,18 @@ public async ValueTask> LoadResourceAsync(Uri resourceIdentifie
};
}
- private static DateTime GetExpiresAt(HttpResponseMessage responseMessage)
+ private static DateTimeOffset GetExpiresAt(HttpResponseMessage responseMessage)
{
var cacheControl = responseMessage.Headers.CacheControl;
- if (cacheControl is null) return DateTime.MaxValue;
- var maxAge = cacheControl.MaxAge;
- if (!maxAge.HasValue) return DateTime.MaxValue;
+ var maxAge = cacheControl?.MaxAge;
+ if (!maxAge.HasValue) return DateTimeOffset.MaxValue;
var age = responseMessage.Headers.Age;
var diff = maxAge.Value;
if (age.HasValue) diff -= age.Value;
- return DateTime.UtcNow + diff;
+ return TimeProvider.System.GetUtcNow() + diff;
}
}
diff --git a/src/Abstractions/NexusMods.Abstractions.Resources/Resource.cs b/src/Abstractions/NexusMods.Abstractions.Resources/Resource.cs
index 239cd4a0e0..e4748b9d9d 100644
--- a/src/Abstractions/NexusMods.Abstractions.Resources/Resource.cs
+++ b/src/Abstractions/NexusMods.Abstractions.Resources/Resource.cs
@@ -16,7 +16,7 @@ public record Resource where TData : notnull
///
/// Gets the expiration date.
///
- public DateTime ExpiresAt { get; init; } = DateTime.MaxValue;
+ public DateTimeOffset ExpiresAt { get; init; } = DateTimeOffset.MaxValue;
///
/// Creates a new resource.
diff --git a/src/Networking/NexusMods.Networking.ModUpdates/IModFeedItem.cs b/src/Networking/NexusMods.Networking.ModUpdates/IModFeedItem.cs
index d9d316698b..2407e04231 100644
--- a/src/Networking/NexusMods.Networking.ModUpdates/IModFeedItem.cs
+++ b/src/Networking/NexusMods.Networking.ModUpdates/IModFeedItem.cs
@@ -16,7 +16,6 @@ public interface IModFeedItem
///
/// Retrieves the time the item was last updated.
- /// This date is in UTC.
///
- public DateTime GetLastUpdatedDateUtc();
+ public DateTimeOffset GetLastUpdatedDateUtc();
}
diff --git a/src/Networking/NexusMods.Networking.ModUpdates/Mixins/ModFeedItemUpdateMixin.cs b/src/Networking/NexusMods.Networking.ModUpdates/Mixins/ModFeedItemUpdateMixin.cs
index 8ba3410d08..d58b45104e 100644
--- a/src/Networking/NexusMods.Networking.ModUpdates/Mixins/ModFeedItemUpdateMixin.cs
+++ b/src/Networking/NexusMods.Networking.ModUpdates/Mixins/ModFeedItemUpdateMixin.cs
@@ -8,7 +8,7 @@ namespace NexusMods.Networking.ModUpdates.Mixins;
///
public readonly struct ModFeedItemUpdateMixin : IModFeedItem
{
- private readonly DateTime _lastUpdatedDate;
+ private readonly DateTimeOffset _lastUpdatedDate;
private readonly GameId _gameId;
private readonly ModId _modId;
@@ -29,7 +29,7 @@ private ModFeedItemUpdateMixin(ModUpdate update, GameId gameId)
public static IEnumerable FromUpdateResults(IEnumerable updates, GameId gameId) => updates.Select(update => new ModFeedItemUpdateMixin(update, gameId));
///
- public DateTime GetLastUpdatedDateUtc() => _lastUpdatedDate;
+ public DateTimeOffset GetLastUpdatedDateUtc() => _lastUpdatedDate;
///
public UidForMod GetModPageId() => new()
diff --git a/src/Networking/NexusMods.Networking.ModUpdates/Mixins/PageMetadataMixin.cs b/src/Networking/NexusMods.Networking.ModUpdates/Mixins/PageMetadataMixin.cs
index b091f528ef..4565c1ac39 100644
--- a/src/Networking/NexusMods.Networking.ModUpdates/Mixins/PageMetadataMixin.cs
+++ b/src/Networking/NexusMods.Networking.ModUpdates/Mixins/PageMetadataMixin.cs
@@ -23,7 +23,7 @@ public struct PageMetadataMixin : IModFeedItem
public EntityId GetModPageEntityId() => _metadata.Id;
///
- public DateTime GetLastUpdatedDateUtc() => _metadata.UpdatedAt; // <= TODO: Change this with 'last file updated at' when V2 supports this field.
+ public DateTimeOffset GetLastUpdatedDateUtc() => _metadata.UpdatedAt; // <= TODO: Change this with 'last file updated at' when V2 supports this field.
///
/// Returns the database entries containing page metadata(s) as a mixin.
diff --git a/src/Networking/NexusMods.Networking.ModUpdates/PerFeedCacheUpdater.cs b/src/Networking/NexusMods.Networking.ModUpdates/PerFeedCacheUpdater.cs
index 56b99dda2b..8086b465d0 100644
--- a/src/Networking/NexusMods.Networking.ModUpdates/PerFeedCacheUpdater.cs
+++ b/src/Networking/NexusMods.Networking.ModUpdates/PerFeedCacheUpdater.cs
@@ -62,8 +62,7 @@ public PerFeedCacheUpdater(TUpdateableItem[] items, TimeSpan expiry)
_itemToIndex[_items[x].GetModPageId().ModId] = x;
// Set the action to refresh cache for any mods which exceed max age.
- var utcNow = DateTime.UtcNow;
- var minCachedDate = utcNow - expiry;
+ var minCachedDate = TimeProvider.System.GetLocalNow()- expiry;
for (var x = 0; x < _items.Length; x++)
{
if (_items[x].GetLastUpdatedDateUtc() < minCachedDate)
diff --git a/src/Networking/NexusMods.Networking.NexusWebApi/Auth/JWTToken.cs b/src/Networking/NexusMods.Networking.NexusWebApi/Auth/JWTToken.cs
index 3fc12123fb..663e03184a 100644
--- a/src/Networking/NexusMods.Networking.NexusWebApi/Auth/JWTToken.cs
+++ b/src/Networking/NexusMods.Networking.NexusWebApi/Auth/JWTToken.cs
@@ -63,7 +63,7 @@ public static Optional Create(IDb db, ITransaction tx, JwtTokenReply r
tx.Add(entityId, AccessToken, reply.AccessToken);
tx.Add(entityId, RefreshToken, reply.RefreshToken);
- tx.Add(entityId, ExpiresAt, DateTimeOffset.FromUnixTimeSeconds(reply.CreatedAt).DateTime + TimeSpan.FromSeconds(reply.ExpiresIn));
+ tx.Add(entityId, ExpiresAt, DateTimeOffset.FromUnixTimeSeconds(reply.CreatedAt) + TimeSpan.FromSeconds(reply.ExpiresIn));
return entityId;
}
diff --git a/src/Networking/NexusMods.Networking.NexusWebApi/Extensions/FragmentExtensions.cs b/src/Networking/NexusMods.Networking.NexusWebApi/Extensions/FragmentExtensions.cs
index 367cfe5413..6d4f498655 100644
--- a/src/Networking/NexusMods.Networking.NexusWebApi/Extensions/FragmentExtensions.cs
+++ b/src/Networking/NexusMods.Networking.NexusWebApi/Extensions/FragmentExtensions.cs
@@ -39,7 +39,7 @@ public static EntityId Resolve(this IModFileFragment modFileFragment, IDb db, IT
nexusFileResolver.Add(NexusModsFileMetadata.ModPageId, modPageEid);
nexusFileResolver.Add(NexusModsFileMetadata.Name, modFileFragment.Name);
nexusFileResolver.Add(NexusModsFileMetadata.Version, modFileFragment.Version);
- nexusFileResolver.Add(NexusModsFileMetadata.UploadedAt, DateTimeOffset.FromUnixTimeSeconds(modFileFragment.Date).DateTime);
+ nexusFileResolver.Add(NexusModsFileMetadata.UploadedAt, DateTimeOffset.FromUnixTimeSeconds(modFileFragment.Date));
if (ulong.TryParse(modFileFragment.SizeInBytes, out var size))
{
@@ -62,7 +62,7 @@ public static EntityId Resolve(this IModFragment modFragment, IDb db, ITransacti
var nexusModResolver = GraphQLResolver.Create(db, tx, NexusModsModPageMetadata.Uid, UidForMod.FromV2Api(modFragment.Uid));
nexusModResolver.Add(NexusModsModPageMetadata.Name, modFragment.Name);
nexusModResolver.Add(NexusModsModPageMetadata.GameDomain, GameDomain.From(modFragment.Game.DomainName));
- nexusModResolver.Add(NexusModsModPageMetadata.UpdatedAt, modFragment.UpdatedAt.UtcDateTime);
+ nexusModResolver.Add(NexusModsModPageMetadata.UpdatedAt, modFragment.UpdatedAt);
if (Uri.TryCreate(modFragment.PictureUrl, UriKind.Absolute, out var fullSizedPictureUri))
nexusModResolver.Add(NexusModsModPageMetadata.FullSizedPictureUri, fullSizedPictureUri);
diff --git a/src/Networking/NexusMods.Networking.NexusWebApi/NexusApiClient.cs b/src/Networking/NexusMods.Networking.NexusWebApi/NexusApiClient.cs
index f36fb01a55..89514295c0 100644
--- a/src/Networking/NexusMods.Networking.NexusWebApi/NexusApiClient.cs
+++ b/src/Networking/NexusMods.Networking.NexusWebApi/NexusApiClient.cs
@@ -105,10 +105,10 @@ public async Task> DownloadLinksAsync(string domain, Mo
///
/// Currently available for Premium users only; with some minor exceptions [nxm links].
///
- public async Task> DownloadLinksAsync(string domain, ModId modId, FileId fileId, NXMKey key, DateTime expireTime, CancellationToken token = default)
+ public async Task> DownloadLinksAsync(string domain, ModId modId, FileId fileId, NXMKey key, DateTimeOffset expireTime, CancellationToken token = default)
{
var msg = await _factory.Create(HttpMethod.Get, new Uri(
- $"https://api.nexusmods.com/v1/games/{domain}/mods/{modId}/files/{fileId}/download_link.json?key={key}&expires={new DateTimeOffset(expireTime).ToUnixTimeSeconds()}"));
+ $"https://api.nexusmods.com/v1/games/{domain}/mods/{modId}/files/{fileId}/download_link.json?key={key}&expires={expireTime.ToUnixTimeSeconds()}"));
return await SendAsyncArray(msg, token);
}
diff --git a/src/Networking/NexusMods.Networking.NexusWebApi/NexusModsLibrary.cs b/src/Networking/NexusMods.Networking.NexusWebApi/NexusModsLibrary.cs
index 1f9627c88c..c83bcc2ed1 100644
--- a/src/Networking/NexusMods.Networking.NexusWebApi/NexusModsLibrary.cs
+++ b/src/Networking/NexusMods.Networking.NexusWebApi/NexusModsLibrary.cs
@@ -105,7 +105,7 @@ public NexusModsLibrary(IServiceProvider serviceProvider)
Version = fileNode.Version,
ModPageId = modPage,
Uid = uid,
- UploadedAt = DateTimeOffset.FromUnixTimeSeconds(fileNode.Date).UtcDateTime,
+ UploadedAt = DateTimeOffset.FromUnixTimeSeconds(fileNode.Date),
Size = size,
};
@@ -115,7 +115,7 @@ public NexusModsLibrary(IServiceProvider serviceProvider)
public async Task GetDownloadUri(
NexusModsFileMetadata.ReadOnly file,
- Optional<(NXMKey, DateTime)> nxmData,
+ Optional<(NXMKey, DateTimeOffset)> nxmData,
CancellationToken cancellationToken = default)
{
Abstractions.NexusWebApi.DTOs.Response links;
@@ -159,7 +159,7 @@ public async Task> CreateDownloadJo
IGameDomainToGameIdMappingCache mappingCache,
CancellationToken cancellationToken)
{
- var nxmData = url.Key is not null && url.ExpireTime is not null ? (url.Key.Value, url.ExpireTime.Value) : Optional.None<(NXMKey, DateTime)>();
+ var nxmData = url.Key is not null && url.ExpireTime is not null ? (url.Key.Value, url.ExpireTime.Value) : Optional.None<(NXMKey, DateTimeOffset)>();
var gameId = (await mappingCache.TryGetIdAsync(GameDomain.From(url.Game), cancellationToken)).Value;
return await CreateDownloadJob(destination, gameId, url.ModId, url.FileId, nxmData, cancellationToken);
}
@@ -172,7 +172,7 @@ public async Task> CreateDownloadJo
GameId gameId,
ModId modId,
FileId fileId,
- Optional<(NXMKey, DateTime)> nxmData = default,
+ Optional<(NXMKey, DateTimeOffset)> nxmData = default,
CancellationToken cancellationToken = default)
{
var modPage = await GetOrAddModPage(modId, gameId, cancellationToken);
diff --git a/src/NexusMods.App.UI/Controls/LoadoutCard/Standard/LoadoutCardViewModel.cs b/src/NexusMods.App.UI/Controls/LoadoutCard/Standard/LoadoutCardViewModel.cs
index 556fe32216..21b561a4fb 100644
--- a/src/NexusMods.App.UI/Controls/LoadoutCard/Standard/LoadoutCardViewModel.cs
+++ b/src/NexusMods.App.UI/Controls/LoadoutCard/Standard/LoadoutCardViewModel.cs
@@ -126,7 +126,7 @@ private static string FormatCreatedTime(DateTimeOffset creationTime)
private static string FormatLastAppliedTime(DateTimeOffset lastAppliedTime)
{
- var stringTime = lastAppliedTime == DateTime.MinValue ? Language.HumanizedDateTime_Never : lastAppliedTime.Humanize();
+ var stringTime = lastAppliedTime == DateTimeOffset.MinValue ? Language.HumanizedDateTime_Never : lastAppliedTime.Humanize();
return string.Format(Language.LoadoutCardViewModel_FormatLastAppliedTime_Last_applied__0_, stringTime);
}
private async Task LoadImage(GameInstallation source)
diff --git a/src/NexusMods.App.UI/Helpers/DoubleClickHelper.cs b/src/NexusMods.App.UI/Helpers/DoubleClickHelper.cs
index ea2996843a..6073349d86 100644
--- a/src/NexusMods.App.UI/Helpers/DoubleClickHelper.cs
+++ b/src/NexusMods.App.UI/Helpers/DoubleClickHelper.cs
@@ -5,6 +5,7 @@
using Avalonia.Input;
using Avalonia.Interactivity;
using JetBrains.Annotations;
+using LiveChartsCore.Defaults;
namespace NexusMods.App.UI.Helpers;
@@ -61,7 +62,7 @@ private class DoubleClickSubject : ADoubleClickSubject
{
- public ReactiveProperty InstalledAt { get; } = new(DateTime.UnixEpoch);
+ public ReactiveProperty InstalledAt { get; } = new(DateTimeOffset.UnixEpoch);
public IObservable NameObservable { get; init; } = System.Reactive.Linq.Observable.Return("-");
public BindableReactiveProperty Name { get; } = new("-");
@@ -31,7 +31,7 @@ public class LoadoutItemModel : TreeDataGridItemModel GetLoadoutItemIds() => _fixedId;
- public Observable? Ticker { get; set; }
+ public Observable? Ticker { get; set; }
public BindableReactiveProperty FormattedInstalledAt { get; } = new("-");
private readonly IDisposable _modelActivationDisposable;
@@ -52,7 +52,7 @@ public LoadoutItemModel(LoadoutItemId loadoutItemId)
Debug.Assert(model.Ticker is not null, "should've been set before activation");
model.Ticker.Subscribe(model, static (now, model) =>
{
- model.FormattedInstalledAt.Value = FormatDate(now, model.InstalledAt.Value);
+ model.FormattedInstalledAt.Value = model.InstalledAt.Value.FormatDate(now);
}).AddTo(disposables);
model.NameObservable.OnUI().Subscribe(name => model.Name.Value = name).AddTo(disposables);
@@ -60,16 +60,10 @@ public LoadoutItemModel(LoadoutItemId loadoutItemId)
model.SizeObservable.OnUI().Subscribe(size => model.ItemSize.Value = size).AddTo(disposables);
model.IsEnabledObservable.OnUI().Subscribe(isEnabled => model.IsEnabled.Value = isEnabled).AddTo(disposables);
- model.InstalledAt.Subscribe(model, static (date, model) => model.FormattedInstalledAt.Value = FormatDate(DateTime.Now, date)).AddTo(disposables);
+ model.InstalledAt.Subscribe(model, static (date, model) => model.FormattedInstalledAt.Value = date.FormatDate(TimeProvider.System.GetLocalNow())).AddTo(disposables);
});
}
- private static string FormatDate(DateTimeOffset now, DateTimeOffset date)
- {
- if (date == DateTime.UnixEpoch || date == default(DateTime)) return "-";
- return date.Humanize(dateToCompareAgainst: now > date ? now : DateTime.Now);
- }
-
private bool _isDisposed;
protected override void Dispose(bool disposing)
{
@@ -162,8 +156,8 @@ public static IColumn CreateInstalledAtColumn()
getter: model => model.FormattedInstalledAt.Value,
options: new TextColumnOptions
{
- CompareAscending = static (a, b) => a?.InstalledAt.Value.CompareTo(b?.InstalledAt.Value ?? DateTime.UnixEpoch) ?? 1,
- CompareDescending = static (a, b) => b?.InstalledAt.Value.CompareTo(a?.InstalledAt.Value ?? DateTime.UnixEpoch) ?? 1,
+ CompareAscending = static (a, b) => a?.InstalledAt.Value.CompareTo(b?.InstalledAt.Value ?? DateTimeOffset.MinValue) ?? 1,
+ CompareDescending = static (a, b) => b?.InstalledAt.Value.CompareTo(a?.InstalledAt.Value ?? DateTimeOffset.MinValue) ?? 1,
IsTextSearchEnabled = false,
CanUserResizeColumn = true,
CanUserSortColumn = true,
diff --git a/src/NexusMods.App.UI/Pages/LoadoutPage/LoadoutViewModel.cs b/src/NexusMods.App.UI/Pages/LoadoutPage/LoadoutViewModel.cs
index e5dd9112bd..57f15046fb 100644
--- a/src/NexusMods.App.UI/Pages/LoadoutPage/LoadoutViewModel.cs
+++ b/src/NexusMods.App.UI/Pages/LoadoutPage/LoadoutViewModel.cs
@@ -41,8 +41,8 @@ public LoadoutViewModel(IWindowManager windowManager, IServiceProvider servicePr
var ticker = Observable
.Interval(period: TimeSpan.FromSeconds(30), timeProvider: ObservableSystem.DefaultTimeProvider)
.ObserveOnUIThreadDispatcher()
- .Select(_ => DateTime.Now)
- .Publish(initialValue: DateTime.Now);
+ .Select(_ => TimeProvider.System.GetLocalNow())
+ .Publish(initialValue: TimeProvider.System.GetLocalNow());
var loadoutFilter = new LoadoutFilter
{
@@ -188,7 +188,7 @@ public class LoadoutTreeDataGridAdapter : TreeDataGridAdapter
{
private readonly ILoadoutDataProvider[] _loadoutDataProviders;
- private readonly ConnectableObservable _ticker;
+ private readonly ConnectableObservable _ticker;
private readonly IConnection _connection;
private readonly LoadoutFilter _loadoutFilter;
@@ -196,7 +196,7 @@ public class LoadoutTreeDataGridAdapter : TreeDataGridAdapter _commandDisposables = new();
private readonly IDisposable _activationDisposable;
- public LoadoutTreeDataGridAdapter(IServiceProvider serviceProvider, ConnectableObservable ticker, LoadoutFilter loadoutFilter)
+ public LoadoutTreeDataGridAdapter(IServiceProvider serviceProvider, ConnectableObservable ticker, LoadoutFilter loadoutFilter)
{
_loadoutFilter = loadoutFilter;
_ticker = ticker;
diff --git a/tests/Networking/NexusMods.Networking.ModUpdates.Tests/Helpers/TestItem.cs b/tests/Networking/NexusMods.Networking.ModUpdates.Tests/Helpers/TestItem.cs
index 3317d01ef8..33d726d20a 100644
--- a/tests/Networking/NexusMods.Networking.ModUpdates.Tests/Helpers/TestItem.cs
+++ b/tests/Networking/NexusMods.Networking.ModUpdates.Tests/Helpers/TestItem.cs
@@ -5,14 +5,14 @@ namespace NexusMods.Networking.ModUpdates.Tests.Helpers;
// Helper class to simulate updateable items
public class TestItem : IModFeedItem
{
- public DateTime LastUpdated { get; set; }
+ public DateTimeOffset LastUpdated { get; set; }
public UidForMod Uid { get; set; }
- public DateTime GetLastUpdatedDateUtc() => LastUpdated;
+ public DateTimeOffset GetLastUpdatedDateUtc() => LastUpdated;
public UidForMod GetModPageId() => Uid;
// Helper method to create a test item
- public static TestItem Create(uint gameId, uint modId, DateTime lastUpdated)
+ public static TestItem Create(uint gameId, uint modId, DateTimeOffset lastUpdated)
{
return new TestItem
{
diff --git a/tests/Networking/NexusMods.Networking.ModUpdates.Tests/MultiFeedCacheUpdaterTests.cs b/tests/Networking/NexusMods.Networking.ModUpdates.Tests/MultiFeedCacheUpdaterTests.cs
index 63a8b7749b..9f20c65f05 100644
--- a/tests/Networking/NexusMods.Networking.ModUpdates.Tests/MultiFeedCacheUpdaterTests.cs
+++ b/tests/Networking/NexusMods.Networking.ModUpdates.Tests/MultiFeedCacheUpdaterTests.cs
@@ -23,7 +23,7 @@ public void Constructor_ShouldSetOldItemsToNeedUpdate()
// should be marked as 'Out of Date' across multiple feeds.
// Arrange
- var now = DateTime.UtcNow;
+ var now = TimeProvider.System.GetLocalNow();
var items = new[]
{
Create(1, 1, now.AddDays(-40)),
@@ -52,7 +52,7 @@ public void Update_ShouldMarkMissingItemsAsUndetermined()
// under this category across multiple feeds.
// Arrange
- var now = DateTime.UtcNow;
+ var now = TimeProvider.System.GetLocalNow();
var items = new[]
{
Create(1, 1, now.AddDays(-10)),
@@ -90,7 +90,7 @@ public void Update_ShouldMarkItemsAsUpToDateOrNeedingUpdateAcrossMultipleFeeds()
// across multiple feeds.
// Arrange
- var now = DateTime.UtcNow;
+ var now = TimeProvider.System.GetLocalNow();
var items = new[]
{
Create(1, 1, now.AddDays(-10)),
@@ -131,7 +131,7 @@ public void Update_HavingExtraItemsInUpdatePayloadHasNoSideEffects()
// therefore they should be ignored without side effects.
// Arrange
- var now = DateTime.UtcNow;
+ var now = TimeProvider.System.GetLocalNow();
var items = new[]
{
Create(1, 1, now.AddDays(-10)),
@@ -176,7 +176,7 @@ public void Update_ShouldHandleUpdatesFromDifferentFeeds()
// from different feeds separately.
// Arrange
- var now = DateTime.UtcNow;
+ var now = TimeProvider.System.GetLocalNow();
var items = new[]
{
Create(1, 1, now.AddDays(-10)),