diff --git a/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj b/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj
new file mode 100644
index 0000000..baec30d
--- /dev/null
+++ b/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj
@@ -0,0 +1,9 @@
+
+
+
+ 1.0.0
+ True
+ MIT
+
+
+
diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping/LocalDateTime.cs b/DotNetThoughts.LocalTimeKit/LocalDateTime.cs
similarity index 90%
rename from TimeKeeping/DotNetThoughts.TimeKeeping/LocalDateTime.cs
rename to DotNetThoughts.LocalTimeKit/LocalDateTime.cs
index 8b60804..9140778 100644
--- a/TimeKeeping/DotNetThoughts.TimeKeeping/LocalDateTime.cs
+++ b/DotNetThoughts.LocalTimeKit/LocalDateTime.cs
@@ -1,4 +1,4 @@
-namespace DotNetThoughts.TimeKeeping;
+namespace DotNetThoughts.LocalTimeKit;
///
/// Unambigious representation of a local datetime
@@ -13,9 +13,9 @@ public LocalDateTime(DateTimeOffset dateTimeOffset, TimeZoneInfo timeZoneInfo)
public LocalDateTime(DateTime dateTime, TimeZoneInfo timeZoneInfo)
{
- if (!((dateTime.Kind == DateTimeKind.Local && timeZoneInfo == TimeZoneInfo.Local)
- || (dateTime.Kind == DateTimeKind.Utc && timeZoneInfo == TimeZoneInfo.Utc)
- || (dateTime.Kind == DateTimeKind.Unspecified && timeZoneInfo != TimeZoneInfo.Local)))
+ if (!(dateTime.Kind == DateTimeKind.Local && timeZoneInfo == TimeZoneInfo.Local
+ || dateTime.Kind == DateTimeKind.Utc && timeZoneInfo == TimeZoneInfo.Utc
+ || dateTime.Kind == DateTimeKind.Unspecified && timeZoneInfo != TimeZoneInfo.Local))
{
throw new ArgumentException("Invalid combinations of datetime kinds and timezoneinfo");
}
diff --git a/DotNetThoughts.TimeTraveler/DotNetThoughts.TimeTraveler.csproj b/DotNetThoughts.TimeTraveler/DotNetThoughts.TimeTraveler.csproj
new file mode 100644
index 0000000..8f93d8a
--- /dev/null
+++ b/DotNetThoughts.TimeTraveler/DotNetThoughts.TimeTraveler.csproj
@@ -0,0 +1,7 @@
+
+
+ 1.0.0
+ True
+ MIT
+
+
diff --git a/DotNetThoughts.TimeTraveler/TimeTravelersClock.cs b/DotNetThoughts.TimeTraveler/TimeTravelersClock.cs
new file mode 100644
index 0000000..ad919e6
--- /dev/null
+++ b/DotNetThoughts.TimeTraveler/TimeTravelersClock.cs
@@ -0,0 +1,139 @@
+namespace DotNetThoughts.TimeTraveler;
+
+
+public class TimeTravelersClock
+{
+ public DateTimeOffset? FreezedAt { get; set; }
+ public DateTimeOffset? Frozen { get; set; }
+ public TimeSpan Offset { get; set; } = TimeSpan.Zero;
+
+ public bool IsFrozen()
+ {
+ using var context = new OperationContext();
+ return FreezedAt != null;
+ }
+
+ public DateTimeOffset Reset()
+ {
+ using var context = new OperationContext();
+ Frozen = null;
+ FreezedAt = null;
+ Offset = TimeSpan.Zero;
+ return UtcNow();
+ }
+
+ public DateTimeOffset Thaw()
+ {
+ using var context = new OperationContext();
+ if (FreezedAt == null)
+ {
+ throw new InvalidOperationException("Cannot thaw when not frozen");
+ }
+ Offset = Offset.Add(-RealTimeFrozen());
+ FreezedAt = null;
+ Frozen = null;
+ return UtcNow();
+ }
+
+ public DateTimeOffset Freeze()
+ {
+ using var context = new OperationContext();
+ FreezedAt = context.UtcNow();
+ Frozen = UtcNow();
+ return Frozen.Value;
+ }
+
+ public DateTimeOffset Freeze(DateTimeOffset baseLine)
+ {
+ using var context = new OperationContext();
+ Reset();
+ SetBaseline(baseLine);
+ FreezedAt = context.UtcNow();
+ Frozen = UtcNow();
+ return Frozen.Value;
+ }
+
+ public DateTimeOffset UtcNow()
+ {
+ using var context = new OperationContext();
+ return Frozen ?? context.UtcNow().Add(Offset);
+ }
+
+ public DateTimeOffset AdvanceDays(double days)
+ {
+ using var context = new OperationContext();
+ return Advance(TimeSpan.FromDays(days));
+ }
+
+ public DateTimeOffset Advance(TimeSpan timeSpan)
+ {
+ using var context = new OperationContext();
+ Offset = Offset.Add(timeSpan);
+ if (IsFrozen())
+ {
+ Frozen = Frozen!.Value.Add(timeSpan);
+ }
+ return UtcNow();
+ }
+
+ public DateTimeOffset SetBaseline(DateTimeOffset baseLine)
+ {
+ if (IsFrozen()) throw new InvalidOperationException();
+ using var context = new OperationContext();
+ Offset = baseLine - context.UtcNow();
+ return UtcNow();
+ }
+
+ public DateTimeOffset SetOffset(TimeSpan offset)
+ {
+ if (IsFrozen()) throw new InvalidOperationException();
+ using var context = new OperationContext();
+ Offset = offset;
+
+ return UtcNow();
+ }
+
+ private TimeSpan RealTimeFrozen()
+ {
+ using var context = new OperationContext();
+ return FreezedAt != null ? context.UtcNow() - FreezedAt.Value : TimeSpan.Zero;
+ }
+
+ internal void From(TimeTravelersClock xSystemTime)
+ {
+ using var context = new OperationContext();
+ Offset = xSystemTime.Offset;
+ FreezedAt = xSystemTime.FreezedAt;
+ Frozen = xSystemTime.Frozen;
+ }
+
+ internal bool IsManipulated()
+ {
+ return Offset != TimeSpan.Zero || FreezedAt != null;
+ }
+
+ internal class OperationContext : IDisposable
+ {
+ private static AsyncLocal _operationNow = new AsyncLocal();
+
+ private bool _shouldReset = false;
+
+ public OperationContext()
+ {
+ if (_operationNow.Value == null)
+ {
+ _operationNow.Value = DateTimeOffset.UtcNow;
+ _shouldReset = true;
+ }
+ }
+ public void Dispose()
+ {
+ if (_shouldReset)
+ {
+ _operationNow.Value = null;
+ }
+ }
+
+ public DateTimeOffset UtcNow() => _operationNow.Value!.Value;
+ }
+}
\ No newline at end of file
diff --git a/DotNetThoughts.sln b/DotNetThoughts.sln
index 9d39be7..5277c19 100644
--- a/DotNetThoughts.sln
+++ b/DotNetThoughts.sln
@@ -50,6 +50,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ReadMe.md = ReadMe.md
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetThoughts.LocalTimeKit", "DotNetThoughts.LocalTimeKit\DotNetThoughts.LocalTimeKit.csproj", "{03DA1023-FDF4-4EF5-8951-19806434F8CA}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -100,6 +102,10 @@ Global
{E0E86A0B-F4CE-4E88-99CF-C54AFC1C0C58}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E0E86A0B-F4CE-4E88-99CF-C54AFC1C0C58}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E0E86A0B-F4CE-4E88-99CF-C54AFC1C0C58}.Release|Any CPU.Build.0 = Release|Any CPU
+ {03DA1023-FDF4-4EF5-8951-19806434F8CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {03DA1023-FDF4-4EF5-8951-19806434F8CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {03DA1023-FDF4-4EF5-8951-19806434F8CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {03DA1023-FDF4-4EF5-8951-19806434F8CA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -116,6 +122,7 @@ Global
{66DCA5D2-4A06-4F1A-8DBB-36118CC01A34} = {BECD3795-EDD5-459E-BFCC-017082E1C34D}
{3D9F5F06-E09D-46B4-8058-1458467E0252} = {BECD3795-EDD5-459E-BFCC-017082E1C34D}
{E0E86A0B-F4CE-4E88-99CF-C54AFC1C0C58} = {BECD3795-EDD5-459E-BFCC-017082E1C34D}
+ {03DA1023-FDF4-4EF5-8951-19806434F8CA} = {695E363E-055E-4ED5-9613-587E02A67578}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CAA5B6BA-B6C8-4A90-B9E6-D7634295532E}
diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping.App/DotNetThoughts.TimeKeeping.App.csproj b/TimeKeeping/DotNetThoughts.TimeKeeping.App/DotNetThoughts.TimeKeeping.App.csproj
index 5d964d3..b6d4aab 100644
--- a/TimeKeeping/DotNetThoughts.TimeKeeping.App/DotNetThoughts.TimeKeeping.App.csproj
+++ b/TimeKeeping/DotNetThoughts.TimeKeeping.App/DotNetThoughts.TimeKeeping.App.csproj
@@ -5,6 +5,7 @@
+
diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping.App/Pages/Timer.razor b/TimeKeeping/DotNetThoughts.TimeKeeping.App/Pages/Timer.razor
index 21f3c4c..29b481b 100644
--- a/TimeKeeping/DotNetThoughts.TimeKeeping.App/Pages/Timer.razor
+++ b/TimeKeeping/DotNetThoughts.TimeKeeping.App/Pages/Timer.razor
@@ -1,4 +1,5 @@
@page "/timer"
+@using DotNetThoughts.LocalTimeKit
Timing
diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping.App/packages.lock.json b/TimeKeeping/DotNetThoughts.TimeKeeping.App/packages.lock.json
index cbd6ef3..03e7df9 100644
--- a/TimeKeeping/DotNetThoughts.TimeKeeping.App/packages.lock.json
+++ b/TimeKeeping/DotNetThoughts.TimeKeeping.App/packages.lock.json
@@ -231,6 +231,9 @@
"System.Text.Encodings.Web": "8.0.0"
}
},
+ "dotnetthoughts.localtimekit": {
+ "type": "Project"
+ },
"dotnetthoughts.timekeeping": {
"type": "Project"
}
diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/DotNetThoughts.TimeKeeping.Tests.csproj b/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/DotNetThoughts.TimeKeeping.Tests.csproj
index 6165ba6..2a6bfc8 100644
--- a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/DotNetThoughts.TimeKeeping.Tests.csproj
+++ b/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/DotNetThoughts.TimeKeeping.Tests.csproj
@@ -1,6 +1,7 @@
+
diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/LocalDateTimeTests.cs b/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/LocalDateTimeTests.cs
index 3ee5d7c..1ad93bf 100644
--- a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/LocalDateTimeTests.cs
+++ b/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/LocalDateTimeTests.cs
@@ -1,4 +1,6 @@
-namespace DotNetThoughts.TimeKeeping.Tests;
+using DotNetThoughts.LocalTimeKit;
+
+namespace DotNetThoughts.TimeKeeping.Tests;
public class LocalDateTimeTests
{
diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/packages.lock.json b/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/packages.lock.json
index baa83d6..47d7ce7 100644
--- a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/packages.lock.json
+++ b/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/packages.lock.json
@@ -167,6 +167,9 @@
"xunit.extensibility.core": "[2.8.1]"
}
},
+ "dotnetthoughts.localtimekit": {
+ "type": "Project"
+ },
"dotnetthoughts.timekeeping": {
"type": "Project"
}
diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj b/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj
index 69a8208..18987c9 100644
--- a/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj
+++ b/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj
@@ -1,6 +1,6 @@
- 1.1.0
+ 1.2.0
True
MIT
diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping/SystemTime.cs b/TimeKeeping/DotNetThoughts.TimeKeeping/SystemTime.cs
index c5af7a0..b26e989 100644
--- a/TimeKeeping/DotNetThoughts.TimeKeeping/SystemTime.cs
+++ b/TimeKeeping/DotNetThoughts.TimeKeeping/SystemTime.cs
@@ -2,7 +2,6 @@
///
/// "Mockable" DateTime that you still can use statically using the ambient context anti-pattern. Let's see how it rolls.
-/// Replace with an IDateTime if problems arise.
///
public class SystemTime
{
diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping/TimeTravelersClock.cs b/TimeKeeping/DotNetThoughts.TimeKeeping/TimeTravelersClock.cs
index 986cead..952146c 100644
--- a/TimeKeeping/DotNetThoughts.TimeKeeping/TimeTravelersClock.cs
+++ b/TimeKeeping/DotNetThoughts.TimeKeeping/TimeTravelersClock.cs
@@ -1,6 +1,5 @@
namespace DotNetThoughts.TimeKeeping;
-
public class TimeTravelersClock
{
public DateTimeOffset? FreezedAt { get; set; }
@@ -96,7 +95,7 @@ public DateTimeOffset SetOffset(TimeSpan offset)
private TimeSpan RealTimeFrozen()
{
using var context = new OperationContext();
- return FreezedAt != null ? (context.UtcNow() - FreezedAt.Value) : TimeSpan.Zero;
+ return FreezedAt != null ? context.UtcNow() - FreezedAt.Value : TimeSpan.Zero;
}
internal void From(TimeTravelersClock xSystemTime)