From eb6e9bf3294f9e958a146819201ac2c4a9c0c584 Mon Sep 17 00:00:00 2001 From: Mattias Nordqvist Date: Sun, 22 Sep 2024 23:19:48 +0200 Subject: [PATCH 01/11] docs on timekeepers clocks and some api changes --- DotNetThoughts.sln | 15 +-- .../Pages/Timer.razor | 4 +- .../TimeTravelersClockTests.cs | 34 ++--- .../DotNetThoughts.TimeKeeping.csproj | 2 +- .../DotNetThoughts.TimeKeeping/SystemTime.cs | 73 +++++++++-- .../TimeTravelersClock.cs | 123 ++++++++++++++---- TimeKeeping/ReadMe.md | 33 +++++ 7 files changed, 224 insertions(+), 60 deletions(-) diff --git a/DotNetThoughts.sln b/DotNetThoughts.sln index 87dcb5e..eea63eb 100644 --- a/DotNetThoughts.sln +++ b/DotNetThoughts.sln @@ -11,10 +11,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetThoughts.TimeKeeping. EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TimeKeeping", "TimeKeeping", "{695E363E-055E-4ED5-9613-587E02A67578}" ProjectSection(SolutionItems) = preProject - ReadMe.md = ReadMe.md + TimeKeeping\ReadMe.md = TimeKeeping\ReadMe.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetThoughts.LocalTimeKit", "TimeKeeping\DotNetThoughts.LocalTimeKit\DotNetThoughts.LocalTimeKit.csproj", "{03DA1023-FDF4-4EF5-8951-19806434F8CA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetThoughts.LocalTimeKit", "TimeKeeping\DotNetThoughts.LocalTimeKit\DotNetThoughts.LocalTimeKit.csproj", "{03DA1023-FDF4-4EF5-8951-19806434F8CA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetThoughts.Results", "Results\DotNetThoughts.Results\DotNetThoughts.Results.csproj", "{7032EE8C-FC18-4610-B7D6-613E552C278D}" EndProject @@ -52,7 +52,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ReadMe.md = ReadMe.md EndProjectSection EndProject - Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -71,6 +70,10 @@ Global {DF62C221-71AD-49A7-A9D8-48E31F06E27B}.Debug|Any CPU.Build.0 = Debug|Any CPU {DF62C221-71AD-49A7-A9D8-48E31F06E27B}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF62C221-71AD-49A7-A9D8-48E31F06E27B}.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 {7032EE8C-FC18-4610-B7D6-613E552C278D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7032EE8C-FC18-4610-B7D6-613E552C278D}.Debug|Any CPU.Build.0 = Debug|Any CPU {7032EE8C-FC18-4610-B7D6-613E552C278D}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -103,10 +106,6 @@ 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 @@ -115,6 +114,7 @@ Global {6B9CCBB9-73D3-4062-8317-5D54CF6B85D4} = {695E363E-055E-4ED5-9613-587E02A67578} {917A0D7B-F7FA-4D39-B892-B9C1BA9BE284} = {695E363E-055E-4ED5-9613-587E02A67578} {DF62C221-71AD-49A7-A9D8-48E31F06E27B} = {695E363E-055E-4ED5-9613-587E02A67578} + {03DA1023-FDF4-4EF5-8951-19806434F8CA} = {695E363E-055E-4ED5-9613-587E02A67578} {7032EE8C-FC18-4610-B7D6-613E552C278D} = {BECD3795-EDD5-459E-BFCC-017082E1C34D} {8CA73623-9009-4FEF-885E-84F9BBD61536} = {BECD3795-EDD5-459E-BFCC-017082E1C34D} {02C498D0-532A-4F1D-9004-7E2E24121DF3} = {BECD3795-EDD5-459E-BFCC-017082E1C34D} @@ -123,7 +123,6 @@ 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/Pages/Timer.razor b/TimeKeeping/DotNetThoughts.TimeKeeping.App/Pages/Timer.razor index 29b481b..d38e1a9 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping.App/Pages/Timer.razor +++ b/TimeKeeping/DotNetThoughts.TimeKeeping.App/Pages/Timer.razor @@ -52,7 +52,7 @@ private void UpdateUI() { - systemTime = clock.UtcNow(); + systemTime = clock.Now(); realTime = DateTimeOffset.UtcNow; localSystemTime = new LocalDateTime(systemTime.DateTime, TimeZoneInfo.Utc).ToTimeZone(TimeZoneInfo.Local).ToDateTimeOffsetLocal(); nairobiLocalSystemTime = new LocalDateTime(systemTime.DateTime, TimeZoneInfo.Utc).ToTimeZone(TimeZoneInfo.FindSystemTimeZoneById(selectedTimeZone)).ToDateTimeOffsetLocal(); @@ -139,7 +139,7 @@ private void SetBaseLine() { - clock.SetBaseline(new DateTimeOffset(2021, 1, 1, 0, 0, 0, TimeSpan.Zero)); + clock.SetNow(new DateTimeOffset(2021, 1, 1, 0, 0, 0, TimeSpan.Zero)); UpdateUI(); } diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/TimeTravelersClockTests.cs b/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/TimeTravelersClockTests.cs index c1da058..df2eb0f 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/TimeTravelersClockTests.cs +++ b/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/TimeTravelersClockTests.cs @@ -11,7 +11,7 @@ public void FreezeTime() var sut = new TimeTravelersClock(); sut.Freeze(frozenTime); sut.IsFrozen().Should().BeTrue(); - sut.UtcNow().Should().Be(frozenTime); + sut.Now().Should().Be(frozenTime); } [Fact] public void FreezeAgainOverridesCurrentFreeze() @@ -22,7 +22,7 @@ public void FreezeAgainOverridesCurrentFreeze() sut.Freeze(frozenTime); sut.Freeze(newFrozenTime); sut.IsFrozen().Should().BeTrue(); - sut.UtcNow().Should().Be(newFrozenTime); + sut.Now().Should().Be(newFrozenTime); } [Fact] @@ -31,8 +31,8 @@ public void FreezeCurrentTime() var sut = new TimeTravelersClock(); sut.Freeze(); sut.IsFrozen().Should().BeTrue(); - sut.UtcNow().Should().BeCloseTo(DateTimeOffset.Now, _allowedDeviation); - sut.UtcNow().Should().NotBeAfter(DateTimeOffset.Now); + sut.Now().Should().BeCloseTo(DateTimeOffset.Now, _allowedDeviation); + sut.Now().Should().NotBeAfter(DateTimeOffset.Now); } [Fact] @@ -44,12 +44,12 @@ public void ResetFrozenTimeShouldRevertEverythingToNormal() sut.Freeze(frozenTime); // check arrange sut.IsFrozen().Should().BeTrue(); - sut.UtcNow().Should().Be(frozenTime); + sut.Now().Should().Be(frozenTime); // Act sut.Reset(); // Assert sut.IsFrozen().Should().BeFalse(); - sut.UtcNow().Should().BeCloseTo(DateTimeOffset.Now, _allowedDeviation); + sut.Now().Should().BeCloseTo(DateTimeOffset.Now, _allowedDeviation); } [Fact] @@ -58,10 +58,10 @@ public void BaselineMocksNow() var sut = new TimeTravelersClock(); var now = DateTimeOffset.Now; var baseLine = now.AddDays(-1); - sut.SetBaseline(baseLine); - sut.UtcNow().Should().BeCloseTo(baseLine, _allowedDeviation); + sut.SetNow(baseLine); + sut.Now().Should().BeCloseTo(baseLine, _allowedDeviation); var slept = Sleep(1000); - sut.UtcNow().Should().BeCloseTo(baseLine.Add(slept), _allowedDeviation); + sut.Now().Should().BeCloseTo(baseLine.Add(slept), _allowedDeviation); } [Fact] @@ -71,7 +71,7 @@ public void AdvanceFrozenTime() var frozenTime = new DateTimeOffset(2021, 1, 1, 0, 0, 0, TimeSpan.Zero); sut.Freeze(frozenTime); sut.Advance(TimeSpan.FromDays(1)); - sut.UtcNow().Should().Be(new DateTimeOffset(2021, 1, 2, 0, 0, 0, TimeSpan.Zero)); + sut.Now().Should().Be(new DateTimeOffset(2021, 1, 2, 0, 0, 0, TimeSpan.Zero)); } [Fact] @@ -79,7 +79,7 @@ public void AdvanceLiveTime() { var sut = new TimeTravelersClock(); sut.Advance(TimeSpan.FromDays(1)); - sut.UtcNow().Should().BeCloseTo(DateTimeOffset.Now.AddDays(1), _allowedDeviation); + sut.Now().Should().BeCloseTo(DateTimeOffset.Now.AddDays(1), _allowedDeviation); } [Fact] @@ -91,7 +91,7 @@ public void AdvanceFrozenTimeAndThenThaw() sut.Advance(TimeSpan.FromDays(1)); sut.Thaw(); var slept = Sleep(1000); - sut.UtcNow().Should().BeCloseTo(frozenTime.AddDays(1).Add(slept), _allowedDeviation); + sut.Now().Should().BeCloseTo(frozenTime.AddDays(1).Add(slept), _allowedDeviation); } [Fact] @@ -100,16 +100,16 @@ public void AComplicatedTest() var sut = new TimeTravelersClock(); var baseLine = new DateTimeOffset(2021, 1, 1, 0, 0, 0, TimeSpan.Zero); var timer = new Stopwatch(); timer.Start(); - sut.SetBaseline(baseLine); - sut.UtcNow().Should().BeCloseTo(baseLine.Add(timer.Elapsed), _allowedDeviation); + sut.SetNow(baseLine); + sut.Now().Should().BeCloseTo(baseLine.Add(timer.Elapsed), _allowedDeviation); Sleep(20); - sut.UtcNow().Should().BeCloseTo(baseLine.Add(timer.Elapsed), _allowedDeviation); + sut.Now().Should().BeCloseTo(baseLine.Add(timer.Elapsed), _allowedDeviation); var frozen = sut.Freeze(); - sut.UtcNow().Should().Be(frozen); + sut.Now().Should().Be(frozen); var sleptWhileFrozen = Sleep(2000); sut.Thaw(); Sleep(1000); - sut.UtcNow().Should().BeCloseTo(baseLine.Add(timer.Elapsed).Add(-sleptWhileFrozen), _allowedDeviation); + sut.Now().Should().BeCloseTo(baseLine.Add(timer.Elapsed).Add(-sleptWhileFrozen), _allowedDeviation); } private static TimeSpan Sleep(int milliseconds) diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj b/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj index 18987c9..b8b6657 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj +++ b/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj @@ -1,6 +1,6 @@  - 1.2.0 + 1.3.0 True MIT diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping/SystemTime.cs b/TimeKeeping/DotNetThoughts.TimeKeeping/SystemTime.cs index b26e989..7bc5a3a 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping/SystemTime.cs +++ b/TimeKeeping/DotNetThoughts.TimeKeeping/SystemTime.cs @@ -7,6 +7,9 @@ public class SystemTime { private static AsyncLocal _asyncLocalState = new(); + /// + /// Returns the underlying instance. + /// public static TimeTravelersClock Clock { get @@ -17,60 +20,114 @@ public static TimeTravelersClock Clock } } - public static DateTimeOffset SetBaseline(DateTimeOffset baseLine) + /// + /// See + /// + /// + /// + public static DateTimeOffset SetNow(DateTimeOffset baseLine) { - return Clock.SetBaseline(baseLine); + return Clock.SetNow(baseLine); } + /// + /// See + /// + /// + /// public static DateTimeOffset SetOffset(TimeSpan offset) { return Clock.SetOffset(offset); } + + /// + /// See + /// + /// + /// public static DateTimeOffset Advance(TimeSpan timeSpan) { return Clock.Advance(timeSpan); } + /// + /// See + /// + /// + /// public static DateTimeOffset AdvanceDays(double days) { return Clock.AdvanceDays(days); } - public static DateTimeOffset UtcNow() + /// + /// See + /// + /// + public static DateTimeOffset Now() { - return Clock.UtcNow(); + return Clock.Now(); } - public static DateTimeOffset Freeze(DateTimeOffset baseLine) + /// + /// See + /// + /// + /// + public static DateTimeOffset Freeze(DateTimeOffset now) { - return Clock.Freeze(baseLine); + return Clock.Freeze(now); } + /// + /// See + /// + /// public static DateTimeOffset Freeze() { return Clock.Freeze(); } + /// + /// See + /// + /// public static bool IsFrozen() { return Clock.IsFrozen(); } + /// + /// See + /// + /// public static DateTimeOffset Reset() { return Clock.Reset(); } + /// + /// See + /// + /// public static DateTimeOffset Thaw() { return Clock.Thaw(); } - public static void From(TimeTravelersClock xSystemTime) + /// + /// See + /// + /// + public static void SyncWith(TimeTravelersClock otherTimeTravelersClock) { - Clock.From(xSystemTime); + Clock.SyncWith(otherTimeTravelersClock); } + /// + /// See + /// + /// public static bool IsManipulated() { return Clock.IsManipulated(); diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping/TimeTravelersClock.cs b/TimeKeeping/DotNetThoughts.TimeKeeping/TimeTravelersClock.cs index 952146c..2469e67 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping/TimeTravelersClock.cs +++ b/TimeKeeping/DotNetThoughts.TimeKeeping/TimeTravelersClock.cs @@ -1,26 +1,54 @@ namespace DotNetThoughts.TimeKeeping; +/// +/// A clock that can be manipulated to simulate time travel and time freezing. +/// public class TimeTravelersClock { + /// + /// Don't use this. It is internal stuff. Only read or write as part of serialization. + /// public DateTimeOffset? FreezedAt { get; set; } + + /// + /// Don't use this. It is internal stuff. Only read or write as part of serialization. + /// public DateTimeOffset? Frozen { get; set; } + + /// + /// Don't use this. It is internal stuff. Only read or write as part of serialization. + /// public TimeSpan Offset { get; set; } = TimeSpan.Zero; + /// + /// Returns true if the clock is currently frozen and time is standing still, otherwise false. + /// When time is frozen, a series of calls to Now() will return the same value. + /// public bool IsFrozen() { using var context = new OperationContext(); return FreezedAt != null; } + /// + /// Resets the clock to the current time and removes any time manipulation. + /// This means the clock is not frozen and time is not offset. + /// + /// Now() public DateTimeOffset Reset() { using var context = new OperationContext(); Frozen = null; FreezedAt = null; Offset = TimeSpan.Zero; - return UtcNow(); + return Now(); } + /// + /// Thaws the clock if it is currently frozen. + /// + /// Now() + /// If you're trying to that a clock that is not frozen. public DateTimeOffset Thaw() { using var context = new OperationContext(); @@ -31,39 +59,66 @@ public DateTimeOffset Thaw() Offset = Offset.Add(-RealTimeFrozen()); FreezedAt = null; Frozen = null; - return UtcNow(); + return Now(); } + /// + /// Stops time from moving forward. + /// A series of calls to Now() will return the same value. + /// + /// Now() public DateTimeOffset Freeze() { using var context = new OperationContext(); - FreezedAt = context.UtcNow(); - Frozen = UtcNow(); + FreezedAt = OperationContext.Now(); + Frozen = Now(); return Frozen.Value; } - public DateTimeOffset Freeze(DateTimeOffset baseLine) + /// + /// Stops time from moving forward. + /// A series of calls to Now() will return the passed time `now`. + /// + /// The time the clock supposedly was when frozen. + /// Now() + public DateTimeOffset Freeze(DateTimeOffset now) { using var context = new OperationContext(); Reset(); - SetBaseline(baseLine); - FreezedAt = context.UtcNow(); - Frozen = UtcNow(); + SetNow(now); + FreezedAt = OperationContext.Now(); + Frozen = Now(); return Frozen.Value; } - public DateTimeOffset UtcNow() + /// + /// Returns the current time, manipulated or not. + /// + /// The time of the time travelers clock + public DateTimeOffset Now() { using var context = new OperationContext(); - return Frozen ?? context.UtcNow().Add(Offset); + return Frozen ?? OperationContext.Now().Add(Offset); } + /// + /// Shortcut for advancing the clock by a number of days. + /// Works on both frozen and unfrozen clocks. + /// + /// + /// Now() public DateTimeOffset AdvanceDays(double days) { using var context = new OperationContext(); return Advance(TimeSpan.FromDays(days)); } + /// + /// Advances the clock by a given time span. + /// Works on both frozen and unfrozen clocks. + /// + /// + /// Now() public DateTimeOffset Advance(TimeSpan timeSpan) { using var context = new OperationContext(); @@ -72,50 +127,70 @@ public DateTimeOffset Advance(TimeSpan timeSpan) { Frozen = Frozen!.Value.Add(timeSpan); } - return UtcNow(); + return Now(); } - public DateTimeOffset SetBaseline(DateTimeOffset baseLine) + /// + /// Instead of advancing the clock, this method sets the clock to a specific time. + /// An unfrozen clock will still be unfrozen, and a frozen clock will still be frozen, but frozen at the given time. + /// + /// + /// Now() + /// + public DateTimeOffset SetNow(DateTimeOffset now) { if (IsFrozen()) throw new InvalidOperationException(); using var context = new OperationContext(); - Offset = baseLine - context.UtcNow(); - return UtcNow(); + Offset = now - OperationContext.Now(); + return Now(); } + /// + /// Sets the clocks offset (offset to real world clock) to a specific time span. + /// + /// + /// + /// Invalid operation on a frozen clock! (I forgot why) public DateTimeOffset SetOffset(TimeSpan offset) { if (IsFrozen()) throw new InvalidOperationException(); using var context = new OperationContext(); Offset = offset; - return UtcNow(); + return Now(); } private TimeSpan RealTimeFrozen() { using var context = new OperationContext(); - return FreezedAt != null ? context.UtcNow() - FreezedAt.Value : TimeSpan.Zero; + return FreezedAt != null ? OperationContext.Now() - FreezedAt.Value : TimeSpan.Zero; } - internal void From(TimeTravelersClock xSystemTime) + /// + /// Syncs this clock with another time travelers clock. + /// + public void SyncWith(TimeTravelersClock otherTimeTravelersClock) { using var context = new OperationContext(); - Offset = xSystemTime.Offset; - FreezedAt = xSystemTime.FreezedAt; - Frozen = xSystemTime.Frozen; + Offset = otherTimeTravelersClock.Offset; + FreezedAt = otherTimeTravelersClock.FreezedAt; + Frozen = otherTimeTravelersClock.Frozen; } - internal bool IsManipulated() + /// + /// Returns true if the clock is currently manipulated in any way. (frozen or offset) + /// + /// + public bool IsManipulated() { return Offset != TimeSpan.Zero || FreezedAt != null; } internal class OperationContext : IDisposable { - private static AsyncLocal _operationNow = new AsyncLocal(); + private static readonly AsyncLocal _operationNow = new AsyncLocal(); - private bool _shouldReset = false; + private readonly bool _shouldReset = false; public OperationContext() { @@ -133,6 +208,6 @@ public void Dispose() } } - public DateTimeOffset UtcNow() => _operationNow.Value!.Value; + public static DateTimeOffset Now() => _operationNow.Value!.Value; } } \ No newline at end of file diff --git a/TimeKeeping/ReadMe.md b/TimeKeeping/ReadMe.md index e69de29..4fcbd1c 100644 --- a/TimeKeeping/ReadMe.md +++ b/TimeKeeping/ReadMe.md @@ -0,0 +1,33 @@ +# Time Keeping + +## SystemTime + +`SystemTime.Now()` is a drop-in replacement for `DateTimeOffset.UtcNow` with a couple of extra features. + +To begin with, you can manipulate time using SystemTime. +This is useful for testing, where you want to test how your code behave at different times, or where you want the code to be deterministic. +`SystemTime` works through `AsyncLocal`, so you do not have to inject a `ITimeProvider` wherever you need to be able to mock time. + +Some example usages: + +```csharp +// Set time to midnight 2024-01-01 (utc I guess) and stop time from moving. +// This means SystemTime.Now() will always return 2024-01-01 +SystemTime.Freeze(DateTimeOffset.Parse("2024-01-01")); + +// Move time one day forward. Time is still frozen. +// This means SystemTime.Now() will always return 2024-01-02 +SystemTime.Advance(TimeSpan.FromDays(1)); + +// Unfreezes time. Time is now 2024-01-02 but time started moving again. +// This means SystemTime.Now() will work as normal, but at an offset to the real-world time. +SystemTime.Thaw(); + +// Reset time to real world time. +SystemTime.Reset(); +``` + + +## TimeTravelersClock + +TimeTravelersClock is SystemTime but without the AsyncLocal magic. You can instantiate a new TimeTravelsersClock and manipulate it just like SystemTime. \ No newline at end of file From 9c02dab4b5e163e3c1318a088ef42368c3275105 Mon Sep 17 00:00:00 2001 From: Mattias Nordqvist Date: Sun, 22 Sep 2024 23:21:14 +0200 Subject: [PATCH 02/11] . --- TimeKeeping/ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TimeKeeping/ReadMe.md b/TimeKeeping/ReadMe.md index 4fcbd1c..f3ad5ba 100644 --- a/TimeKeeping/ReadMe.md +++ b/TimeKeeping/ReadMe.md @@ -4,7 +4,7 @@ `SystemTime.Now()` is a drop-in replacement for `DateTimeOffset.UtcNow` with a couple of extra features. -To begin with, you can manipulate time using SystemTime. +You can manipulate time using SystemTime. This is useful for testing, where you want to test how your code behave at different times, or where you want the code to be deterministic. `SystemTime` works through `AsyncLocal`, so you do not have to inject a `ITimeProvider` wherever you need to be able to mock time. From 77859452a71be780ed131e0ffbe23f458b0045a7 Mon Sep 17 00:00:00 2001 From: Mattias Nordqvist Date: Mon, 23 Sep 2024 21:14:44 +0200 Subject: [PATCH 03/11] docs on localdatetime --- DotNetThoughts.sln | 29 ++++++---- .../DotNetThoughts.LocalTimeKit.Tests.csproj | 13 +++++ .../LocalDateTimeTests.cs | 8 ++- .../DotNetThoughts.LocalTimeKit.csproj | 2 +- .../LocalDateTime.cs | 53 +++++++++++++++++++ LocalTimeKit/ReadMe.md | 14 +++++ ReadMe.md | 3 ++ .../DotNetThoughts.TimeKeeping.Tests.csproj | 1 - 8 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 LocalTimeKit/DotNetThoughts.LocalTimeKit.Tests/DotNetThoughts.LocalTimeKit.Tests.csproj rename {TimeKeeping/DotNetThoughts.TimeKeeping.Tests => LocalTimeKit/DotNetThoughts.LocalTimeKit.Tests}/LocalDateTimeTests.cs (97%) rename {TimeKeeping => LocalTimeKit}/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj (88%) rename {TimeKeeping => LocalTimeKit}/DotNetThoughts.LocalTimeKit/LocalDateTime.cs (59%) create mode 100644 LocalTimeKit/ReadMe.md diff --git a/DotNetThoughts.sln b/DotNetThoughts.sln index eea63eb..0489ec2 100644 --- a/DotNetThoughts.sln +++ b/DotNetThoughts.sln @@ -10,11 +10,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetThoughts.TimeKeeping.Tests", "TimeKeeping\DotNetThoughts.TimeKeeping.Tests\DotNetThoughts.TimeKeeping.Tests.csproj", "{DF62C221-71AD-49A7-A9D8-48E31F06E27B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TimeKeeping", "TimeKeeping", "{695E363E-055E-4ED5-9613-587E02A67578}" - ProjectSection(SolutionItems) = preProject - TimeKeeping\ReadMe.md = TimeKeeping\ReadMe.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetThoughts.LocalTimeKit", "TimeKeeping\DotNetThoughts.LocalTimeKit\DotNetThoughts.LocalTimeKit.csproj", "{03DA1023-FDF4-4EF5-8951-19806434F8CA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetThoughts.Results", "Results\DotNetThoughts.Results\DotNetThoughts.Results.csproj", "{7032EE8C-FC18-4610-B7D6-613E552C278D}" EndProject @@ -52,6 +47,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ReadMe.md = ReadMe.md EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LocalTimeKit", "LocalTimeKit", "{11FA58AF-0B7C-4465-8448-AA710D5725AA}" + ProjectSection(SolutionItems) = preProject + LocalTimeKit\ReadMe.md = LocalTimeKit\ReadMe.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetThoughts.LocalTimeKit", "LocalTimeKit\DotNetThoughts.LocalTimeKit\DotNetThoughts.LocalTimeKit.csproj", "{0F5838D8-AC76-4C87-8DB6-6AE8140397A8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetThoughts.LocalTimeKit.Tests", "LocalTimeKit\DotNetThoughts.LocalTimeKit.Tests\DotNetThoughts.LocalTimeKit.Tests.csproj", "{BA3C3301-7D35-4828-8F00-736CDDEBC800}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -70,10 +74,6 @@ Global {DF62C221-71AD-49A7-A9D8-48E31F06E27B}.Debug|Any CPU.Build.0 = Debug|Any CPU {DF62C221-71AD-49A7-A9D8-48E31F06E27B}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF62C221-71AD-49A7-A9D8-48E31F06E27B}.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 {7032EE8C-FC18-4610-B7D6-613E552C278D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7032EE8C-FC18-4610-B7D6-613E552C278D}.Debug|Any CPU.Build.0 = Debug|Any CPU {7032EE8C-FC18-4610-B7D6-613E552C278D}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -106,6 +106,14 @@ 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 + {0F5838D8-AC76-4C87-8DB6-6AE8140397A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F5838D8-AC76-4C87-8DB6-6AE8140397A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F5838D8-AC76-4C87-8DB6-6AE8140397A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F5838D8-AC76-4C87-8DB6-6AE8140397A8}.Release|Any CPU.Build.0 = Release|Any CPU + {BA3C3301-7D35-4828-8F00-736CDDEBC800}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA3C3301-7D35-4828-8F00-736CDDEBC800}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA3C3301-7D35-4828-8F00-736CDDEBC800}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA3C3301-7D35-4828-8F00-736CDDEBC800}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -114,7 +122,6 @@ Global {6B9CCBB9-73D3-4062-8317-5D54CF6B85D4} = {695E363E-055E-4ED5-9613-587E02A67578} {917A0D7B-F7FA-4D39-B892-B9C1BA9BE284} = {695E363E-055E-4ED5-9613-587E02A67578} {DF62C221-71AD-49A7-A9D8-48E31F06E27B} = {695E363E-055E-4ED5-9613-587E02A67578} - {03DA1023-FDF4-4EF5-8951-19806434F8CA} = {695E363E-055E-4ED5-9613-587E02A67578} {7032EE8C-FC18-4610-B7D6-613E552C278D} = {BECD3795-EDD5-459E-BFCC-017082E1C34D} {8CA73623-9009-4FEF-885E-84F9BBD61536} = {BECD3795-EDD5-459E-BFCC-017082E1C34D} {02C498D0-532A-4F1D-9004-7E2E24121DF3} = {BECD3795-EDD5-459E-BFCC-017082E1C34D} @@ -123,6 +130,8 @@ 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} + {0F5838D8-AC76-4C87-8DB6-6AE8140397A8} = {11FA58AF-0B7C-4465-8448-AA710D5725AA} + {BA3C3301-7D35-4828-8F00-736CDDEBC800} = {11FA58AF-0B7C-4465-8448-AA710D5725AA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {CAA5B6BA-B6C8-4A90-B9E6-D7634295532E} diff --git a/LocalTimeKit/DotNetThoughts.LocalTimeKit.Tests/DotNetThoughts.LocalTimeKit.Tests.csproj b/LocalTimeKit/DotNetThoughts.LocalTimeKit.Tests/DotNetThoughts.LocalTimeKit.Tests.csproj new file mode 100644 index 0000000..29b7c13 --- /dev/null +++ b/LocalTimeKit/DotNetThoughts.LocalTimeKit.Tests/DotNetThoughts.LocalTimeKit.Tests.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/LocalDateTimeTests.cs b/LocalTimeKit/DotNetThoughts.LocalTimeKit.Tests/LocalDateTimeTests.cs similarity index 97% rename from TimeKeeping/DotNetThoughts.TimeKeeping.Tests/LocalDateTimeTests.cs rename to LocalTimeKit/DotNetThoughts.LocalTimeKit.Tests/LocalDateTimeTests.cs index 1ad93bf..1e5cd21 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/LocalDateTimeTests.cs +++ b/LocalTimeKit/DotNetThoughts.LocalTimeKit.Tests/LocalDateTimeTests.cs @@ -1,13 +1,11 @@ -using DotNetThoughts.LocalTimeKit; - -namespace DotNetThoughts.TimeKeeping.Tests; +namespace DotNetThoughts.LocalTimeKit.Tests; public class LocalDateTimeTests { [Fact] public void TestLocalDateTimeOffset() { - var t = new DateTime(2024,06,06, 0, 0, 0, DateTimeKind.Unspecified); + var t = new DateTime(2024, 06, 06, 0, 0, 0, DateTimeKind.Unspecified); var tz = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); var ldt = new LocalDateTime(t, tz); var expectedDateTimeOffset = DateTimeOffset.Parse("2024-06-06T00:00:00+02:00"); @@ -67,7 +65,7 @@ public void TestDateConstructor() var tz = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); var ldt = new LocalDateTime(t, tz); var ldt2 = new LocalDateTime(d, tz); - + ldt.DateTime.Should().Be(ldt2.DateTime); } diff --git a/TimeKeeping/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj b/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj similarity index 88% rename from TimeKeeping/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj rename to LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj index baec30d..0997442 100644 --- a/TimeKeeping/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj +++ b/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj @@ -1,7 +1,7 @@  - 1.0.0 + 1.0.1 True MIT diff --git a/TimeKeeping/DotNetThoughts.LocalTimeKit/LocalDateTime.cs b/LocalTimeKit/DotNetThoughts.LocalTimeKit/LocalDateTime.cs similarity index 59% rename from TimeKeeping/DotNetThoughts.LocalTimeKit/LocalDateTime.cs rename to LocalTimeKit/DotNetThoughts.LocalTimeKit/LocalDateTime.cs index 9140778..e3674e1 100644 --- a/TimeKeeping/DotNetThoughts.LocalTimeKit/LocalDateTime.cs +++ b/LocalTimeKit/DotNetThoughts.LocalTimeKit/LocalDateTime.cs @@ -5,12 +5,40 @@ /// public readonly record struct LocalDateTime { + /// + /// Creates a LocalDateTime from a DateOnly and a TimeZoneInfo + /// The date represents midnight in the given timezone + /// + /// See + /// + /// + /// public LocalDateTime(DateOnly date, TimeZoneInfo timeZoneInfo) : this(date.ToDateTime(TimeOnly.MinValue), timeZoneInfo) { } + /// + /// Creates a LocalDateTime from a DateTimeOffset and a TimeZoneInfo + /// + /// See + /// + /// + /// public LocalDateTime(DateTimeOffset dateTimeOffset, TimeZoneInfo timeZoneInfo) : this(TimeZoneInfo.ConvertTime(dateTimeOffset, timeZoneInfo).DateTime, timeZoneInfo) { } + /// + /// Creates a LocalDateTime from a DateTime and a TimeZoneInfo + /// If the datetime has DateTimeKind.Local, the timezone must be TimeZoneInfo.Local + /// If the datetime has DateTimeKind.Utc, the timezone must be TimeZoneInfo.Utc + /// Otherwise, the timezone must not be TimeZoneInfo.Local + /// In any of the cases above, an ArgumentException is thrown. + /// + /// Some times are invalid for some timezones, and some times are ambiguous. + /// Any such time and timezone combination will throw an ArgumentException. + /// + /// + /// + /// public LocalDateTime(DateTime dateTime, TimeZoneInfo timeZoneInfo) { if (!(dateTime.Kind == DateTimeKind.Local && timeZoneInfo == TimeZoneInfo.Local @@ -37,32 +65,57 @@ public LocalDateTime(DateTime dateTime, TimeZoneInfo timeZoneInfo) public DateTime DateTime { get; } public TimeZoneInfo TimeZoneInfo { get; } + /// + /// Returns a new LocalDateTime representing the same instance in time, but in relation to a different time zone. + /// + /// + /// public LocalDateTime ToTimeZone(TimeZoneInfo newTz) { return new LocalDateTime(TimeZoneInfo.ConvertTime(DateTime, TimeZoneInfo, newTz), newTz); } + /// + /// Returns a new LocalDateTime representing the midnight of the current date. + /// + /// public LocalDateTime ToMidnight() { return new LocalDateTime(DateTime.Date, TimeZoneInfo); } + /// + /// Returns a new LocalDateTime representing the beginning of the current year. + /// + /// public LocalDateTime ToBeginningOfYear() { return new LocalDateTime(new DateTime(DateTime.Year, 1, 1), TimeZoneInfo); } + /// + /// Returns a new LocalDateTime representing the end of the current year. + /// + /// public LocalDateTime ToEndOfYear() { return new LocalDateTime(new DateTime(DateTime.Year, 12, 31, 23, 59, 59, 999, 999), TimeZoneInfo); } + /// + /// Returns a DateTimeOffset, with offset 0, representing this instance in time. + /// + /// public DateTimeOffset ToDateTimeOffsetUtc() { var shouldHaveHadKindUtc = TimeZoneInfo.ConvertTimeToUtc(DateTime, TimeZoneInfo); return shouldHaveHadKindUtc; } + /// + /// Returns a DateTimeOffset, with offset matching the current timezone, representing this instance in time. + /// + /// public DateTimeOffset ToDateTimeOffsetLocal() { return new DateTimeOffset(DateTime, TimeZoneInfo.GetUtcOffset(DateTime)); diff --git a/LocalTimeKit/ReadMe.md b/LocalTimeKit/ReadMe.md new file mode 100644 index 0000000..b41c217 --- /dev/null +++ b/LocalTimeKit/ReadMe.md @@ -0,0 +1,14 @@ +# Local Time Kit + +## LocalDateTime + +`LocalDateTime` is an unambiguous representation of an instance in time. +Types like `DateTime` and `DateOnly` does not provide enough information to determine exactly what instance in time they represent. LocalDateTime aims to package all you need into one type. + +Don't worry about the word "Local" in the name. LocalDateTime is not limited to local instances in time in the normal sense. LocalDateTime consider even a UTC date to be local. Local to the utc time zone. + +While .Net has a lot of shady "helpful" implicit conversions between `DateTime` and `DateTimeOffset`, LocalDateTime is explicit about what it does. + +No time-operations, makes any sense without knowing which timezone you are operating in. Remember, daylight savings have the effect of some days lasting only 23 hours, while others last 25. +LocalDateTime therefor requires you to specify a timezone when you create an instance. +LocalDateTime then helps you to convert between timezones, and to calculate timezone-related specific instances of time, like beginning of year, or midnight of current day etc. diff --git a/ReadMe.md b/ReadMe.md index c8d0dcd..2889855 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -4,5 +4,8 @@ # TimeKeeping [TimeKeeping/ReadMe.md](TimeKeeping/ReadMe.md) +# LocalDateTime +[LocalDateTime/ReadMe.md](LocalDateTime/ReadMe.md) + # UserSecrets [UserSecrets/ReadMe.md](UserSecrets/ReadMe.md) diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/DotNetThoughts.TimeKeeping.Tests.csproj b/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/DotNetThoughts.TimeKeeping.Tests.csproj index 87fe745..6165ba6 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/DotNetThoughts.TimeKeeping.Tests.csproj +++ b/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/DotNetThoughts.TimeKeeping.Tests.csproj @@ -1,7 +1,6 @@  - From de662a82a69bdb43f29b21b44dde86f44e316989 Mon Sep 17 00:00:00 2001 From: Mattias Nordqvist Date: Mon, 23 Sep 2024 21:21:41 +0200 Subject: [PATCH 04/11] . --- .../DotNetThoughts.LocalTimeKit.Tests.csproj | 6 ------ .../DotNetThoughts.TimeKeeping.App.csproj | 3 +-- .../packages.lock.json | 12 ++++++------ 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/LocalTimeKit/DotNetThoughts.LocalTimeKit.Tests/DotNetThoughts.LocalTimeKit.Tests.csproj b/LocalTimeKit/DotNetThoughts.LocalTimeKit.Tests/DotNetThoughts.LocalTimeKit.Tests.csproj index 29b7c13..e62be9c 100644 --- a/LocalTimeKit/DotNetThoughts.LocalTimeKit.Tests/DotNetThoughts.LocalTimeKit.Tests.csproj +++ b/LocalTimeKit/DotNetThoughts.LocalTimeKit.Tests/DotNetThoughts.LocalTimeKit.Tests.csproj @@ -1,11 +1,5 @@  - - net8.0 - enable - enable - - diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping.App/DotNetThoughts.TimeKeeping.App.csproj b/TimeKeeping/DotNetThoughts.TimeKeeping.App/DotNetThoughts.TimeKeeping.App.csproj index 7812479..d97bbf8 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping.App/DotNetThoughts.TimeKeeping.App.csproj +++ b/TimeKeeping/DotNetThoughts.TimeKeeping.App/DotNetThoughts.TimeKeeping.App.csproj @@ -5,8 +5,7 @@ - - + diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping.App/packages.lock.json b/TimeKeeping/DotNetThoughts.TimeKeeping.App/packages.lock.json index 03e7df9..5ffb3a9 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping.App/packages.lock.json +++ b/TimeKeeping/DotNetThoughts.TimeKeeping.App/packages.lock.json @@ -23,15 +23,15 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.6, )", - "resolved": "8.0.6", - "contentHash": "E+lDylsTeP4ZiDmnEkiJ5wobnGaIJzFhOgZppznJCb69UZgbh6G3cfv1pnLDLLBx6JAgl0kAlnINDeT3uCuczQ==" + "requested": "[8.0.8, )", + "resolved": "8.0.8", + "contentHash": "P8wR6MUWwYXIjPJuBaZgo5zlI/GWI6QEAo6NyVIbPefa9CCkohYu7dP2rD/mrqnjEqfRHyl+h9VZrDoGpELqYg==" }, "Microsoft.NET.Sdk.WebAssembly.Pack": { "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "CABBOqZZEDgiVSjkwUr+pJEGEtQbrT+6dltzJnBDap7mOSvfn53+zwb112hRGB7ynm9ikAocGZTUp+GizbH7XQ==" + "requested": "[8.0.8, )", + "resolved": "8.0.8", + "contentHash": "z2UxqxjssvxlcQ68KZorgQDITdV2u5H6CWOGhs3kiv+qqDtokhDpcK660TUMrX+FTDdRKie0l7EeZEdej26Ycw==" }, "Microsoft.AspNetCore.Authorization": { "type": "Transitive", From b04f0cb0fc77722c3e1d4a9948ba0e208b8859cf Mon Sep 17 00:00:00 2001 From: Mattias Nordqvist Date: Mon, 23 Sep 2024 21:25:11 +0200 Subject: [PATCH 05/11] moved localdatetime to its own package --- .../DotNetThoughts.TimeKeeping.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj b/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj index b8b6657..7fb29c1 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj +++ b/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj @@ -1,6 +1,6 @@  - 1.3.0 + 1.3.1 True MIT From 2385db502c02d064a32de3c87fe4665a6f6c6426 Mon Sep 17 00:00:00 2001 From: Mattias Nordqvist Date: Mon, 23 Sep 2024 21:34:29 +0200 Subject: [PATCH 06/11] add readmes --- DotNetThoughts.sln | 6 ------ .../DotNetThoughts.LocalTimeKit.csproj | 1 + LocalTimeKit/{ => DotNetThoughts.LocalTimeKit}/ReadMe.md | 0 ReadMe.md | 6 +++--- .../DotNetThoughts.Results/DotNetThoughts.Results.csproj | 3 ++- Results/{ => DotNetThoughts.Results}/ReadMe.md | 0 .../DotNetThoughts.TimeKeeping.csproj | 1 + TimeKeeping/{ => DotNetThoughts.TimeKeeping}/ReadMe.md | 0 8 files changed, 7 insertions(+), 10 deletions(-) rename LocalTimeKit/{ => DotNetThoughts.LocalTimeKit}/ReadMe.md (100%) rename Results/{ => DotNetThoughts.Results}/ReadMe.md (100%) rename TimeKeeping/{ => DotNetThoughts.TimeKeeping}/ReadMe.md (100%) diff --git a/DotNetThoughts.sln b/DotNetThoughts.sln index 0489ec2..0e6ca06 100644 --- a/DotNetThoughts.sln +++ b/DotNetThoughts.sln @@ -28,9 +28,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetThoughts.Results.Analyzer.Package", "Results\DotNetThoughts.Results.Analyzer\DotNetThoughts.Results.Analyzer.Package\DotNetThoughts.Results.Analyzer.Package.csproj", "{E0E86A0B-F4CE-4E88-99CF-C54AFC1C0C58}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Results", "Results", "{BECD3795-EDD5-459E-BFCC-017082E1C34D}" - ProjectSection(SolutionItems) = preProject - Results\ReadMe.md = Results\ReadMe.md - EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UserSecrets", "UserSecrets", "{BC7FC0EC-0CDC-4B88-A60C-BC920712841B}" ProjectSection(SolutionItems) = preProject @@ -48,9 +45,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LocalTimeKit", "LocalTimeKit", "{11FA58AF-0B7C-4465-8448-AA710D5725AA}" - ProjectSection(SolutionItems) = preProject - LocalTimeKit\ReadMe.md = LocalTimeKit\ReadMe.md - EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetThoughts.LocalTimeKit", "LocalTimeKit\DotNetThoughts.LocalTimeKit\DotNetThoughts.LocalTimeKit.csproj", "{0F5838D8-AC76-4C87-8DB6-6AE8140397A8}" EndProject diff --git a/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj b/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj index 0997442..e354ab4 100644 --- a/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj +++ b/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj @@ -4,6 +4,7 @@ 1.0.1 True MIT + ReadMe.md diff --git a/LocalTimeKit/ReadMe.md b/LocalTimeKit/DotNetThoughts.LocalTimeKit/ReadMe.md similarity index 100% rename from LocalTimeKit/ReadMe.md rename to LocalTimeKit/DotNetThoughts.LocalTimeKit/ReadMe.md diff --git a/ReadMe.md b/ReadMe.md index 2889855..30b5543 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,11 +1,11 @@ # Results -[Results/ReadMe.md](Results/ReadMe.md) +[Results/DotNetThoughts.Results/ReadMe.md](Results/DotNetThoughts.Results/ReadMe.md) # TimeKeeping -[TimeKeeping/ReadMe.md](TimeKeeping/ReadMe.md) +[TimeKeeping/DotNetThoughts.TimeKeeping/ReadMe.md](TimeKeeping/DotNetThoughts.TimeKeeping/ReadMe.md) # LocalDateTime -[LocalDateTime/ReadMe.md](LocalDateTime/ReadMe.md) +[LocalDateTime/DotNetThoughts.LocalTimeKit/ReadMe.md](LocalDateTime/DotNetThoughts.LocalTimeKit/ReadMe.md) # UserSecrets [UserSecrets/ReadMe.md](UserSecrets/ReadMe.md) diff --git a/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj b/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj index fd18619..4e0851c 100644 --- a/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj +++ b/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj @@ -1,9 +1,10 @@  - 1.5.8 + 1.5.9 True MIT + ReadMe.md diff --git a/Results/ReadMe.md b/Results/DotNetThoughts.Results/ReadMe.md similarity index 100% rename from Results/ReadMe.md rename to Results/DotNetThoughts.Results/ReadMe.md diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj b/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj index 7fb29c1..8a383b8 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj +++ b/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj @@ -3,5 +3,6 @@ 1.3.1 True MIT + ReadMe.md diff --git a/TimeKeeping/ReadMe.md b/TimeKeeping/DotNetThoughts.TimeKeeping/ReadMe.md similarity index 100% rename from TimeKeeping/ReadMe.md rename to TimeKeeping/DotNetThoughts.TimeKeeping/ReadMe.md From b866e227a9491b82d13d02c46d7731db15edb204 Mon Sep 17 00:00:00 2001 From: Mattias Nordqvist Date: Mon, 23 Sep 2024 21:36:13 +0200 Subject: [PATCH 07/11] ? --- .../DotNetThoughts.LocalTimeKit.csproj | 4 +++- Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj | 4 +++- .../DotNetThoughts.TimeKeeping.csproj | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj b/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj index e354ab4..5fec2b0 100644 --- a/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj +++ b/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj @@ -6,5 +6,7 @@ MIT ReadMe.md - + + + diff --git a/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj b/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj index 4e0851c..c229a7f 100644 --- a/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj +++ b/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj @@ -6,7 +6,9 @@ MIT ReadMe.md - + + + $(DefineConstants);CONTRACTS_FULL diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj b/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj index 8a383b8..c173494 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj +++ b/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj @@ -5,4 +5,7 @@ MIT ReadMe.md + + + From 9fde67074ef995a9b359c5ffe8b7f5648128799e Mon Sep 17 00:00:00 2001 From: Mattias Nordqvist Date: Mon, 23 Sep 2024 21:37:26 +0200 Subject: [PATCH 08/11] omg --- .../DotNetThoughts.LocalTimeKit.csproj | 2 +- Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj | 2 +- .../DotNetThoughts.TimeKeeping.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj b/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj index 5fec2b0..50bbf5b 100644 --- a/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj +++ b/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj @@ -7,6 +7,6 @@ ReadMe.md - + diff --git a/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj b/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj index c229a7f..6d1a506 100644 --- a/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj +++ b/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj @@ -7,7 +7,7 @@ ReadMe.md - + $(DefineConstants);CONTRACTS_FULL diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj b/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj index c173494..b9ec999 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj +++ b/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj @@ -6,6 +6,6 @@ ReadMe.md - + From 284f4878439470109c8ed15d8e1f18d4726c6484 Mon Sep 17 00:00:00 2001 From: Mattias Nordqvist Date: Wed, 25 Sep 2024 22:09:13 +0200 Subject: [PATCH 09/11] added more readmes --- ReadMe.md | 6 ++++++ Results/DotNetThoughts.Results.Json/ReadMe.md | 0 2 files changed, 6 insertions(+) create mode 100644 Results/DotNetThoughts.Results.Json/ReadMe.md diff --git a/ReadMe.md b/ReadMe.md index 30b5543..c21c4ca 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,6 +1,12 @@ # Results [Results/DotNetThoughts.Results/ReadMe.md](Results/DotNetThoughts.Results/ReadMe.md) +# Results.Validation +[Results/DotNetThoughts.Results.Validation/ReadMe.md](Results/DotNetThoughts.Results.Validation/ReadMe.md) + +# Results.Json +[Results/DotNetThoughts.Results.Json/ReadMe.md](Results/DotNetThoughts.Results.Json/ReadMe.md) + # TimeKeeping [TimeKeeping/DotNetThoughts.TimeKeeping/ReadMe.md](TimeKeeping/DotNetThoughts.TimeKeeping/ReadMe.md) diff --git a/Results/DotNetThoughts.Results.Json/ReadMe.md b/Results/DotNetThoughts.Results.Json/ReadMe.md new file mode 100644 index 0000000..e69de29 From bbd3ae82287a53872b437d8e8b666d3a457e50d1 Mon Sep 17 00:00:00 2001 From: Mattias Nordqvist Date: Wed, 25 Sep 2024 22:22:30 +0200 Subject: [PATCH 10/11] add readme dynamically --- Directory.Build.props | 6 ++++++ .../DotNetThoughts.LocalTimeKit.csproj | 4 ---- .../DotNetThoughts.Results.Json.Tests/packages.lock.json | 2 +- .../packages.lock.json | 2 +- .../DotNetThoughts.Results/DotNetThoughts.Results.csproj | 4 ---- .../DotNetThoughts.TimeKeeping.Tests/packages.lock.json | 3 --- .../DotNetThoughts.TimeKeeping.csproj | 4 ---- 7 files changed, 8 insertions(+), 17 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 3554c5f..7db17af 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,6 +4,12 @@ enable enable + + ReadMe.md + + + + diff --git a/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj b/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj index 50bbf5b..1920ba0 100644 --- a/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj +++ b/LocalTimeKit/DotNetThoughts.LocalTimeKit/DotNetThoughts.LocalTimeKit.csproj @@ -4,9 +4,5 @@ 1.0.1 True MIT - ReadMe.md - - - diff --git a/Results/DotNetThoughts.Results.Json.Tests/packages.lock.json b/Results/DotNetThoughts.Results.Json.Tests/packages.lock.json index 2301965..2a1a7e5 100644 --- a/Results/DotNetThoughts.Results.Json.Tests/packages.lock.json +++ b/Results/DotNetThoughts.Results.Json.Tests/packages.lock.json @@ -176,7 +176,7 @@ "dotnetthoughts.results.json": { "type": "Project", "dependencies": { - "DotNetThoughts.Results": "[1.5.7, )" + "DotNetThoughts.Results": "[1.5.9, )" } }, "DotNetThoughts.Results.Analyzer": { diff --git a/Results/DotNetThoughts.Results.Validation.Tests/packages.lock.json b/Results/DotNetThoughts.Results.Validation.Tests/packages.lock.json index 8df550d..a2e6a16 100644 --- a/Results/DotNetThoughts.Results.Validation.Tests/packages.lock.json +++ b/Results/DotNetThoughts.Results.Validation.Tests/packages.lock.json @@ -176,7 +176,7 @@ "dotnetthoughts.results.validation": { "type": "Project", "dependencies": { - "DotNetThoughts.Results": "[1.5.7, )" + "DotNetThoughts.Results": "[1.5.9, )" } }, "DotNetThoughts.Results.Analyzer": { diff --git a/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj b/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj index 6d1a506..00dc581 100644 --- a/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj +++ b/Results/DotNetThoughts.Results/DotNetThoughts.Results.csproj @@ -4,11 +4,7 @@ 1.5.9 True MIT - ReadMe.md - - - $(DefineConstants);CONTRACTS_FULL diff --git a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/packages.lock.json b/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/packages.lock.json index 47d7ce7..baa83d6 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/packages.lock.json +++ b/TimeKeeping/DotNetThoughts.TimeKeeping.Tests/packages.lock.json @@ -167,9 +167,6 @@ "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 b9ec999..7fb29c1 100644 --- a/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj +++ b/TimeKeeping/DotNetThoughts.TimeKeeping/DotNetThoughts.TimeKeeping.csproj @@ -3,9 +3,5 @@ 1.3.1 True MIT - ReadMe.md - - - From ce84ebb9e904d86cf3f6133728561f63193848c4 Mon Sep 17 00:00:00 2001 From: Mattias Nordqvist Date: Wed, 25 Sep 2024 23:00:11 +0200 Subject: [PATCH 11/11] added results.json docs --- Results/DotNetThoughts.Results.Json/ReadMe.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Results/DotNetThoughts.Results.Json/ReadMe.md b/Results/DotNetThoughts.Results.Json/ReadMe.md index e69de29..4cd28ae 100644 --- a/Results/DotNetThoughts.Results.Json/ReadMe.md +++ b/Results/DotNetThoughts.Results.Json/ReadMe.md @@ -0,0 +1,36 @@ +# Results.Json + +Package contains code to help serialize and deserialize Result objects to and from JSON. +`JsonConverterFactoryForResultOfT` is a `JsonConverterFactory` that can create `JsonConverter` objects for any `Result` object. +Remember, a Result representing one or more Errors contains a list of objects implementing the `IError` interface. +The actual type of an Error object is not included when serialized. This means, that when deserializing that Error again, it can't be deserialized to its original type. +Instead, the Error object is deserialized to a generic `DeserializedError` object. + +A serialized _Successful_ `Result` will look like this: +```json +{ + "Success": true, + "Value": "Some value" +} +``` +A serialized _Successful_ `Result` will look like this: +```json +{ + "Success": true +} +``` +A serialized _Error_ `Result` or `Result` will look like this: +```json +{ + "Success": false, + "Errors": [ + { + "Type": "MyError", + "Message": "Some error message" + "Data": { + "Key": "value", + } + } + ] +} +``` \ No newline at end of file